package RunPipe_PerlMonks; use strict; use warnings; use IPC::Shareable; use IPC::Run; use FileHandle; my $failed_cmd; my $glue = 'h_09'; my %options = ( create => 'yes', exclusive => 1, mode => 0644, destroy => 1, ); # STATIC method. sub failed_cmd { IPC::Shareable->clean_up_all; return $failed_cmd; } sub new { shift; my $self = { 'cmds' => undef, @_ }; defined $self->{'cmds'} or die "ERROR: didn't get CMD in constructor."; IPC::Shareable->clean_up_all; bless $self; return $self; } sub fh { my $self = shift; return $self->{'readfh'}; } # Input is an array of array references of commands. For example: ( ["ls", "-al"], ["grep", "-v", "something"] ); sub start { my $self = shift; my $fh_glob_ref = shift; my @cmds = @{$self->{'cmds'}} or die "Error: can't start() when CMDS not defined!\n"; my ($readfh, $writefh) = FileHandle::pipe; $self->{'readfh'} = $readfh; $self->{'writefh'} = $writefh; my @ipcArray = (); my $ipc_run_h; #pipe each command to the next foreach my $cmd (@cmds) { push @ipcArray, $cmd; if (defined $fh_glob_ref && @ipcArray == 1) # ('', $writefh, "2>", $writefh); $self->{'h'} = $ipc_run_h; } sub run { my $self = shift; my $ipc_run_h = $self->{'h'}; my $readfh = $self->{'readfh'}; my $writefh = $self->{'writefh'}; defined $readfh or die "Error: READFH not defined\n"; defined $writefh or die "Error: WRITEFH not defined\n"; defined $ipc_run_h or die "Error: ipc_run_h not defined\n"; eval { tie($failed_cmd, 'IPC::Shareable', $glue, { %options }) }; $@ and die "ERROR: GLUE already bound.\n"; # Run cmd as child process, so we can return FH immediately to the running program and process the output in real time. my $pid = fork(); if ($pid) # parent { my $child_pid = waitpid($pid, 0); if ($child_pid == -1) { die "Child stopped with an error! ($!)\n"; } elsif ($child_pid == 0) # child still running { die "ERROR: Child still running, but it should not have returned until done...\n"; } # if it gets here, child has finished. Close parent's copy of writeFH. # note: DONT close the ReadFH. It needs to remain open for the driver program; it will trigger EOF when writeFH is closed. close $writefh; return; } ####### From here onward, it's the child running. #******* Now call $ipc_run_h->finish(), which waitspid() each child. #******* THIS is the failure point in the code because now we're running as the child, and we need to waitpid() for other children (each cmd in the series). #******* Since a child can't waitpid another child, waitpid inside $ipc_run_h->finish() always returns -1, the correct exit code is never returned, and we can't identify the exact cmd that failed. if (!$ipc_run_h->finish()) #failure at some point { my $ctr = 0; foreach my $cmd (@{$self->{'cmds'}}) { #find the point where the failure occured, get the return value at that point if($ipc_run_h->result($ctr) != 0){ my $returnval = $ipc_run_h->result($ctr); # Set the failed command. $failed_cmd = join(" ",@{$cmd}); last; } $ctr++; } } close $readfh; close $writefh; exit; } 1;