]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/Tools.pm
bump version to 8.2.1
[pve-common.git] / src / PVE / Tools.pm
index cdbee6d14aa43650b16b82c4a83b601a924f74e6..766c8091554a1ff1ed048ea1f634c65cceab00a1 100644 (file)
@@ -62,6 +62,20 @@ CLONE_NEWIPC
 CLONE_NEWUSER
 CLONE_NEWPID
 CLONE_NEWNET
+MS_RDONLY
+MS_NOSUID
+MS_NODEV
+MS_NOEXEC
+MS_SYNCHRONOUS
+MS_REMOUNT
+MS_MANDLOCK
+MS_DIRSYNC
+MS_NOSYMFOLLOW
+MS_NOATIME
+MS_NODIRATIME
+MS_BIND
+MS_MOVE
+MS_REC
 );
 
 my $pvelogdir = "/var/log/pve";
@@ -100,7 +114,7 @@ use constant {CLONE_NEWNS   => 0x00020000,
 
 use constant {O_PATH    => 0x00200000,
               O_CLOEXEC => 0x00080000,
-              O_TMPFILE => 0x00410000}; # This includes O_DIRECTORY
+              O_TMPFILE => 0x00400000 | O_DIRECTORY};
 
 use constant {AT_EMPTY_PATH => 0x1000,
               AT_FDCWD => -100};
@@ -110,6 +124,23 @@ use constant {RENAME_NOREPLACE => (1 << 0),
               RENAME_EXCHANGE  => (1 << 1),
               RENAME_WHITEOUT  => (1 << 2)};
 
+use constant {
+    MS_RDONLY      => (1),
+    MS_NOSUID      => (1 <<  1),
+    MS_NODEV       => (1 <<  2),
+    MS_NOEXEC      => (1 <<  3),
+    MS_SYNCHRONOUS => (1 <<  4),
+    MS_REMOUNT     => (1 <<  5),
+    MS_MANDLOCK    => (1 <<  6),
+    MS_DIRSYNC     => (1 <<  7),
+    MS_NOSYMFOLLOW => (1 <<  8),
+    MS_NOATIME     => (1 << 10),
+    MS_NODIRATIME  => (1 << 11),
+    MS_BIND        => (1 << 12),
+    MS_MOVE        => (1 << 13),
+    MS_REC         => (1 << 14),
+};
+
 sub run_with_timeout {
     my ($timeout, $code, @param) = @_;
 
@@ -118,11 +149,12 @@ sub run_with_timeout {
     my $prev_alarm = alarm 0; # suspend outer alarm early
 
     my $sigcount = 0;
+    my $got_timeout = 0;
 
     my $res;
 
     eval {
-       local $SIG{ALRM} = sub { $sigcount++; die "got timeout\n"; };
+       local $SIG{ALRM} = sub { $sigcount++; $got_timeout = 1;  die "got timeout\n"; };
        local $SIG{PIPE} = sub { $sigcount++; die "broken pipe\n" };
        local $SIG{__DIE__};   # see SA bug 4631
 
@@ -142,9 +174,10 @@ sub run_with_timeout {
     # this shouldn't happen anymore?
     die "unknown error" if $sigcount && !$err; # seems to happen sometimes
 
-    die $err if $err;
+    # assume that user handles timeout err if called in list context
+    die $err if $err && (!wantarray || !$got_timeout);
 
-    return $res;
+    return wantarray ? ($res, $got_timeout) : $res;
 }
 
 # flock: we use one file handle per process, so lock file
@@ -236,7 +269,7 @@ sub lock_file {
 }
 
 sub file_set_contents {
-    my ($filename, $data, $perm)  = @_;
+    my ($filename, $data, $perm, $force_utf8)  = @_;
 
     $perm = 0644 if !defined($perm);
 
@@ -251,6 +284,9 @@ sub file_set_contents {
            }
        }
        die "unable to open file '$tmpname' - $!\n" if !$fh;
+
+       binmode($fh, ":encoding(UTF-8)") if $force_utf8;
+
        die "unable to write '$tmpname' - $!\n" unless print $fh $data;
        die "closing file '$tmpname' failed - $!\n" unless close $fh;
     };
@@ -291,7 +327,10 @@ sub file_read_firstline {
     my ($filename) = @_;
 
     my $fh = IO::File->new ($filename, "r");
-    return undef if !$fh;
+    if (!$fh) {
+       return undef if $! == POSIX::ENOENT;
+       die "file '$filename' exists but open for reading failed - $!\n";
+    }
     my $res = <$fh>;
     chomp $res if $res;
     $fh->close;
@@ -575,7 +614,7 @@ sub run_command {
            }
        }
 
-        alarm(0);
+       alarm(0);
     };
 
     my $err = $@;
@@ -1012,9 +1051,16 @@ sub run_fork_with_timeout {
        $res = $child_res->{result};
        $error = $child_res->{error};
     };
+
+    my $got_timeout = 0;
+    my $wantarray = wantarray; # so it can be queried inside eval
     eval {
        if (defined($timeout)) {
-           run_with_timeout($timeout, $readvalues);
+           if ($wantarray) {
+               (undef, $got_timeout) = run_with_timeout($timeout, $readvalues);
+           } else {
+               run_with_timeout($timeout, $readvalues);
+           }
        } else {
            $readvalues->();
        }
@@ -1029,7 +1075,7 @@ sub run_fork_with_timeout {
     die "interrupted by unexpected signal\n" if $sig_received;
 
     die $error if $error;
-    return $res;
+    return wantarray ? ($res, $got_timeout) : $res;
 }
 
 sub run_fork {
@@ -1265,29 +1311,25 @@ sub split_args {
     return $str ? [ Text::ParseWords::shellwords($str) ] : [];
 }
 
-sub dump_logfile {
-    my ($filename, $start, $limit, $filter) = @_;
+sub dump_logfile_by_filehandle {
+    my ($fh, $filter, $state) = @_;
 
-    my $lines = [];
-    my $count = 0;
+    my $count = ($state->{count} //= 0);
+    my $lines = ($state->{lines} //= []);
+    my $start = ($state->{start} //= 0);
+    my $limit = ($state->{limit} //= 50);
+    my $final = ($state->{final} //= 1);
+    my $read_until_end = ($state->{read_until_end} //= $limit == 0);
 
-    my $fh = IO::File->new($filename, "r");
-    if (!$fh) {
-       $count++;
-       push @$lines, { n => $count, t => "unable to open file - $!"};
-       return ($count, $lines);
-    }
-
-    $start = $start // 0;
-    $limit = $limit // 50;
-
-    my $read_until_end = $limit == 0;
     my $line;
-
     if ($filter) {
        # duplicate code, so that we do not slow down normal path
        while (defined($line = <$fh>)) {
-           next if $line !~ m/$filter/;
+           if (ref($filter) eq 'CODE') {
+               next if !$filter->($line);
+           } else {
+               next if $line !~ m/$filter/;
+           }
            next if $count++ < $start;
            if (!$read_until_end) {
                next if $limit <= 0;
@@ -1308,16 +1350,37 @@ sub dump_logfile {
        }
     }
 
-    close($fh);
-
     # HACK: ExtJS store.guaranteeRange() does not like empty array
     # so we add a line
-    if (!$count) {
+    if (!$count && $final) {
        $count++;
        push @$lines, { n => $count, t => "no content"};
     }
 
-    return ($count, $lines);
+    $state->{count} = $count;
+    $state->{limit} = $limit;
+}
+
+sub dump_logfile {
+    my ($filename, $start, $limit, $filter) = @_;
+
+    my $fh = IO::File->new($filename, "r");
+    if (!$fh) {
+       return (1, { n => 1, t => "unable to open file - $!"});
+    }
+
+    my %state = (
+       'count' => 0,
+       'lines' => [],
+       'start' => $start,
+       'limit' => $limit,
+    );
+
+    dump_logfile_by_filehandle($fh, $filter, \%state);
+
+    close($fh);
+
+    return ($state{'count'}, $state{'lines'});
 }
 
 sub dump_journal {
@@ -1332,7 +1395,7 @@ sub dump_journal {
     my $parser = sub {
        my $line = shift;
 
-        return if $count++ < $start;
+       return if $count++ < $start;
        return if $limit <= 0;
        push @$lines, { n => int($count), t => $line};
        $limit--;
@@ -1566,6 +1629,7 @@ sub sendmail {
 
     if (defined($text)) {
        print $mail "Content-Type: text/plain;\n";
+       print $mail "Auto-Submitted: auto-generated;\n";
        print $mail "\tcharset=\"UTF-8\"\n";
        print $mail "Content-Transfer-Encoding: 8bit\n";
        print $mail "\n";
@@ -1581,6 +1645,7 @@ sub sendmail {
 
     if (defined($html)) {
        print $mail "Content-Type: text/html;\n";
+       print $mail "Auto-Submitted: auto-generated;\n";
        print $mail "\tcharset=\"UTF-8\"\n";
        print $mail "Content-Transfer-Encoding: 8bit\n";
        print $mail "\n";
@@ -1686,6 +1751,11 @@ sub mkdirat($$$) {
     return syscall(PVE::Syscall::mkdirat, int($dirfd), $name, int($mode)) == 0;
 }
 
+sub mknod($$$) {
+    my ($filename, $mode, $dev) = @_;
+    return syscall(PVE::Syscall::SYS_mknod, $filename, int($mode), int($dev)) == 0;
+}
+
 sub fchownat($$$$$) {
     my ($dirfd, $pathname, $owner, $group, $flags) = @_;
     return syscall(
@@ -1991,10 +2061,14 @@ sub download_file_from_url {
        }
     }
 
-    my $tmpdest = "$dest.tmp.$$";
+    my $tmp_download = "$dest.tmp_dwnl.$$";
+    my $tmp_decomp = "$dest.tmp_dcom.$$";
     eval {
        local $SIG{INT} = sub {
-           unlink $tmpdest or warn "could not cleanup temporary file: $!";
+           unlink $tmp_download or warn "could not cleanup temporary file: $!"
+               if -e $tmp_download;
+           unlink $tmp_decomp or warn "could not cleanup temporary file: $!"
+               if $opts->{decompression_command} && -e $tmp_decomp;
            die "got interrupted by signal\n";
        };
 
@@ -2007,7 +2081,7 @@ sub download_file_from_url {
                $ENV{https_proxy} = $opts->{https_proxy};
            }
 
-           my $cmd = ['wget', '--progress=dot:giga', '-O', $tmpdest, $url];
+           my $cmd = ['wget', '--progress=dot:giga', '-O', $tmp_download, $url];
 
            if (!($opts->{verify_certificates} // 1)) { # default to true
                push @$cmd, '--no-check-certificate';
@@ -2019,7 +2093,7 @@ sub download_file_from_url {
        if ($checksum_algorithm) {
            print "calculating checksum...";
 
-           my $checksum_got = get_file_hash($checksum_algorithm, $tmpdest);
+           my $checksum_got = get_file_hash($checksum_algorithm, $tmp_download);
 
            if (lc($checksum_got) eq lc($checksum_expected)) {
                print "OK, checksum verified\n";
@@ -2029,10 +2103,25 @@ sub download_file_from_url {
            }
        }
 
-       rename($tmpdest, $dest) or die "unable to rename temporary file: $!\n";
+       if (my $cmd = $opts->{decompression_command}) {
+           push @$cmd, $tmp_download;
+           my $fh;
+           if (!open($fh, ">", "$tmp_decomp")) {
+               die "cant open temporary file $tmp_decomp for decompresson: $!\n";
+           }
+           print "decompressing $tmp_download to $tmp_decomp\n";
+           run_command($cmd, output => '>&'.fileno($fh));
+           unlink $tmp_download;
+           rename($tmp_decomp, $dest) or die "unable to rename temporary file: $!\n";
+       } else {
+           rename($tmp_download, $dest) or die "unable to rename temporary file: $!\n";
+       }
     };
     if (my $err = $@) {
-       unlink $tmpdest or warn "could not cleanup temporary file: $!";
+       unlink $tmp_download or warn "could not cleanup temporary file: $!"
+           if -e $tmp_download;
+       unlink $tmp_decomp or warn "could not cleanup temporary file: $!"
+           if $opts->{decompression_command} && -e $tmp_decomp;
        die $err;
     }
 
@@ -2061,4 +2150,35 @@ sub get_file_hash {
     return lc($digest);
 }
 
+# compare two perl variables recursively, so this works for scalars, nested
+# hashes and nested arrays
+sub is_deeply {
+    my ($a, $b) = @_;
+
+    return 0 if defined($a) != defined($b);
+    return 1 if !defined($a); # both are undef
+
+    my ($ref_a, $ref_b) = (ref($a), ref($b));
+
+    # scalar case
+    return 0 if !$ref_a && !$ref_b && "$a" ne "$b";
+
+    # different types, ok because ref never returns undef, only empty string
+    return 0 if $ref_a ne $ref_b;
+
+    if ($ref_a eq 'HASH') {
+       return 0 if scalar(keys $a->%*) != scalar(keys $b->%*);
+       for my $opt (keys $a->%*) {
+           return 0 if !is_deeply($a->{$opt}, $b->{$opt});
+       }
+    } elsif ($ref_a eq 'ARRAY') {
+       return 0 if scalar($a->@*) != scalar($b->@*);
+       for (my $i = 0; $i < $a->@*; $i++) {
+           return 0 if !is_deeply($a->[$i], $b->[$i]);
+       }
+    }
+
+    return 1;
+}
+
 1;