[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