File Coverage

blib/lib/IPC/Run/Win32Helper.pm
Criterion Covered Total %
statement 39 172 22.6
branch 0 126 0.0
condition 0 69 0.0
subroutine 13 20 65.0
pod 3 3 100.0
total 55 390 14.1


line stmt bran cond sub pod time code
1             package IPC::Run::Win32Helper;
2              
3             =pod
4              
5             =head1 NAME
6              
7             IPC::Run::Win32Helper - helper routines for IPC::Run on Win32 platforms.
8              
9             =head1 SYNOPSIS
10              
11             use IPC::Run::Win32Helper; # Exports all by default
12              
13             =head1 DESCRIPTION
14              
15             IPC::Run needs to use sockets to redirect subprocess I/O so that the select() loop
16             will work on Win32. This seems to only work on WinNT and Win2K at this time, not
17             sure if it will ever work on Win95 or Win98. If you have experience in this area, please
18             contact me at barries@slaysys.com, thanks!.
19              
20             =cut
21              
22 1     1   60136 use strict;
  1         12  
  1         24  
23 1     1   4 use warnings;
  1         2  
  1         20  
24 1     1   4 use Carp;
  1         1  
  1         40  
25 1     1   403 use IO::Handle;
  1         4955  
  1         38  
26 1     1   6 use vars qw{ $VERSION @ISA @EXPORT };
  1         1  
  1         59  
27              
28             BEGIN {
29 1     1   3 $VERSION = '20220807.0';
30 1         13 @ISA = qw( Exporter );
31 1         25 @EXPORT = qw(
32             win32_spawn
33             win32_parse_cmd_line
34             _dont_inherit
35             _inherit
36             );
37             }
38              
39             require POSIX;
40              
41 1     1   5 use File::Spec ();
  1         2  
  1         12  
42 1     1   393 use Text::ParseWords;
  1         1126  
  1         65  
43 1     1   6 use Win32 ();
  1         2  
  1         14  
44 1     1   4 use Win32::Process;
  1         2  
  1         33  
45 1     1   4 use Win32::ShellQuote ();
  1         2  
  1         13  
46 1     1   357 use IPC::Run::Debug;
  1         11  
  1         69  
47 1         1723 use Win32API::File qw(
48             FdGetOsFHandle
49             SetHandleInformation
50             HANDLE_FLAG_INHERIT
51             INVALID_HANDLE_VALUE
52 1     1   5 );
  1         2  
53              
54             ## Takes an fd or a GLOB ref, never never never a Win32 handle.
55             sub _dont_inherit {
56 0     0     for (@_) {
57 0 0         next unless defined $_;
58 0           my $fd = $_;
59 0 0         $fd = fileno $fd if ref $fd;
60 0 0         _debug "disabling inheritance of ", $fd if _debugging_details;
61 0           my $osfh = FdGetOsFHandle $fd;
62 0 0 0       croak $^E if !defined $osfh || $osfh == INVALID_HANDLE_VALUE;
63              
64 0           SetHandleInformation( $osfh, HANDLE_FLAG_INHERIT, 0 );
65             }
66             }
67              
68             sub _inherit { #### REMOVE
69 0     0     for (@_) { #### REMOVE
70 0 0         next unless defined $_; #### REMOVE
71 0           my $fd = $_; #### REMOVE
72 0 0         $fd = fileno $fd if ref $fd; #### REMOVE
73 0 0         _debug "enabling inheritance of ", $fd if _debugging_details; #### REMOVE
74 0           my $osfh = FdGetOsFHandle $fd; #### REMOVE
75 0 0 0       croak $^E if !defined $osfh || $osfh == INVALID_HANDLE_VALUE; #### REMOVE
76             #### REMOVE
77 0           SetHandleInformation( $osfh, HANDLE_FLAG_INHERIT, 1 ); #### REMOVE
78             } #### REMOVE
79             } #### REMOVE
80             #### REMOVE
81             #sub _inherit {
82             # for ( @_ ) {
83             # next unless defined $_;
84             # my $osfh = GetOsFHandle $_;
85             # croak $^E if ! defined $osfh || $osfh == INVALID_HANDLE_VALUE;
86             # SetHandleInformation( $osfh, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT );
87             # }
88             #}
89              
90             =pod
91              
92             =head1 FUNCTIONS
93              
94             =over
95              
96             =item optimize()
97              
98             Most common incantations of C (I C, C,
99             or C) now use temporary files to redirect input and output
100             instead of pumper processes.
101              
102             Temporary files are used when sending to child processes if input is
103             taken from a scalar with no filter subroutines. This is the only time
104             we can assume that the parent is not interacting with the child's
105             redirected input as it runs.
106              
107             Temporary files are used when receiving from children when output is
108             to a scalar or subroutine with or without filters, but only if
109             the child in question closes its inputs or takes input from
110             unfiltered SCALARs or named files. Normally, a child inherits its STDIN
111             from its parent; to close it, use "0<&-" or the C<< noinherit => 1 >> option.
112             If data is sent to the child from CODE refs, filehandles or from
113             scalars through filters than the child's outputs will not be optimized
114             because C assumes the parent is interacting with the child.
115             It is ok if the output is filtered or handled by a subroutine, however.
116              
117             This assumes that all named files are real files (as opposed to named
118             pipes) and won't change; and that a process is not communicating with
119             the child indirectly (through means not visible to IPC::Run).
120             These can be an invalid assumptions, but are the 99% case.
121             Write me if you need an option to enable or disable optimizations; I
122             suspect it will work like the C modifier.
123              
124             To detect cases that you might want to optimize by closing inputs, try
125             setting the C environment variable to the special C
126             value:
127              
128             C:> set IPCRUNDEBUG=notopt
129             C:> my_app_that_uses_IPC_Run.pl
130              
131             =item optimizer() rationalizations
132              
133             Only for that limited case can we be sure that it's ok to batch all the
134             input in to a temporary file. If STDIN is from a SCALAR or from a named
135             file or filehandle (again, only in C), then outputs to CODE refs
136             are also assumed to be safe enough to batch through a temp file,
137             otherwise only outputs to SCALAR refs are batched. This can cause a bit
138             of grief if the parent process benefits from or relies on a bit of
139             "early returns" coming in before the child program exits. As long as
140             the output is redirected to a SCALAR ref, this will not be visible.
141             When output is redirected to a subroutine or (deprecated) filters, the
142             subroutine will not get any data until after the child process exits,
143             and it is likely to get bigger chunks of data at once.
144              
145             The reason for the optimization is that, without it, "pumper" processes
146             are used to overcome the inconsistencies of the Win32 API. We need to
147             use anonymous pipes to connect to the child processes' stdin, stdout,
148             and stderr, yet select() does not work on these. select() only works on
149             sockets on Win32. So for each redirected child handle, there is
150             normally a "pumper" process that connects to the parent using a
151             socket--so the parent can select() on that fd--and to the child on an
152             anonymous pipe--so the child can read/write a pipe.
153              
154             Using a socket to connect directly to the child (as at least one MSDN
155             article suggests) seems to cause the trailing output from most children
156             to be lost. I think this is because child processes rarely close their
157             stdout and stderr explicitly, and the winsock dll does not seem to flush
158             output when a process that uses it exits without explicitly closing
159             them.
160              
161             Because of these pumpers and the inherent slowness of Win32
162             CreateProcess(), child processes with redirects are quite slow to
163             launch; so this routine looks for the very common case of
164             reading/writing to/from scalar references in a run() routine and
165             converts such reads and writes in to temporary file reads and writes.
166              
167             Such files are marked as FILE_ATTRIBUTE_TEMPORARY to increase speed and
168             as FILE_FLAG_DELETE_ON_CLOSE so it will be cleaned up when the child
169             process exits (for input files). The user's default permissions are
170             used for both the temporary files and the directory that contains them,
171             hope your Win32 permissions are secure enough for you. Files are
172             created with the Win32API::File defaults of
173             FILE_SHARE_READ|FILE_SHARE_WRITE.
174              
175             Setting the debug level to "details" or "gory" will give detailed
176             information about the optimization process; setting it to "basic" or
177             higher will tell whether or not a given call is optimized. Setting
178             it to "notopt" will highlight those calls that aren't optimized.
179              
180             =cut
181              
182             sub optimize {
183 0     0 1   my ($h) = @_;
184              
185 0           my @kids = @{ $h->{KIDS} };
  0            
186              
187 0           my $saw_pipe;
188              
189 0           my ( $ok_to_optimize_outputs, $veto_output_optimization );
190              
191 0           for my $kid (@kids) {
192 0 0         ( $ok_to_optimize_outputs, $veto_output_optimization ) = ()
193             unless $saw_pipe;
194              
195 0 0 0       _debug "Win32 optimizer: (kid $kid->{NUM}) STDIN piped, carrying over ok of non-SCALAR output optimization"
196             if _debugging_details && $ok_to_optimize_outputs;
197 0 0 0       _debug "Win32 optimizer: (kid $kid->{NUM}) STDIN piped, carrying over veto of non-SCALAR output optimization"
198             if _debugging_details && $veto_output_optimization;
199              
200 0 0 0       if ( $h->{noinherit} && !$ok_to_optimize_outputs ) {
201 0 0 0       _debug "Win32 optimizer: (kid $kid->{NUM}) STDIN not inherited from parent oking non-SCALAR output optimization"
202             if _debugging_details && $ok_to_optimize_outputs;
203 0           $ok_to_optimize_outputs = 1;
204             }
205              
206 0           for ( @{ $kid->{OPS} } ) {
  0            
207 0 0 0       if ( substr( $_->{TYPE}, 0, 1 ) eq "<" ) {
    0 0        
    0          
    0          
208 0 0         if ( $_->{TYPE} eq "<" ) {
209 0 0 0       if ( @{ $_->{FILTERS} } > 1 ) {
  0 0 0        
    0 0        
210             ## Can't assume that the filters are idempotent.
211             }
212             elsif (ref $_->{SOURCE} eq "SCALAR"
213             || ref $_->{SOURCE} eq "GLOB"
214             || UNIVERSAL::isa( $_, "IO::Handle" ) ) {
215 0 0         if ( $_->{KFD} == 0 ) {
216             _debug
217             "Win32 optimizer: (kid $kid->{NUM}) 0$_->{TYPE}",
218             ref $_->{SOURCE},
219 0 0         ", ok to optimize outputs"
220             if _debugging_details;
221 0           $ok_to_optimize_outputs = 1;
222             }
223 0           $_->{SEND_THROUGH_TEMP_FILE} = 1;
224 0           next;
225             }
226             elsif ( !ref $_->{SOURCE} && defined $_->{SOURCE} ) {
227 0 0         if ( $_->{KFD} == 0 ) {
228 0 0         _debug
229             "Win32 optimizer: (kid $kid->{NUM}) 0<$_->{SOURCE}, ok to optimize outputs",
230             if _debugging_details;
231 0           $ok_to_optimize_outputs = 1;
232             }
233 0           next;
234             }
235             }
236             _debug
237             "Win32 optimizer: (kid $kid->{NUM}) ",
238             $_->{KFD},
239             $_->{TYPE},
240             defined $_->{SOURCE}
241             ? ref $_->{SOURCE}
242             ? ref $_->{SOURCE}
243             : $_->{SOURCE}
244             : defined $_->{FILENAME} ? $_->{FILENAME}
245             : "",
246 0 0 0       @{ $_->{FILTERS} } > 1 ? " with filters" : (),
  0 0          
    0          
    0          
    0          
247             ", VETOING output opt."
248             if _debugging_details || _debugging_not_optimized;
249 0           $veto_output_optimization = 1;
250             }
251             elsif ( $_->{TYPE} eq "close" && $_->{KFD} == 0 ) {
252 0           $ok_to_optimize_outputs = 1;
253 0 0         _debug "Win32 optimizer: (kid $kid->{NUM}) saw 0<&-, ok to optimize outputs"
254             if _debugging_details;
255             }
256             elsif ( $_->{TYPE} eq "dup" && $_->{KFD2} == 0 ) {
257 0           $veto_output_optimization = 1;
258 0 0 0       _debug "Win32 optimizer: (kid $kid->{NUM}) saw 0<&$_->{KFD2}, VETOING output opt."
259             if _debugging_details || _debugging_not_optimized;
260             }
261             elsif ( $_->{TYPE} eq "|" ) {
262 0           $saw_pipe = 1;
263             }
264             }
265              
266 0 0 0       if ( !$ok_to_optimize_outputs && !$veto_output_optimization ) {
267 0 0 0       _debug "Win32 optimizer: (kid $kid->{NUM}) child STDIN not redirected, VETOING non-SCALAR output opt."
268             if _debugging_details || _debugging_not_optimized;
269 0           $veto_output_optimization = 1;
270             }
271              
272 0 0 0       if ( $ok_to_optimize_outputs && $veto_output_optimization ) {
273 0           $ok_to_optimize_outputs = 0;
274 0 0 0       _debug "Win32 optimizer: (kid $kid->{NUM}) non-SCALAR output optimizations VETOed"
275             if _debugging_details || _debugging_not_optimized;
276             }
277              
278             ## SOURCE/DEST ARRAY means it's a filter.
279             ## TODO: think about checking to see if the final input/output of
280             ## a filter chain (an ARRAY SOURCE or DEST) is a scalar...but
281             ## we may be deprecating filters.
282              
283 0           for ( @{ $kid->{OPS} } ) {
  0            
284 0 0         if ( $_->{TYPE} eq ">" ) {
285 0 0 0       if (
      0        
      0        
      0        
286             ref $_->{DEST} eq "SCALAR"
287             || (
288             (
289             @{ $_->{FILTERS} } > 1
290             || ref $_->{DEST} eq "CODE"
291             || ref $_->{DEST} eq "ARRAY" ## Filters?
292             )
293             && ( $ok_to_optimize_outputs && !$veto_output_optimization )
294             )
295             ) {
296 0           $_->{RECV_THROUGH_TEMP_FILE} = 1;
297 0           next;
298             }
299             _debug
300             "Win32 optimizer: NOT optimizing (kid $kid->{NUM}) ",
301             $_->{KFD},
302             $_->{TYPE},
303             defined $_->{DEST}
304             ? ref $_->{DEST}
305             ? ref $_->{DEST}
306             : $_->{SOURCE}
307             : defined $_->{FILENAME} ? $_->{FILENAME}
308             : "",
309 0 0         @{ $_->{FILTERS} } ? " with filters" : (),
  0 0          
    0          
    0          
    0          
310             if _debugging_details;
311             }
312             }
313             }
314              
315             }
316              
317             =pod
318              
319             =item win32_parse_cmd_line
320              
321             @words = win32_parse_cmd_line( q{foo bar 'baz baz' "bat bat"} );
322              
323             returns 4 words. This parses like the bourne shell (see
324             the bit about shellwords() in L), assuming we're
325             trying to be a little cross-platform here. The only difference is
326             that "\" is *not* treated as an escape except when it precedes
327             punctuation, since it's used all over the place in DOS path specs.
328              
329             TODO: strip caret escapes?
330              
331             TODO: use
332             https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#parsing-c-command-line-arguments
333              
334             TODO: globbing? probably not (it's unDOSish).
335              
336             TODO: shebang emulation? Probably, but perhaps that should be part
337             of Run.pm so all spawned processes get the benefit.
338              
339             LIMITATIONS: shellwords dies silently on malformed input like
340              
341             a\"
342              
343             =cut
344              
345             sub win32_parse_cmd_line {
346 0     0 1   my $line = shift;
347 0           $line =~ s{(\\[\w\s])}{\\$1}g;
348 0           return shellwords $line;
349             }
350              
351             =pod
352              
353             =item win32_spawn
354              
355             Spawns a child process, possibly with STDIN, STDOUT, and STDERR (file descriptors 0, 1, and 2, respectively) redirected.
356              
357             B.
358              
359             Cannot redirect higher file descriptors due to lack of support for this in the
360             Win32 environment.
361              
362             This can be worked around by marking a handle as inheritable in the
363             parent (or leaving it marked; this is the default in perl), obtaining it's
364             Win32 handle with C or
365             C and passing it to the child using the command
366             line, the environment, or any other IPC mechanism (it's a plain old integer).
367             The child can then use C or C and possibly
368             C<&BAR">> or C<&$fd>> as need be. Ach, the pain!
369              
370             Remember to check the Win32 handle against INVALID_HANDLE_VALUE.
371              
372             =cut
373              
374             sub _save {
375 0     0     my ( $saved, $saved_as, $fd ) = @_;
376              
377             ## We can only save aside the original fds once.
378 0 0         return if exists $saved->{$fd};
379              
380 0           my $saved_fd = IPC::Run::_dup($fd);
381 0           _dont_inherit $saved_fd;
382              
383 0           $saved->{$fd} = $saved_fd;
384 0           $saved_as->{$saved_fd} = $fd;
385              
386 0           _dont_inherit $saved->{$fd};
387             }
388              
389             sub _dup2_gently {
390 0     0     my ( $saved, $saved_as, $fd1, $fd2 ) = @_;
391 0           _save $saved, $saved_as, $fd2;
392              
393 0 0         if ( exists $saved_as->{$fd2} ) {
394             ## The target fd is colliding with a saved-as fd, gotta bump
395             ## the saved-as fd to another fd.
396 0           my $orig_fd = delete $saved_as->{$fd2};
397 0           my $saved_fd = IPC::Run::_dup($fd2);
398 0           _dont_inherit $saved_fd;
399              
400 0           $saved->{$orig_fd} = $saved_fd;
401 0           $saved_as->{$saved_fd} = $orig_fd;
402             }
403 0 0         _debug "moving $fd1 to kid's $fd2" if _debugging_details;
404 0           IPC::Run::_dup2_rudely( $fd1, $fd2 );
405             }
406              
407             sub win32_spawn {
408 0     0 1   my ( $cmd, $ops ) = @_;
409              
410 0           my ( $app, $cmd_line );
411 0           my $need_pct = 0;
412 0 0         if ( UNIVERSAL::isa( $cmd, 'IPC::Run::Win32Process' ) ) {
    0          
413 0           $app = $cmd->{lpApplicationName};
414 0           $cmd_line = $cmd->{lpCommandLine};
415             }
416             elsif ( $cmd->[0] !~ /\.(bat|cmd) *$/i ) {
417 0           $app = $cmd->[0];
418 0           $cmd_line = Win32::ShellQuote::quote_native(@$cmd);
419             }
420             else {
421             # Batch file, so follow the batch-specific guidance of
422             # https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa
423             # There's no one true way to locate cmd.exe. In the unlikely event that
424             # %COMSPEC% is missing, fall back on a Windows API. We could search
425             # %PATH% like _wsystem() does. That would be prone to security bugs,
426             # and one fallback is enough.
427             $app = (
428             $ENV{COMSPEC}
429 0   0       || File::Spec->catfile(
430             Win32::GetFolderPath(Win32::CSIDL_SYSTEM),
431             'cmd.exe'
432             )
433             );
434              
435             # Win32 rejects attempts to create files with names containing certain
436             # characters. Ignore most, but reject the subset that might otherwise
437             # cause us to execute the wrong file instead of failing cleanly.
438 0 0         if ( $cmd->[0] =~ /["\r\n\0]/ ) {
439 0           croak "invalid batch file name";
440             }
441              
442             # Make cmd.exe see the batch file name as quoted. Suppose we instead
443             # used caret escapes, as we do for arguments. cmd.exe could then "break
444             # the command token at the first occurrence of , ; or ="
445             # (https://stackoverflow.com/a/4095133).
446 0           my @parts = qq{"$cmd->[0]"};
447              
448             # cmd.exe will strip escapes once when parsing our $cmd_line and again
449             # where the batch file injects the argument via %*, %1, etc. Compensate
450             # by adding one extra cmd_escape layer.
451 0 0         if ( @$cmd > 1 ) {
452 0           my @q = Win32::ShellQuote::quote_cmd( @{$cmd}[ 1 .. $#{$cmd} ] );
  0            
  0            
453 0           push @parts, map { Win32::ShellQuote::cmd_escape($_) } @q;
  0            
454             }
455              
456             # One can't stop cmd.exe from expanding %var%, so inject each literal %
457             # via an environment variable. Delete that variable before the real
458             # child can see it. See
459             # https://www.dostips.com/forum/viewtopic.php?f=3&t=10131 for more on
460             # this technique and the limitations of alternatives.
461 0           $cmd_line = join ' ', @parts;
462 0 0         if ( $cmd_line =~ s/%/%ipcrunpct%/g ) {
463 0           $cmd_line = qq{/c "set "ipcrunpct=" & $cmd_line"};
464 0           $need_pct = 1;
465             }
466             else {
467 0           $cmd_line = qq{/c "$cmd_line"};
468             }
469             }
470 0 0         _debug "app: ", $app
471             if _debugging;
472 0 0         _debug "cmd line: ", $cmd_line
473             if _debugging;
474              
475             ## NOTE: The debug pipe write handle is passed to pump processes as STDOUT.
476             ## and is not to the "real" child process, since they would not know
477             ## what to do with it...unlike Unix, we have no code executing in the
478             ## child before the "real" child is exec()ed.
479              
480 0           my %saved; ## Map of parent's orig fd -> saved fd
481             my %saved_as; ## Map of parent's saved fd -> orig fd, used to
482             ## detect collisions between a KFD and the fd a
483             ## parent's fd happened to be saved to.
484              
485 0           for my $op (@$ops) {
486 0 0         _dont_inherit $op->{FD} if defined $op->{FD};
487              
488 0 0 0       if ( defined $op->{KFD} && $op->{KFD} > 2 ) {
489             ## TODO: Detect this in harness()
490             ## TODO: enable temporary redirections if ever necessary, not
491             ## sure why they would be...
492             ## 4>&1 1>/dev/null 1>&4 4>&-
493 0           croak "Can't redirect fd #", $op->{KFD}, " on Win32";
494             }
495              
496             ## This is very similar logic to IPC::Run::_do_kid_and_exit().
497 0 0         if ( defined $op->{TFD} ) {
    0          
    0          
    0          
498 0 0         unless ( $op->{TFD} == $op->{KFD} ) {
499 0           _dup2_gently \%saved, \%saved_as, $op->{TFD}, $op->{KFD};
500 0           _dont_inherit $op->{TFD};
501             }
502             }
503             elsif ( $op->{TYPE} eq "dup" ) {
504             _dup2_gently \%saved, \%saved_as, $op->{KFD1}, $op->{KFD2}
505 0 0         unless $op->{KFD1} == $op->{KFD2};
506             }
507             elsif ( $op->{TYPE} eq "close" ) {
508 0           _save \%saved, \%saved_as, $op->{KFD};
509 0           IPC::Run::_close( $op->{KFD} );
510             }
511             elsif ( $op->{TYPE} eq "init" ) {
512             ## TODO: detect this in harness()
513 0           croak "init subs not allowed on Win32";
514             }
515             }
516              
517 0 0         local $ENV{ipcrunpct} = '%' if $need_pct;
518 0           my $process;
519             Win32::Process::Create(
520             $process,
521             $app,
522             $cmd_line,
523             1, ## Inherit handles
524             0, ## Inherit parent priortiy class. Was NORMAL_PRIORITY_CLASS
525             ".",
526             )
527 0 0         or do {
528 0           my $err = Win32::FormatMessage( Win32::GetLastError() );
529 0           $err =~ s/\r?\n$//s;
530 0           croak "$err: Win32::Process::Create()";
531             };
532              
533 0           for my $orig_fd ( keys %saved ) {
534 0           IPC::Run::_dup2_rudely( $saved{$orig_fd}, $orig_fd );
535 0           IPC::Run::_close( $saved{$orig_fd} );
536             }
537              
538 0           return ( $process->GetProcessID(), $process );
539             }
540              
541             1;
542              
543             =pod
544              
545             =back
546              
547             =head1 AUTHOR
548              
549             Barries Slaymaker . Funded by Perforce Software, Inc.
550              
551             =head1 COPYRIGHT
552              
553             Copyright 2001, Barrie Slaymaker, All Rights Reserved.
554              
555             You may use this under the terms of either the GPL 2.0 or the Artistic License.
556              
557             =cut