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.120';
3 39     39   2991790 use warnings;
  39     1   461  
  39     1   1494  
  1     1   1617  
  1     1   2  
  1         33  
  1         1345  
  1         2  
  1         26  
  1         1299  
  1         2  
  1         25  
  1         776  
  1         3  
  1         25  
4 39     39   209 use strict;
  39     1   106  
  39     1   737  
  1     1   5  
  1     1   2  
  1         28  
  1         5  
  1         3  
  1         16  
  1         5  
  1         2  
  1         17  
  1         5  
  1         3  
  1         16  
5 39     39   1041 use 5.006;
  39     1   147  
  1     1   32  
  1     1   5  
  1     1   15  
  1         4  
  1         15  
  1         3  
  1         15  
  1         4  
6              
7 39     39   229 use Carp;
  39     1   86  
  39     1   2514  
  1     1   5  
  1     1   2  
  1         65  
  1         5  
  1         1  
  1         50  
  1         6  
  1         1  
  1         47  
  1         5  
  1         2  
  1         44  
8 39     39   224 use Cwd qw( cwd );
  39     1   108  
  39     1   2371  
  1     1   7  
  1     1   3  
  1         43  
  1         5  
  1         2  
  1         28  
  1         8  
  1         2  
  1         29  
  1         5  
  1         2  
  1         28  
9 39     39   16489 use IO::Handle;
  39     1   173609  
  39     1   1831  
  1     1   5  
  1     1   3  
  1         28  
  1         8  
  1         2  
  1         22  
  1         5  
  1         2  
  1         20  
  1         5  
  1         2  
  1         22  
10 39     39   264 use Symbol ();
  39     1   72  
  39     1   835  
  1     1   5  
  1     1   1  
  1         29  
  1         4  
  1         2  
  1         16  
  1         5  
  1         2  
  1         15  
  1         5  
  1         2  
  1         17  
11 39     39   216 use Scalar::Util qw( blessed reftype );
  39     1   84  
  39     1   2140  
  1     1   9  
  1     1   2  
  1         41  
  1         5  
  1         2  
  1         32  
  1         6  
  1         2  
  1         34  
  1         5  
  1         1  
  1         33  
12 39     39   275 use List::Util qw( reduce );
  39     1   68  
  39     1   4590  
  1     1   5  
  1     1   2  
  1         52  
  1         6  
  1         1  
  1         33  
  1         5  
  1         2  
  1         34  
  1         5  
  1         2  
  1         31  
13 39     39   19285 use System::Command::Reaper;
  39     1   99  
  39     1   1083  
  1     1   7  
  1     1   35  
  1         27  
  1         6  
  1         2  
  1         16  
  1         5  
  1         2  
  1         16  
  1         6  
  1         1  
  1         17  
14              
15 39     39   275 use Config;
  39     1   60  
  39     1   1604  
  1     1   6  
  1     1   10  
  1         44  
  1         5  
  1         1  
  1         27  
  1         5  
  1         3  
  1         26  
  1         5  
  1         2  
  1         28  
16 39     39   205 use Fcntl qw( F_GETFD F_SETFD FD_CLOEXEC );
  39     1   54  
  39     1   2158  
  1     1   6  
  1     1   2  
  1         46  
  1         5  
  1         1  
  1         40  
  1         5  
  1         2  
  1         40  
  1         4  
  1         8  
  1         41  
17              
18             # MSWin32 support
19 39     39   222 use constant MSWin32 => $^O eq 'MSWin32';
  39     1   71  
  39     1   14261  
  1     1   6  
  1     1   2  
  1         343  
  1         6  
  1         2  
  1         286  
  1         5  
  1         2  
  1         329  
  1         5  
  1         2  
  1         288  
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   330 my ( $class, @args ) = @_;
37 39     0   166 my %arg = ( quiet => sub { $QUIET = 1 } );
  0         0  
38 39         24826 for my $arg (@args) {
39 1         4 $arg =~ s/^-//; # allow dashed options
40             croak "Unknown option '$arg' in 'use System::Command'"
41 1 50       114 if !exists $arg{$arg};
42 0         0 $arg{$arg}->();
43             }
44             }
45              
46             # a few simple accessors
47             {
48 39     39   333 no strict 'refs';
  39     1   69  
  39     1   3535  
  1     1   56  
  1     1   3  
  1         67  
  1         54  
  1         2  
  1         68  
  1         61  
  1         2  
  1         69  
  1         56  
  1         2  
  1         71  
49             for my $attr (qw( pid stdin stdout stderr options )) {
50 470     470   1117451 *$attr = sub { return $_[0]{$attr} };
51             }
52             for my $attr (qw( exit signal core )) {
53 39     39   244 no strict 'refs';
  39     1   67  
  39     1   95417  
  1     1   6  
  1     1   2  
  1         372  
  1         6  
  1         1  
  1         353  
  1         6  
  1         2  
  1         362  
  1         6  
  1         2  
  1         383  
54 259     259   73595 *$attr = sub { $_[0]->is_terminated(); return $_[0]{$attr} };
  259         2414  
55             }
56             for my $attr (qw( cmdline )) {
57 36     36   13310 *$attr = sub { return @{ $_[0]{$attr} } };
  36         333  
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 159373 my ( $class, @cmd ) = @_;
198              
199             # split the args
200 130         850 my @o = { setpgrp => 1 };
201 130 100       539 @cmd = grep { !( ref eq 'HASH' ? push @o, $_ : 0 ) } @cmd;
  460         2092  
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   1158 ? ( env => { %{ $a->{env} }, %{ $b->{env} } } )
  10         150  
  10         165  
208             : ()
209             };
210             }
211 130         3669 @o;
212              
213             # open the trace file before changing directory
214 130         1494 my ( $trace, $th );
215 130 50       761 ( $trace, $th ) = $_trace_opts->( $o->{trace} ) if $o->{trace};
216 130 50       629 ( $trace, $th ) = @trace if @trace; # environment override
217              
218             # chdir to the expected directory
219 130         458055 my $orig = cwd;
220 130 100       3608 my $dest = defined $o->{cwd} ? $o->{cwd} : undef;
221 130 100       1428 if ( defined $dest ) {
222 7 100       1040 chdir $dest or croak "Can't chdir to $dest: $!";
223             }
224              
225             # keep changes to the environment local
226 129         24507 local %ENV = %ENV;
227              
228             # update the environment
229 129 100       1603 if ( exists $o->{env} ) {
230             croak "ENV variables cannot be empty strings on Win32"
231 35         386 if MSWin32 and grep { defined and !length } values %{ $o->{env} };
232 35         121 @ENV{ keys %{ $o->{env} } } = values %{ $o->{env} };
  35         615  
  35         403  
233             delete $ENV{$_}
234 35         161 for grep { !defined $o->{env}{$_} } keys %{ $o->{env} };
  48         421  
  35         368  
235             }
236              
237             # interactive mode requested
238 129 50       1379 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         489 my ( $pid, $in, $out, $err ) = eval { $_spawn->( $o, @cmd ); };
  129         2853  
265              
266             # FIXME - better check error conditions
267 100 100       1273 if ( !defined $pid ) {
268 1 50       12 $_do_trace->( $trace, $th, '!', \@cmd, $o ) if $trace;
269 1         650 croak $@;
270             }
271              
272             # trace is mostly a debugging tool
273 99 50       870 $_do_trace->( $trace, $th, $pid, \@cmd, $o ) if $trace;
274              
275             # some input was provided
276 99 100       812 if ( defined $o->{input} ) {
277             local $SIG{PIPE}
278 0     0   0 = sub { croak "Broken pipe when writing to: @cmd" }
279 5 50       899 if $Config{sig_name} =~ /\bPIPE\b/;
280 5 100       126 print {$in} $o->{input} if length $o->{input};
  3         93  
281 5         206 $in->close;
282             }
283              
284             # chdir back to origin
285 99 100       770 if ( defined $dest ) {
286 5 50       315 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         8266 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         3215 ${*$in} = ${*$out} = ${*$err} = $self->{reaper} # typeglobs FTW
  99         2476  
  99         1249  
302 99         7056 = System::Command::Reaper->new( $self, { trace => $trace, th => $th } );
303              
304 99         23746 return $self;
305             }
306              
307             sub spawn {
308 2     2 1 3286 my ( $class, @cmd ) = @_;
309 2         18 return @{ $class->new(@cmd) }{qw( pid stdin stdout stderr )};
  2         26  
310             }
311              
312             sub loop_on {
313 3     3 1 237 my $self = shift;
314              
315             # handle options and defaults
316             my %args = (
317 0     0   0 stderr => sub { print STDERR shift },
318             @_
319 3         130 );
320 3         95 for my $which ( grep exists $args{$_}, qw( stdout stderr ) ) {
321 6 100       42 if ( $args{$which} ) {
322             croak "'$which' option must be a CODE reference"
323 5 50       57 if reftype $args{$which} ne 'CODE';
324             }
325             else {
326 1         16 delete $args{$which};
327             }
328             }
329              
330             # create an object for the class method
331 3 50       32 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         3363 require IO::Select;
338 3         6563 my $select = IO::Select->new( $self->stdout, $self->stderr );
339              
340             local $/ = $args{input_record_separator}
341 3 50       496 if exists $args{input_record_separator};
342              
343             # loop until end of streams
344 3         17 while ( my @ready = $select->can_read ) {
345 18         5674 for my $fh (@ready) {
346 28 100       144 my $which = $fh == $self->stdout ? 'stdout' : 'stderr';
347 28 100       389 if ( defined( my $line = <$fh> ) ) {
348 24         70 my $ret = 1;
349             $ret = $args{$which}->($line)
350 24 100       130 if exists $args{$which};
351 24 100       14945 return if !$ret;
352             }
353             else {
354 4         28 $select->remove($fh);
355 4         400 $fh->close;
356             }
357             }
358             }
359              
360             # close all pipes and wait for the child to terminate
361 2         70 $self->close;
362              
363             # success in the Unix sense
364 2   33     10 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 21007580 : $_[0]{reaper}->is_terminated();
372             }
373              
374             sub close {
375 69 50   69 1 72661 $_[0]{reaper}->close() unless $_[0]{options}{interactive};
376 69         411 return $_[0];
377             }
378              
379             1;
380              
381             __END__