]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/Tools.pm
inotify: also detect VLAN id from "vlan\d+" ifaces
[pve-common.git] / src / PVE / Tools.pm
index 7b82e005d555ac999f82485059c9556d91caf698..c90810ca764449a1365286db1a8f9ac6e79279f2 100644 (file)
@@ -2,30 +2,31 @@ package PVE::Tools;
 
 use strict;
 use warnings;
-use POSIX qw(EINTR EEXIST EOPNOTSUPP);
-use IO::Socket::IP;
-use Socket qw(AF_INET AF_INET6 AI_ALL AI_V4MAPPED AI_CANONNAME SOCK_DGRAM
-             IPPROTO_TCP);
-use IO::Select;
+
+use Date::Format qw(time2str);
+use Digest::MD5;
+use Digest::SHA;
+use Encode;
+use Fcntl qw(:DEFAULT :flock);
 use File::Basename;
 use File::Path qw(make_path);
 use Filesys::Df (); # don't overwrite our df()
-use IO::Pipe;
-use IO::File;
 use IO::Dir;
+use IO::File;
 use IO::Handle;
+use IO::Pipe;
+use IO::Select;
+use IO::Socket::IP;
 use IPC::Open3;
-use Fcntl qw(:DEFAULT :flock);
-use base 'Exporter';
-use URI::Escape;
-use Encode;
-use Digest::SHA;
 use JSON;
-use Text::ParseWords;
+use POSIX qw(EINTR EEXIST EOPNOTSUPP);
+use Scalar::Util 'weaken';
+use Socket qw(AF_INET AF_INET6 AI_ALL AI_V4MAPPED AI_CANONNAME SOCK_DGRAM IPPROTO_TCP);
 use String::ShellQuote;
+use Text::ParseWords;
 use Time::HiRes qw(usleep gettimeofday tv_interval alarm);
-use Scalar::Util 'weaken';
-use Date::Format qw(time2str);
+use URI::Escape;
+use base 'Exporter';
 
 use PVE::Syscall;
 
@@ -104,6 +105,11 @@ use constant {O_PATH    => 0x00200000,
 use constant {AT_EMPTY_PATH => 0x1000,
               AT_FDCWD => -100};
 
+# from <linux/fs.h>
+use constant {RENAME_NOREPLACE => (1 << 0),
+              RENAME_EXCHANGE  => (1 << 1),
+              RENAME_WHITEOUT  => (1 << 2)};
+
 sub run_with_timeout {
     my ($timeout, $code, @param) = @_;
 
@@ -1461,6 +1467,11 @@ sub fsync($) {
     return 0 == syscall(PVE::Syscall::fsync, $fileno);
 }
 
+sub renameat2($$$$$) {
+    my ($olddirfd, $oldpath, $newdirfd, $newpath, $flags) = @_;
+    return 0 == syscall(PVE::Syscall::renameat2, $olddirfd, $oldpath, $newdirfd, $newpath, $flags);
+}
+
 sub sync_mountpoint {
     my ($path) = @_;
     sysopen my $fd, $path, O_RDONLY|O_CLOEXEC or die "failed to open $path: $!\n";
@@ -1830,52 +1841,48 @@ sub safe_compare {
 }
 
 
-# opts
-#  -> hash_required
-#       if 1, at least one checksum has to be specified otherwise an error will be thrown
-#  -> http_proxy
-#  -> https_proxy
-#  -> verify_certificates
-#  -> sha(1|224|256|384|512)sum
-#  -> md5sum
+# opts is a hash ref with the following known properties
+#  hash_required - if 1, at least one checksum has to be specified otherwise an error will be thrown
+#  http_proxy
+#  https_proxy
+#  verify_certificates - if 0 (false) we tell wget to ignore untrusted TLS certs. Default to true
+#  md5sum|sha(1|224|256|384|512)sum - the respective expected checksum string
 sub download_file_from_url {
     my ($dest, $url, $opts) = @_;
 
-    my $tmpdest = "$dest.tmp.$$";
-
-    my $algorithm;
-    my $expected;
-
+    my ($checksum_algorithm, $checksum_expected);
     for ('sha512', 'sha384', 'sha256', 'sha224', 'sha1', 'md5') {
        if (defined($opts->{"${_}sum"})) {
-           $algorithm = $_;
-           $expected = $opts->{"${_}sum"};
+           $checksum_algorithm = $_;
+           $checksum_expected = $opts->{"${_}sum"};
            last;
        }
     }
+    die "checksum required but not specified\n" if ($opts->{hash_required} && !$checksum_algorithm);
 
-    die "checksum required but not specified\n" if ($opts->{hash_required} && !$algorithm);
-
-    my $worker = sub  {
-       my $upid = shift;
+    print "downloading $url to $dest\n";
 
-       print "downloading $url to $dest\n";
+    my $tmpdest = "$dest.tmp.$$";
+    eval {
+       if (-f $dest && $checksum_algorithm) {
+           print "calculating checksum of existing file...";
+           my $checksum_got = get_file_hash($checksum_algorithm, $dest);
 
-       eval {
-           if (-f $dest && $algorithm) {
-               print "calculating checksum of existing file...\n";
-               my $correct = check_file_hash($algorithm, $expected, $dest);
-
-               if ($correct) {
-                   print "file already exists, no need to download\n";
-                   return;
-               } else {
-                   print "mismatch, downloading\n";
-               }
+           if (lc($checksum_got) eq lc($checksum_expected)) {
+               print "OK, got correct file already, no need to download\n";
+               return;
+           } else {
+               # we could re-download, but may not be safe so just abort for now..
+               die "mismatch (got '$checksum_got' != expect '$checksum_expected'), aborting\n";
            }
+       }
 
-           my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $url);
+       local $SIG{INT} = sub {
+           unlink $tmpdest or warn "could not cleanup temporary file: $!";
+           die "got interrupted by signal\n";
+       };
 
+       { # limit the scope of the ENV change
            local %ENV;
            if ($opts->{http_proxy}) {
                $ENV{http_proxy} = $opts->{http_proxy};
@@ -1884,55 +1891,39 @@ sub download_file_from_url {
                $ENV{https_proxy} = $opts->{https_proxy};
            }
 
-           my $verify = $opts->{verify_certificates} // 1;
-           if (!$verify) {
-               push @cmd, '--no-check-certificate';
-           }
+           my $cmd = ['wget', '--progress=dot:giga', '-O', $tmpdest, $url];
 
-           if (run_command([[@cmd]]) != 0) {
-               die "download failed: $!\n";
+           if (!($opts->{verify_certificates} // 1)) { # default to true
+               push @$cmd, '--no-check-certificate';
            }
 
-           if ($algorithm) {
-               print "calculating checksum...\n";
+           run_command($cmd, errmsg => "download failed");
+       }
 
-               my $correct = check_file_hash($algorithm, $expected, $tmpdest);
+       if ($checksum_algorithm) {
+           print "calculating checksum...";
 
-               if ($correct) {
-                   print "checksum verified\n";
-               } else {
-                   die "checksum mismatch\n";
-               }
-           } else {
-               print "no checksum for verification specified\n";
-           }
+           my $checksum_got = get_file_hash($checksum_algorithm, $tmpdest);
 
-           if (!rename($tmpdest, $dest)) {
-               die "unable to save file: $!\n";
+           if (lc($checksum_got) eq lc($checksum_expected)) {
+               print "OK, checksum verified\n";
+           } else {
+               die "ERROR, checksum mismatch: got '$checksum_got' != expect '$checksum_expected'\n";
            }
-       };
-       my $err = $@;
-
-       unlink $tmpdest;
-
-       if ($err) {
-           print "\n";
-           die $err;
        }
 
-       print "download finished\n";
+       rename($tmpdest, $dest) or die "unable to rename temporary file: $!\n";
     };
+    if (my $err = $@) {
+       unlink $tmpdest or warn "could not cleanup temporary file: $!";
+       die $err;
+    }
 
-    my $rpcenv = PVE::RPCEnvironment::get();
-    my $user = $rpcenv->get_user();
-
-    (my $filename = $dest) =~ s!.*/([^/]*)$!$1!;
-
-    return $rpcenv->fork_worker('download', $filename, $user, $worker);
+    print "download of '$url' to '$dest' finished\n";
 }
 
-sub check_file_hash {
-    my ($algorithm, $expected, $filename) = @_;
+sub get_file_hash {
+    my ($algorithm, $filename) = @_;
 
     my $algorithm_map = {
        'md5' => sub { Digest::MD5->new },
@@ -1950,7 +1941,7 @@ sub check_file_hash {
 
     my $digest = $digester->addfile($fh)->hexdigest;
 
-    return lc($digest) eq lc($expected);
+    return lc($digest);
 }
 
 1;