File Coverage

blib/lib/System/Command.pm
Criterion Covered Total %
statement 301 315 95.5
branch 43 60 71.6
condition 4 12 33.3
subroutine 85 88 96.5
pod 5 5 100.0
total 438 480 91.2


line stmt bran cond sub pod time code
1             package System::Command;
2             $System::Command::VERSION = '1.121';
3 39     39   2987695 use warnings;
  39     1   817  
  39     1   1439  
  1     1   1456  
  1     1   2  
  1         27  
  1         1320  
  1         2  
  1         24  
  1         1290  
  1         2  
  1         25  
  1         737  
  1         2  
  1         24  
4 39     39   250 use strict;
  39     1   68  
  39     1   747  
  1     1   5  
  1     1   2  
  1         39  
  1         4  
  1         2  
  1         16  
  1         5  
  1         1  
  1         16  
  1         4  
  1         2  
  1         16  
5 39     39   934 use 5.006;
  39     1   129  
  1     1   25  
  1     1   4  
  1     1   14  
  1         4  
  1         14  
  1         4  
  1         15  
  1         3  
6              
7 39     39   243 use Carp;
  39     1   73  
  39     1   2385  
  1     1   5  
  1     1   10  
  1         56  
  1         4  
  1         2  
  1         46  
  1         5  
  1         2  
  1         43  
  1         4  
  1         2  
  1         42  
8 39     39   279 use Cwd qw( cwd );
  39     1   70  
  39     1   2015  
  1     1   7  
  1     1   2  
  1         41  
  1         6  
  1         1  
  1         27  
  1         5  
  1         1  
  1         28  
  1         5  
  1         2  
  1         26  
9 39     39   16407 use IO::Handle;
  39     1   173319  
  39     1   1744  
  1     1   6  
  1     1   2  
  1         82  
  1         5  
  1         2  
  1         21  
  1         5  
  1         2  
  1         20  
  1         5  
  1         2  
  1         20  
10 39     39   318 use Symbol ();
  39     1   170  
  39     1   902  
  1     1   6  
  1     1   2  
  1         30  
  1         5  
  1         2  
  1         15  
  1         5  
  1         1  
  1         16  
  1         5  
  1         2  
  1         17  
11 39     39   226 use Scalar::Util qw( blessed reftype );
  39     1   73  
  39     1   2197  
  1     1   6  
  1     1   1  
  1         41  
  1         5  
  1         1  
  1         30  
  1         4  
  1         2  
  1         34  
  1         4  
  1         2  
  1         31  
12 39     39   262 use List::Util qw( reduce );
  39     1   84  
  39     1   5080  
  1     1   4  
  1     1   2  
  1         46  
  1         5  
  1         1  
  1         32  
  1         5  
  1         1  
  1         33  
  1         5  
  1         1  
  1         31  
13 39     39   19239 use System::Command::Reaper;
  39     1   99  
  39     1   1074  
  1     1   7  
  1     1   1  
  1         16  
  1         6  
  1         1  
  1         16  
  1         5  
  1         1  
  1         16  
  1         5  
  1         2  
  1         15  
14              
15 39     39   241 use Config;
  39     1   75  
  39     1   1805  
  1     1   4  
  1     1   9  
  1         43  
  1         4  
  1         2  
  1         26  
  1         4  
  1         2  
  1         26  
  1         4  
  1         2  
  1         25  
16 39     39   214 use Fcntl qw( F_GETFD F_SETFD FD_CLOEXEC );
  39     1   80  
  39     1   2070  
  1     1   6  
  1     1   2  
  1         53  
  1         4  
  1         2  
  1         39  
  1         5  
  1         1  
  1         40  
  1         5  
  1         2  
  1         40  
17              
18             # MSWin32 support
19 39     39   235 use constant MSWin32 => $^O eq 'MSWin32';
  39     1   75  
  39     1   14019  
  1     1   7  
  1     1   3  
  1         336  
  1         5  
  1         2  
  1         280  
  1         5  
  1         2  
  1         300  
  1         15  
  1         2  
  1         267  
20             require IPC::Run if MSWin32;
21              
22             our $QUIET = 0;
23              
24             # trace setup at startup
25             my $_trace_opts = sub {
26             my ( $trace, $file, $th ) = split /=/, shift, 2;
27             open $th, '>>', $file or carp "Can't open $file: $!" if $file;
28             $th ||= *STDERR;
29             return ( $trace, $th );
30             };
31             my @trace;
32             @trace = $_trace_opts->( $ENV{SYSTEM_COMMAND_TRACE} )
33             if $ENV{SYSTEM_COMMAND_TRACE};
34              
35             sub import {
36 39     39   353 my ( $class, @args ) = @_;
37 39     0   177 my %arg = ( quiet => sub { $QUIET = 1 } );
  0         0  
38 39         24637 for my $arg (@args) {
39 1         4 $arg =~ s/^-//; # allow dashed options
40             croak "Unknown option '$arg' in 'use System::Command'"
41 1 50       111 if !exists $arg{$arg};
42 0         0 $arg{$arg}->();
43             }
44             }
45              
46             # a few simple accessors
47             {
48 39     39   317 no strict 'refs';
  39     1   73  
  39     1   3424  
  1     1   56  
  1     1   1  
  1         69  
  1         54  
  1         2  
  1         68  
  1         57  
  1         2  
  1         68  
  1         57  
  1         1  
  1         70  
49             for my $attr (qw( pid stdin stdout stderr options )) {
50 467     467   1129112 *$attr = sub { return $_[0]{$attr} };
51             }
52             for my $attr (qw( exit signal core )) {
53 39     39   264 no strict 'refs';
  39     1   176  
  39     1   96201  
  1     1   5  
  1     1   2  
  1         388  
  1         5  
  1         2  
  1         371  
  1         5  
  1         2  
  1         379  
  1         5  
  1         2  
  1         390  
54 259     259   73204 *$attr = sub { $_[0]->is_terminated(); return $_[0]{$attr} };
  259         2156  
55             }
56             for my $attr (qw( cmdline )) {
57 36     36   12333 *$attr = sub { return @{ $_[0]{$attr} } };
  36         256  
58             }
59             }
60              
61             # REALLY PRIVATE FUNCTIONS
62             # a sub-process spawning function
63             my $_spawn = sub {
64             my ($o, @cmd) = @_;
65             my $pid;
66              
67             # setup filehandles
68             my $in = Symbol::gensym;
69             my $out = Symbol::gensym;
70             my $err = Symbol::gensym;
71              
72             # no buffering on pipes used for writing
73             select( ( select($in), $| = 1 )[0] );
74              
75             # start the command
76             if (MSWin32) {
77             $pid = IPC::Run::start(
78             [@cmd],
79             ' $in,
80             '>pipe' => $out,
81             '2>pipe' => $err,
82             );
83             }
84             else {
85              
86             # the code below takes inspiration from IPC::Open3 and Sys::Cmd
87              
88             # create handles for the child process (using CAPITALS)
89             my $IN = Symbol::gensym;
90             my $OUT = Symbol::gensym;
91             my $ERR = Symbol::gensym;
92              
93             # no buffering on pipes used for writing
94             select( ( select($OUT), $| = 1 )[0] );
95             select( ( select($ERR), $| = 1 )[0] );
96              
97             # connect parent and child with pipes
98             pipe $IN, $in or croak "input pipe(): $!";
99             pipe $out, $OUT or croak "output pipe(): $!";
100             pipe $err, $ERR or croak "errput pipe(): $!";
101              
102             # an extra pipe to communicate exec() failure
103             pipe my ( $stat_r, $stat_w );
104              
105             # create the child process
106             $pid = fork;
107             croak "Can't fork: $!" if !defined $pid;
108              
109             if ($pid) {
110              
111             # parent won't use those handles
112             close $stat_w;
113             close $IN;
114             close $OUT;
115             close $ERR;
116              
117             # failed to fork+exec?
118             my $mesg = do { local $/; <$stat_r> };
119             die $mesg if $mesg;
120             }
121             else { # kid
122              
123             # use $stat_r to communicate errors back to the parent
124             eval {
125              
126             # child won't use those handles
127             close $stat_r;
128             close $in;
129             close $out;
130             close $err;
131              
132             # setup process group if possible
133             setpgrp 0, 0 if $o->{setpgrp} && $Config{d_setpgrp};
134              
135             # close $stat_w on exec
136             my $flags = fcntl( $stat_w, F_GETFD, 0 )
137             or croak "fcntl GETFD failed: $!";
138             fcntl( $stat_w, F_SETFD, $flags | FD_CLOEXEC )
139             or croak "fcntl SETFD failed: $!";
140              
141             # associate STDIN, STDOUT and STDERR to the pipes
142             my ( $fd_IN, $fd_OUT, $fd_ERR )
143             = ( fileno $IN, fileno $OUT, fileno $ERR );
144             open \*STDIN, "<&=$fd_IN"
145             or croak "Can't open( \\*STDIN, '<&=$fd_IN' ): $!";
146             open \*STDOUT, ">&=$fd_OUT"
147             or croak "Can't open( \\*STDOUT, '<&=$fd_OUT' ): $!";
148             open \*STDERR, ">&=$fd_ERR"
149             or croak "Can't open( \\*STDERR, '<&=$fd_ERR' ): $!";
150              
151             # and finally, exec into @cmd
152             exec( { $cmd[0] } @cmd )
153             or do { croak "Can't exec( @cmd ): $!"; }
154             };
155              
156             # something went wrong
157             print $stat_w $@;
158             close $stat_w;
159              
160             # DIE DIE DIE
161             eval { require POSIX; POSIX::_exit(255); };
162             exit 255;
163             }
164             }
165              
166             return ( $pid, $in, $out, $err );
167             };
168              
169             my $_dump_ref = sub {
170             require Data::Dumper; # only load if needed
171             local $Data::Dumper::Indent = 0;
172             local $Data::Dumper::Purity = 0;
173             local $Data::Dumper::Maxdepth = 0;
174             local $Data::Dumper::Quotekeys = 0;
175             local $Data::Dumper::Sortkeys = 1;
176             local $Data::Dumper::Useqq = 1;
177             local $Data::Dumper::Terse = 1;
178             return Data::Dumper->Dump( [shift] );
179             };
180              
181             my $_do_trace = sub {
182             my ( $trace, $th, $pid, $cmd, $o ) = @_;
183             print $th "System::Command cmd[$pid]: ",
184             join( ' ', map /\s/ ? $_dump_ref->($_) : $_, @$cmd ), "\n";
185             print $th map "System::Command opt[$pid]: $_->[0] => $_->[1]\n",
186             map [ $_ => $_dump_ref->( $o->{$_} ) ],
187             grep { $_ ne 'env' } sort keys %$o
188             if $trace > 1;
189             print $th map "System::Command env[$pid]: $_->[0] => $_->[1]\n",
190             map [ $_ => $_dump_ref->( $o->{env}{$_} ) ],
191             keys %{ $o->{env} || {} }
192             if $trace > 2;
193             };
194              
195             # module methods
196             sub new {
197 130     130 1 155393 my ( $class, @cmd ) = @_;
198              
199             # split the args
200 130         828 my @o = { setpgrp => 1 };
201 130 100       453 @cmd = grep { !( ref eq 'HASH' ? push @o, $_ : 0 ) } @cmd;
  460         1982  
202              
203             # merge the option hashes
204             my $o = reduce {
205             +{ %$a, %$b,
206             exists $a->{env} && exists $b->{env}
207 86 100 100 86   1303 ? ( env => { %{ $a->{env} }, %{ $b->{env} } } )
  10         30  
  10         125  
208             : ()
209             };
210             }
211 130         3741 @o;
212              
213             # open the trace file before changing directory
214 130         1414 my ( $trace, $th );
215 130 50       685 ( $trace, $th ) = $_trace_opts->( $o->{trace} ) if $o->{trace};
216 130 50       677 ( $trace, $th ) = @trace if @trace; # environment override
217              
218             # chdir to the expected directory
219 130         484652 my $orig = cwd;
220 130 100       3793 my $dest = defined $o->{cwd} ? $o->{cwd} : undef;
221 130 100       1294 if ( defined $dest ) {
222 7 100       962 chdir $dest or croak "Can't chdir to $dest: $!";
223             }
224              
225             # keep changes to the environment local
226 129         28679 local %ENV = %ENV;
227              
228             # update the environment
229 129 100       1719 if ( exists $o->{env} ) {
230             croak "ENV variables cannot be empty strings on Win32"
231 35         242 if MSWin32 and grep { defined and !length } values %{ $o->{env} };
232 35         133 @ENV{ keys %{ $o->{env} } } = values %{ $o->{env} };
  35         634  
  35         453  
233             delete $ENV{$_}
234 35         179 for grep { !defined $o->{env}{$_} } keys %{ $o->{env} };
  48         404  
  35         373  
235             }
236              
237             # interactive mode requested
238 129 50       1089 if ( $o->{interactive} ) {
239 0 0       0 croak "Can't run command in interactive mode: not a terminal"
240             unless -t STDIN;
241              
242 0         0 system { $cmd[0] } @cmd;
  0         0  
243              
244 0         0 my $self = bless {
245             cmdline => [@cmd],
246             options => $o,
247             stdin => IO::Handle->new,
248             stdout => IO::Handle->new,
249             stderr => IO::Handle->new,
250             exit => $? >> 8,
251             signal => $? & 127,
252             core => $? & 128,
253             }, $class;
254              
255             defined reftype( $o->{$_} )
256             and reftype( $o->{$_} ) eq 'SCALAR'
257 0         0 and ${ $o->{$_} } = $self->{$_}
258 0   0     0 for qw( exit signal core );
      0        
259              
260 0         0 return $self;
261             }
262              
263             # start the command
264 129         873 my ( $pid, $in, $out, $err ) = eval { $_spawn->( $o, @cmd ); };
  129         2858  
265              
266             # FIXME - better check error conditions
267 100 100       1555 if ( !defined $pid ) {
268 1 50       18 $_do_trace->( $trace, $th, '!', \@cmd, $o ) if $trace;
269 1         1241 croak $@;
270             }
271              
272             # trace is mostly a debugging tool
273 99 50       841 $_do_trace->( $trace, $th, $pid, \@cmd, $o ) if $trace;
274              
275             # some input was provided
276 99 100       1075 if ( defined $o->{input} ) {
277             local $SIG{PIPE}
278 0     0   0 = sub { croak "Broken pipe when writing to: @cmd" }
279 5 50       1330 if $Config{sig_name} =~ /\bPIPE\b/;
280 5 100       130 print {$in} $o->{input} if length $o->{input};
  3         90  
281 5         340 $in->close;
282             }
283              
284             # chdir back to origin
285 99 100       1045 if ( defined $dest ) {
286 5 50       375 chdir $orig or croak "Can't chdir back to $orig: $!";
287             }
288              
289             # create the object
290             my $self = bless {
291             cmdline => [@cmd],
292             options => $o,
293 99         8415 pid => MSWin32 ? $pid->{KIDS}[0]{PID} : $pid,
294             stdin => $in,
295             stdout => $out,
296             stderr => $err,
297             ( _ipc_run => $pid )x!! MSWin32,
298             }, $class;
299              
300             # create the subprocess reaper and link the handles and command to it
301 99         2760 ${*$in} = ${*$out} = ${*$err} = $self->{reaper} # typeglobs FTW
  99         1877  
  99         1053  
302 99         7027 = System::Command::Reaper->new( $self, { trace => $trace, th => $th } );
303              
304 99         22392 return $self;
305             }
306              
307             sub spawn {
308 2     2 1 4360 my ( $class, @cmd ) = @_;
309 2         18 return @{ $class->new(@cmd) }{qw( pid stdin stdout stderr )};
  2         12  
310             }
311              
312             sub loop_on {
313 3     3 1 164 my $self = shift;
314              
315             # handle options and defaults
316             my %args = (
317 0     0   0 stderr => sub { print STDERR shift },
318             @_
319 3         102 );
320 3         76 for my $which ( grep exists $args{$_}, qw( stdout stderr ) ) {
321 6 100       30 if ( $args{$which} ) {
322             croak "'$which' option must be a CODE reference"
323 5 50       46 if reftype $args{$which} ne 'CODE';
324             }
325             else {
326 1         10 delete $args{$which};
327             }
328             }
329              
330             # create an object for the class method
331 3 50       18 if ( !ref $self ) {
332             die "'command' attribute required by loop_on when used as a class method"
333 0 0       0 if !exists $args{command};
334 0         0 $self = $self->new( @{ $args{command} } );
  0         0  
335             }
336              
337 3         2628 require IO::Select;
338 3         5868 my $select = IO::Select->new( $self->stdout, $self->stderr );
339              
340             local $/ = $args{input_record_separator}
341 3 50       503 if exists $args{input_record_separator};
342              
343             # loop until end of streams
344 3         18 while ( my @ready = $select->can_read ) {
345 16         12576 for my $fh (@ready) {
346 25 100       174 my $which = $fh == $self->stdout ? 'stdout' : 'stderr';
347 25 100       320 if ( defined( my $line = <$fh> ) ) {
348 21         57 my $ret = 1;
349             $ret = $args{$which}->($line)
350 21 100       105 if exists $args{$which};
351 21 100       15835 return if !$ret;
352             }
353             else {
354 4         32 $select->remove($fh);
355 4         332 $fh->close;
356             }
357             }
358             }
359              
360             # close all pipes and wait for the child to terminate
361 2         90 $self->close;
362              
363             # success in the Unix sense
364 2   33     6 return defined $self->exit && $self->exit == 0;
365             }
366              
367             # delegate those to the reaper (when there's one)
368             sub is_terminated {
369             return $_[0]{options}{interactive}
370             ? 1
371 333 50   333 1 21008242 : $_[0]{reaper}->is_terminated();
372             }
373              
374             sub close {
375 69 50   69 1 61613 $_[0]{reaper}->close() unless $_[0]{options}{interactive};
376 69         414 return $_[0];
377             }
378              
379             1;
380              
381             __END__