Helper to build chain of pipes to avoid passing long strings
with quoted text to the shell.
Takes an input (filename, string-reference for direct text,
filehandle or pipe), an output (same options as for input),
an optional stderr handle (this however can currently only
be a filehandle).
---
 src/PVE/Tools.pm | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)

diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm
index 577a8bc..290b2c3 100644
--- a/src/PVE/Tools.pm
+++ b/src/PVE/Tools.pm
@@ -9,6 +9,7 @@ use IO::Select;
 use File::Basename;
 use File::Path qw(make_path);
 use IO::File;
+use IO::Pipe;
 use IO::Dir;
 use IPC::Open3;
 use Fcntl qw(:DEFAULT :flock);
@@ -1116,4 +1117,163 @@ sub parse_host_and_port {
     return; # nothing
 }
 
+sub wait_on_pids {
+    my @values;
+    foreach my $pid (@_) {
+       while (waitpid($pid, 0) != $pid) {
+           # waiting...
+       }
+       push @values, $?;
+    }
+    return @values;
+}
+
+sub command_pipes {
+    my ($in, $out, $err, @commands) = @_;
+    my @pids;
+
+    # If $in our $out are pipes we don't want to wait for processes and instead
+    # return the list of PIDs. The caller is then responsible to wait for them.
+    my $dont_wait = 0;
+
+    my $first_in; # avoid closing the first handle if it's a pipe
+    my $prev_out;
+    my $prev_out_is_pipe;
+    if (my $ref = ref($in)) {
+       if ($ref eq 'SCALAR') {
+           $first_in = $prev_out = IO::Pipe->new();
+           $prev_out_is_pipe = 1;
+       } elsif ($ref eq 'GLOB') {
+           $prev_out = $in;
+       } else {
+           $first_in = $prev_out = $in;
+           $prev_out_is_pipe = $in->isa('IO::Pipe');
+           $dont_wait = 1;
+       }
+    } elsif ($in) {
+       if ($in ^ $in) {
+           $prev_out = IO::File->new($in, O_RDONLY);
+       } else {
+           $prev_out = IO::Handle->new_from_fd($in, O_RDONLY);
+       }
+    }
+
+    my $setup_redirs = sub {
+       my ($cmd) = @_;
+       my $first;
+       while ($first = shift(@$cmd)) {
+           last if !ref($first);
+           $first = $$first;
+           if ($first =~ /^(\d+)>(\d+)$/) {
+               POSIX::dup2($1, $2);
+           } elsif ($first =~ /^(\d+)>(.+)$/) {
+               my $fd = $1;
+               open my $fh, '>', $2 or die "failed to open $2: $!";
+               POSIX::dup2(fileno($fh), $fd);
+           } elsif ($first =~ /^(\d+)<(.+)$/) {
+               my $fd = $1;
+               open my $fh, '<', $2 or die "failed to open $2: $!";
+               POSIX::dup2(fileno($fh), $fd);
+           } else {
+               die "invalid redirection: $first";
+           }
+       }
+       return $first;
+    };
+
+    my $last_cmd = pop @commands;
+    foreach my $cmd (@commands) {
+       my $current_out = IO::Pipe->new();
+
+       my $pid = fork();
+       if (!$pid) {
+           $prev_out->reader() if $prev_out_is_pipe;
+           $current_out->writer();
+           POSIX::dup2(fileno($prev_out), 0) if $prev_out;
+           POSIX::dup2(fileno($current_out), 1);
+           POSIX::dup2(fileno($err), 2) if $err;
+           my $first = &$setup_redirs($cmd);
+           exec($first, @$cmd);
+           die "failed to execute $first";
+       }
+       push @pids, $pid;
+       $current_out->reader();
+       if ($prev_out) {
+           $prev_out->writer() if $prev_out_is_pipe;
+           close($prev_out) if !$first_in || $prev_out != $first_in;
+       }
+       $prev_out = $current_out;
+       $prev_out_is_pipe = 0;
+    }
+
+    my $last_out;
+    my $last_out_is_pipe;
+    my $close_last_out;
+    my $read_output;
+    if (my $ref = ref($out)) {
+       if ($ref eq 'SCALAR') {
+           $last_out = IO::Pipe->new();
+           $read_output = 1;
+           $last_out_is_pipe = 1;
+       } elsif ($ref eq 'GLOB') {
+           $last_out = $out;
+       } else {
+           $last_out = $out;
+           $last_out_is_pipe = $out->isa('IO::Pipe');
+           $dont_wait = 1;
+       }
+    } elsif ($out) {
+       if ($out ^ $out) {
+           $last_out = IO::File->new($out, O_WRONLY | O_CREAT);
+           $close_last_out = 1;
+       } else {
+           $last_out = IO::Handle->new_from_fd($out, O_WRONLY | O_CREAT);
+       }
+    }
+
+    my $pid = fork();
+    if (!$pid) {
+       $prev_out->reader() if $prev_out_is_pipe;
+       $last_out->writer() if $last_out_is_pipe;
+       POSIX::dup2(fileno($prev_out), 0) if $prev_out;
+       POSIX::dup2(fileno($last_out), 1) if $last_out;
+       POSIX::dup2(fileno($err), 2) if $err;
+       my $first = &$setup_redirs($last_cmd);
+       exec($first, @$last_cmd);
+       die "failed to execute $first";
+    }
+    push @pids, $pid;
+
+    if ($prev_out) {
+       $prev_out->writer() if $prev_out_is_pipe;
+       close($prev_out) if !$first_in || $prev_out != $first_in;
+       undef $prev_out; # only first_in matters from here on
+    }
+
+    if ($last_out) {
+       $last_out->reader() if $last_out_is_pipe;
+       close($last_out) if $close_last_out;
+    }
+
+    if ($first_in && $first_in != $in) {
+       print {$first_in} $$in;
+       close $first_in;
+    }
+
+    if ($read_output) {
+       $$out = do { local $/; <$last_out> };
+       close $last_out;
+    }
+
+    if ($dont_wait) {
+       return @pids;
+    }
+
+    my @status_values = wait_on_pids(@pids);
+    return @status_values if wantarray; # if you want pipefail use any()
+    my $ok = 1;
+    $ok &&= ($_ == 0) foreach @status_values;
+    return $ok;
+}
+
 1;
-- 
2.1.4


_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to