]> git.proxmox.com Git - pve-common.git/blobdiff - data/PVE/Tools.pm
correctly call errfunc inside run_command
[pve-common.git] / data / PVE / Tools.pm
index 6eaf68c7ac3422bd74db51f0f8899ffecf6e147b..33f0de1a217715395c521caf77b218673d9fe51e 100644 (file)
@@ -12,6 +12,9 @@ use Fcntl qw(:DEFAULT :flock);
 use base 'Exporter';
 use URI::Escape;
 use Encode;
+use Digest::SHA1;
+use Text::ParseWords;
+use String::ShellQuote;
 
 our @EXPORT_OK = qw(
 lock_file 
@@ -167,7 +170,7 @@ sub run_command {
 
     $cmd = [ $cmd ] if !ref($cmd);
 
-    my $cmdstr = join (' ', @$cmd);
+    my $cmdstr = cmd2string($cmd);
 
     my $errmsg;
     my $laststderr;
@@ -175,14 +178,13 @@ sub run_command {
     my $oldtimeout;
     my $pid;
 
-    eval {
-       my $reader = IO::File->new();
-       my $writer = IO::File->new();
-       my $error  = IO::File->new();
+    my $outfunc;
+    my $errfunc;
+    my $logfunc;
+    my $input;
+    my $output;
 
-       my $input;
-       my $outfunc;
-       my $errfunc;
+    eval {
 
        foreach my $p (keys %param) {
            if ($p eq 'timeout') {
@@ -191,33 +193,60 @@ sub run_command {
                umask($param{$p});
            } elsif ($p eq 'errmsg') {
                $errmsg = $param{$p};
-               $errfunc = sub {
-                   print STDERR "$laststderr\n" if $laststderr;
-                   $laststderr = shift; 
-               };
            } elsif ($p eq 'input') {
                $input = $param{$p};
+           } elsif ($p eq 'output') {
+               $output = $param{$p};
            } elsif ($p eq 'outfunc') {
                $outfunc = $param{$p};
            } elsif ($p eq 'errfunc') {
                $errfunc = $param{$p};
+           } elsif ($p eq 'logfunc') {
+               $logfunc = $param{$p};
            } else {
                die "got unknown parameter '$p' for run_command\n";
            }
        }
 
+       if ($errmsg) {
+           my $origerrfunc = $errfunc;
+           $errfunc = sub {
+               if ($laststderr) {
+                   if ($origerrfunc) {
+                       &$origerrfunc("$laststderr\n");
+                   } else {
+                       print STDERR "$laststderr\n" if $laststderr;
+                   }
+               }
+               $laststderr = shift; 
+           };
+       }
+
+       my $reader = $output && $output =~ m/^>&/ ? $output : IO::File->new();
+       my $writer = $input && $input =~ m/^<&/ ? $input : IO::File->new();
+       my $error  = IO::File->new();
+
        # try to avoid locale related issues/warnings
        my $lang = $param{lang} || 'C'; 
  
        my $orig_pid = $$;
 
        eval {
-           local $ENV{LANG} = $lang;
+           local $ENV{LC_ALL} = $lang;
 
            # suppress LVM warnings like: "File descriptor 3 left open";
            local $ENV{LVM_SUPPRESS_FD_WARNINGS} = "1";
 
            $pid = open3($writer, $reader, $error, @$cmd) || die $!;
+
+           # if we pipe fron STDIN, open3 closes STDIN, so we we
+           # a perl warning "Filehandle STDIN reopened as GENXYZ .. "
+           # as soon as we open a new file.
+           # to avoid that we open /dev/null
+           if (!ref($writer) && !defined(fileno(STDIN))) {
+               POSIX::close(0);
+               open(STDIN, "</dev/null");
+           }
        };
 
        my $err = $@;
@@ -234,11 +263,13 @@ sub run_command {
        local $SIG{ALRM} = sub { die "got timeout\n"; } if $timeout;
        $oldtimeout = alarm($timeout) if $timeout;
 
-       print $writer $input if defined $input;
-       close $writer;
+       if (ref($writer)) {
+           print $writer $input if defined $input;
+           close $writer;
+       }
 
        my $select = new IO::Select;
-       $select->add($reader);
+       $select->add($reader) if ref($reader);
        $select->add($error);
 
        my $outlog = '';
@@ -260,12 +291,13 @@ sub run_command {
                }
                $select->remove ($h) if !$count;
                if ($h eq $reader) {
-                   if ($outfunc) {
+                   if ($outfunc || $logfunc) {
                        eval {
                            $outlog .= $buf;
                            while ($outlog =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
                                my $line = $1;
-                               &$outfunc($line);
+                               &$outfunc($line) if $outfunc;
+                               &$logfunc($line) if $logfunc;
                            }
                        };
                        my $err = $@;
@@ -279,12 +311,13 @@ sub run_command {
                        *STDOUT->flush();
                    }
                } elsif ($h eq $error) {
-                   if ($errfunc) {
+                   if ($errfunc || $logfunc) {
                        eval {
                            $errlog .= $buf;
                            while ($errlog =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) {
                                my $line = $1;
-                               &$errfunc($line);
+                               &$errfunc($line) if $errfunc;
+                               &$logfunc($line) if $logfunc;
                            }
                        };
                        my $err = $@;
@@ -302,7 +335,10 @@ sub run_command {
        }
 
        &$outfunc($outlog) if $outfunc && $outlog;
+       &$logfunc($outlog) if $logfunc && $outlog;
+
        &$errfunc($errlog) if $errfunc && $errlog;
+       &$logfunc($errlog) if $logfunc && $errlog;
 
        waitpid ($pid, 0);
   
@@ -311,12 +347,14 @@ sub run_command {
        } elsif (my $sig = ($? & 127)) {
            die "got signal $sig\n";
        } elsif (my $ec = ($? >> 8)) {
-           if ($errmsg && $laststderr) {
-               my $lerr = $laststderr;
-               $laststderr = undef;
-               die "$lerr\n";
+           if (!($ec == 24 && ($cmdstr =~ m|^(\S+/)?rsync\s|))) {
+               if ($errmsg && $laststderr) {
+                   my $lerr = $laststderr;
+                   $laststderr = undef;
+                   die "$lerr\n";
+               }
+               die "exit code $ec\n";
            }
-           die "exit code $ec\n";
        }
 
         alarm(0);
@@ -326,7 +364,9 @@ sub run_command {
 
     alarm(0);
 
-    print STDERR "$laststderr\n" if $laststderr;
+    if ($errmsg && $laststderr) {
+       &$errfunc(undef); # flush laststderr
+    }
 
     umask ($old_umask) if defined($old_umask);
 
@@ -345,12 +385,16 @@ sub run_command {
            die "command '$cmdstr' failed: $err";
        }
     }
+
+    return undef;
 }
 
 sub split_list {
     my $listtxt = shift || '';
 
-    $listtxt =~ s/[,;\0]/ /g;
+    return split (/\0/, $listtxt) if $listtxt =~ m/\0/;
+    
+    $listtxt =~ s/[,;]/ /g;
     $listtxt =~ s/^\s+//;
 
     my @data = split (/\s+/, $listtxt);
@@ -589,7 +633,7 @@ sub upid_open {
  
     my $outfh = IO::File->new ($filename, O_WRONLY|O_CREAT|O_EXCL, $perm) ||
        die "unable to create output file '$filename' - $!\n";
-    chown $wwwid, $outfh;
+    chown $wwwid, -1, $outfh;
 
     return $outfh;
 };
@@ -635,5 +679,90 @@ sub decode_text {
     return Encode::decode("utf8", uri_unescape($data));
 }
 
+sub random_ether_addr {
+
+    my $rand = Digest::SHA1::sha1_hex(rand(), time());
+
+    my $mac = '';
+    for (my $i = 0; $i < 6; $i++) {
+       my $ss = hex(substr($rand, $i*2, 2));
+       if (!$i) {
+           $ss &= 0xfe; # clear multicast
+           $ss |= 2; # set local id
+       }
+       $ss = sprintf("%02X", $ss);
+
+       if (!$i) {
+           $mac .= "$ss";
+       } else {
+           $mac .= ":$ss";
+       }
+    }
+
+    return $mac;
+}
+
+sub shellquote {
+    my $str = shift;
+
+    return String::ShellQuote::shell_quote($str);
+}
+
+sub cmd2string {
+    my ($cmd) = @_;
+
+    die "no arguments" if !$cmd;
+
+    return $cmd if !ref($cmd);
+
+    my @qa = ();
+    foreach my $arg (@$cmd) { push @qa, shellquote($arg); }
+
+    return join (' ', @qa);
+}
+
+# split an shell argument string into an array,
+sub split_args {
+    my ($str) = @_;
+
+    return $str ? [ Text::ParseWords::shellwords($str) ] : [];
+}
+
+sub dump_logfile {
+    my ($filename, $start, $limit) = @_;
+
+    my $lines = [];
+    my $count = 0;
+
+    my $fh = IO::File->new($filename, "r");
+    if (!$fh) { 
+       $count++;
+       push @$lines, { n => $count, t => "unable to open file - $!"};
+       return ($count, $lines);
+    }
+
+    $start = 0 if !$start;
+    $limit = 50 if !$limit;
+
+    my $line;
+    while (defined($line = <$fh>)) {
+       next if $count++ < $start;
+       next if $limit <= 0;
+       chomp $line;
+       push @$lines, { n => $count, t => $line};
+       $limit--;
+    }
+
+    close($fh);
+
+    # HACK: ExtJS store.guaranteeRange() does not like empty array
+    # so we add a line
+    if (!$count) {
+       $count++;
+       push @$lines, { n => $count, t => "no content"};
+    }
+
+    return ($count, $lines);
+}
 
 1;