[pve-devel] [PATCH pve-common] Added Tools::command_pipes helper
Wolfgang Bumiller
w.bumiller at proxmox.com
Thu Sep 3 14:18:16 CEST 2015
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
More information about the pve-devel
mailing list