File Coverage

lib/IPC/Open3/Utils.pm
Criterion Covered Total %
statement 154 177 87.0
branch 105 144 72.9
condition 32 54 59.2
subroutine 17 19 89.4
pod 8 8 100.0
total 316 402 78.6


line stmt bran cond sub pod time code
1             package IPC::Open3::Utils;
2              
3 4     4   504524 use strict;
  4         11  
  4         164  
4 4     4   35 use warnings;
  4         11  
  4         4773  
5              
6             $IPC::Open3::Utils::VERSION = '0.91';
7              
8             require Exporter;
9             @IPC::Open3::Utils::ISA = qw(Exporter);
10             @IPC::Open3::Utils::EXPORT = qw(run_cmd put_cmd_in);
11             @IPC::Open3::Utils::EXPORT_OK = qw(
12             run_cmd put_cmd_in
13             child_error_ok child_error_failed_to_execute
14             child_error_exit_signal child_error_seg_faulted
15             child_error_core_dumped child_error_exit_value
16             create_ipc_open3_utils_wrap_script
17             );
18             %IPC::Open3::Utils::EXPORT_TAGS = (
19             'all' => \@IPC::Open3::Utils::EXPORT_OK,
20             'cmd' => [qw(run_cmd put_cmd_in)],
21             'err' => [
22             qw(
23             child_error_ok child_error_failed_to_execute
24             child_error_exit_signal child_error_seg_faulted
25             child_error_core_dumped child_error_exit_value
26             )
27             ],
28             );
29              
30             require IO::Select;
31             require IPC::Open3;
32             require IO::Handle;
33              
34             sub run_cmd {
35 47     47 1 49354 my @cmd = @_;
36 47 50       302 my $arg_hr = ref $cmd[-1] eq 'HASH' ? pop(@cmd) : {};
37 47   100     587 $arg_hr->{'ignore_handle'} ||= '';
38              
39 47 100       264 if ( ref $arg_hr->{'handler'} ne 'CODE' ) {
40             $arg_hr->{'handler'} = sub {
41 0     0   0 my ( $cur_line, $stdin, $is_stderr, $is_open3_err, $short_circuit_loop_sr ) = @_;
42 0 0       0 if ($is_stderr) {
43 0         0 print STDERR $cur_line;
44             }
45             else {
46 0         0 print STDOUT $cur_line;
47             }
48              
49 0         0 return 1;
50 14         158 };
51             }
52              
53 47         804 my $stdout = IO::Handle->new();
54 47         2070 my $stderr = IO::Handle->new(); # TODO ? $arg_hr->{'combine_fhs'} ? $stdout : IO::Handle->new(); && then no select()
55 47         1159 my $stdin = IO::Handle->new();
56 47         1639 my $sel = IO::Select->new();
57              
58 47 100       737 if ( ref $arg_hr->{'autoflush'} eq 'HASH' ) {
59 1 50       10 $stdout->autoflush(1) if $arg_hr->{'autoflush'}{'stdout'};
60 1 50       69 $stderr->autoflush(1) if $arg_hr->{'autoflush'}{'stderr'};
61 1 50       36 $stdin->autoflush(1) if $arg_hr->{'autoflush'}{'stdin'};
62             }
63              
64             # this is a hack to work around an exit-before-use race condition
65 47 50 33     1537 local $SIG{'PIPE'} = exists $SIG{'PIPE'} && defined $SIG{'PIPE'} ? $SIG{'PIPE'} : '';
66 47         228 my $current_sig_pipe = $SIG{'PIPE'};
67 47 100       574 if ( exists $arg_hr->{'pre_read_print_to_stdin'} ) {
68             $SIG{'PIPE'} = sub {
69              
70             # my $oserr = $!;
71             # my $cherr = $?;
72 2 50 33 2   558 $stdin->close if defined $stdin && ref $stdin eq 'IO::Handle'; # && !$arg_hr->{'close_stdin'};
73 2         148 $stdout->close;
74 2         36 $stderr->close;
75              
76             # $! = $oserr;
77             # $? = $cherr;
78 2 50 33     65 $current_sig_pipe->() if $current_sig_pipe && ref $current_sig_pipe eq 'CODE';
79 7         79 };
80             }
81              
82             # ensure these are always re-set at the beginning of an execution
83 47         128 $! = 0;
84 47         139 $? = 0;
85              
86 47         67 my $child_pid;
87 47         100 eval { $child_pid = IPC::Open3::open3( $stdin, $stdout, $stderr, @cmd ) };
  47         2488  
88 47 100       3427238 if ($@) {
89 11 100       391 if ( $@ =~ m{not enough arguments} ) {
    50          
90 2         6 $! = 22; # Invalid argument
91 2         6 $? = 65280; # system();print $?;
92             }
93             elsif ( $@ =~ m{open3: exec of .* failed at} ) {
94 9         101 $? = -1;
95             }
96              
97 11 100       106 if ( ref $arg_hr->{'open3_error'} eq 'SCALAR' ) {
98 4         23 ${ $arg_hr->{'open3_error'} } = $@;
  4         42  
99             }
100             else {
101 7         56 $arg_hr->{'open3_error'} = $@;
102             }
103              
104 11 50       51 if ( $arg_hr->{'carp_open3_errors'} ) {
105 0         0 require Carp;
106 0         0 Carp::carp($@);
107             }
108              
109 11         1320 return;
110             }
111              
112 36 100       787 $arg_hr->{'timeout'} = exists $arg_hr->{'timeout'} ? abs( $arg_hr->{'timeout'} ) : 0;
113              
114 36         160 my $alarm;
115             my $original_alarm;
116              
117             # small hack to avoid uninitialized value warnings w/ out lots of warning.pm voo-doo to
118             # keep the "no warnings 'uninitialized'" scoped to this assignment but the local() scoped from here on
119             # local $SIG{'ALRM'} = $SIG{'ALRM'};# this gives uninitialized value warnings
120 36         360 my $no_unit_warnings = $SIG{'ALRM'};
121 36   50     2440 local $SIG{'ALRM'} ||= '';
122 36 100       379 if ( $arg_hr->{'timeout'} ) {
123 12 100       67 if ( $arg_hr->{'timeout_is_microseconds'} ) {
124 4 50       41 if ( defined &Time::HiRes::ualarm ) {
125 0         0 $alarm = \&Time::HiRes::ualarm;
126             }
127             else {
128 4 50   8   122 $alarm = defined &Time::HiRes::alarm ? \&Time::HiRes::alarm : sub { alarm( $_[0] ) }; # \&CORE::alarm, \&CORE::GLOBAL::alarm, etc don't work ...
  8         73  
129 4         20 $arg_hr->{'timeout'} = $arg_hr->{'timeout'} / 1_000_000;
130 4 100       28 $arg_hr->{'timeout'} = 1 if $arg_hr->{'timeout'} < 1;
131 4         14 $arg_hr->{'timeout_is_microseconds'} = 0;
132             }
133             }
134             else {
135 8 50   16   370 $alarm = defined &Time::HiRes::alarm ? \&Time::HiRes::alarm : sub { alarm( $_[0] ) }; # \&CORE::alarm, \&CORE::GLOBAL::alarm, etc don't work ...
  16         182  
136             }
137              
138 12     7   185 $SIG{'ALRM'} = sub { die 'Alarm clock' };
  7         9516361  
139 12         60 $original_alarm = $alarm->( $arg_hr->{'timeout'} );
140              
141             # undocumented, for testing purposes only
142 12 50 33     241 if ( exists $arg_hr->{'_timeout_info'} && ref( $arg_hr->{'_timeout_info'} ) eq 'HASH' ) {
143 12 50       131 %{ $arg_hr->{'_timeout_info'} } = (
  12 50       552  
144             'timeout' => $arg_hr->{'timeout'},
145             'timeout_is_microseconds' => $arg_hr->{'timeout_is_microseconds'},
146             'Time::Hires' => $INC{'Time/HiRes.pm'},
147             'Time::Hires::ualarm' => defined &Time::HiRes::ualarm ? 1 : 0,
148             'Time::Hires::alarm' => defined &Time::HiRes::alarm ? 1 : 0,
149             'original_alarm' => $original_alarm,
150             );
151             }
152             }
153             else {
154 4     4   30 no warnings 'uninitialized'; # at least we can scopt it here ...
  4         12  
  4         6667  
155 24         363 $SIG{'ALRM'} = $no_unit_warnings;
156             }
157              
158 36         246 my $is_open3_err = 0;
159 36         76 my $open3_err_is_exec = 0;
160 36         66 my $return_bool = 1;
161 36         205 my $short_circuit_loop = 0;
162              
163 36         84 eval {
164              
165 36 100       226 if ( exists $arg_hr->{'_pre_run_sleep'} ) {
166 6 50       48 if ( my $sec = int( $arg_hr->{'_pre_run_sleep'} ) ) {
167 6         9001179 sleep $sec; # undocumented, only for testing
168             }
169             }
170              
171 36         983 $sel->add($stdout); # unless exists $arg_hr->{'ignore_handle'} && $arg_hr->{'ignore_handle'} eq 'stdout';
172 36         4041 $sel->add($stderr); # unless exists $arg_hr->{'ignore_handle'} && $arg_hr->{'ignore_handle'} eq 'stderr';
173              
174 36 100       1774 if ( exists $arg_hr->{'pre_read_print_to_stdin'} ) {
175 7 100       58 if ( my $type = ref( $arg_hr->{'pre_read_print_to_stdin'} ) ) {
176 2 100       37 if ( $type eq 'ARRAY' ) {
    50          
177 1         8 for my $line ( @{ $arg_hr->{'pre_read_print_to_stdin'} } ) {
  1         19  
178 2         200 $stdin->printflush($line);
179             }
180             }
181             elsif ( $type eq 'CODE' ) {
182 1         16 for my $line ( $arg_hr->{'pre_read_print_to_stdin'}->() ) {
183 2         129 $stdin->printflush($line);
184             }
185             }
186             }
187             else {
188 5         82 $stdin->printflush( $arg_hr->{'pre_read_print_to_stdin'} );
189             }
190             }
191              
192 36 100       824 if ( $arg_hr->{'close_stdin'} ) {
193 3         49 $stdin->close();
194 3         721 undef $stdin;
195             }
196              
197 36         249 local *_;
198              
199             # to avoid "Modification of readonly value attempted" errors with @_
200             # You ask, "Do you mean the _open3()'s or while()'s @_? " and the answer is: "exactly!" ;p
201              
202 36     79   644 my $get_next = sub { readline(shift) };
  79         11306  
203              
204 36 50 50     527 if ( my $byte_size = int( $arg_hr->{'read_length_bytes'} || 0 ) ) {
205 0         0 my $buffer;
206 0 0       0 $byte_size = 128 if $byte_size < 128;
207 0     0   0 $get_next = sub { shift->sysread( $buffer, $byte_size ); return $buffer; };
  0         0  
  0         0  
208             }
209              
210 36         505 my $odd_errno = int($!);
211              
212             READ_LOOP:
213 36         220 while ( my @ready = $sel->can_read ) {
214             HANDLE:
215 59         18135607 for my $fh (@ready) {
216 89 100       1132 if ( $fh->eof ) {
217 49         2010165 $sel->remove($fh);
218 49         3283 $fh->close;
219 49         1292 next HANDLE;
220             }
221              
222 39 100       2859 my $is_stderr = $fh eq $stderr ? 1 : 0;
223              
224             CMD_OUTPUT:
225 39         205 while ( my $cur_line = $get_next->($fh) ) {
226 43 100       305 next CMD_OUTPUT if $arg_hr->{'ignore_handle'} eq ( $is_stderr ? 'stderr' : 'stdout' );
    100          
227              
228 41 50 66     486 $is_open3_err = 1 if $is_stderr && $cur_line =~ m{^open3:};
229 41 50       220 if ($is_open3_err) {
230 0 0       0 if ( ref $arg_hr->{'open3_error'} eq 'SCALAR' ) {
231 0         0 ${ $arg_hr->{'open3_error'} } = $cur_line;
  0         0  
232             }
233             else {
234 0         0 $arg_hr->{'open3_error'} = $cur_line;
235             }
236              
237 0 0       0 if ( $arg_hr->{'carp_open3_errors'} ) {
238 0         0 require Carp;
239 0         0 Carp::carp($cur_line);
240             }
241              
242 0 0       0 if ( $cur_line =~ m{open3: exec of .* failed at} ) {
243 0         0 $open3_err_is_exec = 1;
244             }
245             }
246              
247 41         220 $return_bool = $arg_hr->{'handler'}->( $cur_line, $stdin, $is_stderr, $is_open3_err, \$short_circuit_loop );
248              
249 40 100       343 last READ_LOOP if !$return_bool;
250 39 50 33     141 last READ_LOOP if $is_open3_err && $arg_hr->{'stop_read_on_open3_err'}; # this is probably the last one anyway
251 39 100       245 last READ_LOOP if $short_circuit_loop;
252             }
253             }
254              
255 55         470 $odd_errno = int($!);
256             }
257              
258 28 50 66     808 $! = 0 if $odd_errno == 0 && $! == 9;
259              
260             # my $oserr = $!;
261             # my $cherr = $?;
262 28 100       140 $stdout->close if $stdout->opened;
263 28 100       429 $stderr->close if $stderr->opened;
264 28 100 66     761 $stdin->close if defined $stdin && ref $stdin eq 'IO::Handle' && $stdin->opened; # && !$arg_hr->{'close_stdin'};
      100        
265              
266             # $! = $oserr;
267             # $? = $cherr;
268              
269 28         2469 waitpid $child_pid, 0;
270              
271             };
272              
273 36 100 66     450 if ( $arg_hr->{'timeout'} && defined $original_alarm ) {
274 12         57 $alarm->($original_alarm);
275             }
276              
277 36 100       148 if ($@) {
278              
279             # if ($@ =~ m/^Alarm clock /) {
280             # $! = 60;
281             # # $? = ??;
282             # }
283 8         1804 return;
284             }
285              
286 28 0 33     153 if ( $is_open3_err && $open3_err_is_exec && $? != -1 ) {
      33        
287 0         0 $? = -1;
288             }
289              
290 28 100 66     532 return if $is_open3_err || !$return_bool || !child_error_ok($?);
      100        
291 24         2839 return 1;
292             }
293              
294             sub put_cmd_in {
295 18     18 1 32402 my (@cmd) = @_;
296              
297 18 100       167 my $arg_hr = ref $cmd[-1] eq 'HASH' ? pop(@cmd) : {};
298              
299             # not being this strict allows us to do "no" output ref quietness
300             # return if @cmd < 2;
301             # return if defined $cmd[-1] && !ref $cmd[-1];
302             # my $err = pop(@cmd);
303              
304 18 50 66     435 my $err = !defined $cmd[-1] || ref $cmd[-1] ? pop(@cmd) : undef;
305 18 100 100     266 my $out = !defined $cmd[-1] || ref $cmd[-1] ? pop(@cmd) : $err;
306              
307             $arg_hr->{'handler'} = sub {
308 29     29   101 my ( $cur_line, $stdin, $is_stderr, $is_open3_err, $short_circuit_loop_sr ) = @_;
309              
310 29 100       143 my $mod = $is_stderr ? $err : $out;
311 29 100       121 return 1 if !defined $mod;
312              
313 27 100       106 if ( ref $mod eq 'SCALAR' ) {
314 19         39 ${$mod} .= $cur_line;
  19         91  
315             }
316             else {
317 8         15 push @{$mod}, $cur_line;
  8         35  
318             }
319              
320 27         84 return 1;
321 18         147 };
322              
323 18         82 return run_cmd( @cmd, $arg_hr );
324             }
325              
326             #####################
327             #### child_error_* ##
328             #####################
329              
330             sub child_error_ok {
331 33 100   33 1 1992 my $sysrc = @_ ? shift() : $?;
332 33 100       284 return 1 if $sysrc == 0;
333 7         212 return;
334             }
335              
336             sub child_error_failed_to_execute {
337 24 100   24 1 82 my $sysrc = @_ ? shift() : $?;
338 24         91 return $sysrc == -1;
339             }
340              
341             sub child_error_seg_faulted {
342 4 100   4 1 17 my $sysrc = @_ ? shift() : $?;
343 4 50       13 return if child_error_failed_to_execute($sysrc);
344 4         13 return child_error_exit_signal($sysrc) == 11;
345             }
346              
347             sub child_error_core_dumped {
348 4 100   4 1 15 my $sysrc = @_ ? shift() : $?;
349 4 50       10 return if child_error_failed_to_execute($sysrc);
350 4         99 return $sysrc & 128;
351             }
352              
353             sub child_error_exit_signal {
354 8 100   8 1 22 my $sysrc = @_ ? shift() : $?;
355 8 50       20 return if child_error_failed_to_execute($sysrc);
356 8         253 return $sysrc & 127;
357             }
358              
359             sub child_error_exit_value {
360 4 100   4 1 14 my $sysrc = @_ ? shift() : $?;
361 4 50       8 return if child_error_failed_to_execute($sysrc);
362 4         17 return $sysrc >> 8;
363             }
364              
365             1;
366              
367             __END__