# # Copyright (c) 2003-2014 Christian Jaeger, copying@christianjaeger.ch # # This is free software, offered under either the same terms as perl 5 # or the terms of the Artistic License version 2 or the terms of the # MIT License (Expat version). See the file COPYING.md that came # bundled with this file. # =head1 NAME Chj::IO::CommandCommon =head1 SYNOPSIS - not to be used directly - =head1 DESCRIPTION Common superclass ('mixin'?) for Chj::IO::Command and Chj::IO::CommandBidirectional. =head1 NOTE This is alpha software! Read the package README. =cut #' package Chj::IO::CommandCommon; use strict; use warnings; use warnings FATAL => 'uninitialized'; use Chj::xperlfunc; use Chj::xpipe; use Carp; use NEXT; use Chj::singlequote 'singlequote_sh'; my %metadata; # numified => [pid, cmd+args] sub PID () {0} sub CMD_ARGS () {1} sub pid { my $self=shift; my $meta= $metadata{pack "I",$self} || die "missing metadata"; # ||[]; why? $$meta[PID] } sub name { my $self=shift; my $meta= $metadata{pack "I",$self} || die "missing metadata"; $$meta[CMD_ARGS][0] # XX ok? what use is it? } sub quotedname { my $self=shift; my $meta= $metadata{pack "I",$self} || die "missing metadata"; join(" ", map { singlequote_sh $_ } @{$$meta[CMD_ARGS]}) } sub _launch { my ($subname,$otherendclose,$closeinchild)=@_; sub {# curry unnecessary, but whatever my $self=shift; my ($cmd)=@_; my $maybe_env; if (ref($$cmd[0]) eq "HASH") { # env settings (since with "use threads" $ENV does not work!) $maybe_env= shift @$cmd; } @$cmd or die "$subname: missing cmd arguments"; my ($readerr,$writeerr)=xpipe; if (my $pid= xfork) { $metadata{pack"I",$self}= [$pid, $cmd]; &$otherendclose; $writeerr->xclose; #" here it's clear that it's needed." # close all handles that have been given for redirections? # or leave that to the user? the latter. my $err= $readerr->xcontent; if ($err) { croak (__PACKAGE__."::$subname: could not execute " .join(" ",map{singlequote_sh $_}@$cmd) .": $err"); } return $self } else { &$closeinchild; if (defined $maybe_env) { my @newcmd= ("/usr/bin/env"); my $env= $maybe_env; for my $k (keys %$env) { die "invalid env key starting with '-': ".singlequote_sh($k) if $k=~ /^-/; push @newcmd, "$k=$$env{$k}"; } push @newcmd, @$cmd; $cmd= \@newcmd; } if (ref($$cmd[0]) eq "CODE") { my $code= shift @$cmd; eval { $code->(@$cmd); die "coderf did return"; }; $writeerr->xprint($@);# [well serialize it?..tja..] } else { no warnings; exec @$cmd; $writeerr->xprint($!); } exit; } } } sub xlaunch { my $self=shift; my ($otherend,$hdl,@cmd)=@_; _launch ("xlaunch", sub { $otherend->xclose; # ^important; seems like it's not cleaned up and destroyed # otherwise upon return from new_* methods [soon enough], # xcontent and the like would block outside. }, sub { $otherend->xdup2($hdl); }) ->($self,\@cmd); } sub xlaunch3 { my $self=shift; my ($in,$out,$err,@cmd)=@_; _launch ("xlaunch3", sub { }, sub { $in->xdup2(0) if $in; $out->xdup2(1) if $out; $err->xdup2(2) if $err; }) ->($self,\@cmd); } sub wait { my $s=shift; waitpid $s->pid,0; $? } sub finish { my $s=shift; $s->close; my $rv= $s->wait; delete$metadata{pack"I",$s}; $rv; } sub finish_nowait { my $s=shift; $s->close; delete$metadata{pack"I",$s}; } sub xfinish { # Note: does not throw on error exit codes. Just throws # on errors closing. my $self=shift; $self->xclose; waitpid $self->pid,0; delete$metadata{pack"I",$self}; $? } sub xxfinish { # does also throw on error exit codes. my $self=shift; $self->xclose; waitpid $self->pid,0; if ($?==0) { delete $metadata{pack"I",$self}; } else { my $qn= $self->quotedname; delete $metadata{pack"I",$self}; # [XX really? or only in DESTROY?] if ($? & 127) { croak ("xxfinish on ".$qn .": subcommand has been killed with signal ".($? & 127)); } else { croak ("xxfinish on ".$qn .": subcommand gave error ".($? >>8)); } } } sub DESTROY { # no exceptions thrown from here my $self=shift; local ($@,$!,$?); if (defined $metadata{pack "I",$self}) { $self->finish; } $self->NEXT::DESTROY; } 1