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