Skip to content

Commit 8491dfe

Browse files
committed
Add IPC::Run::Win32Process, for delivering nonstandard command lines.
This unblocks general use of programs like cmd.exe and cscript.exe as stages in IPC::Run pipelines.
1 parent c299a86 commit 8491dfe

File tree

6 files changed

+173
-42
lines changed

6 files changed

+173
-42
lines changed

MANIFEST

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lib/IPC/Run/IO.pm
1616
lib/IPC/Run/Timer.pm
1717
lib/IPC/Run/Win32Helper.pm
1818
lib/IPC/Run/Win32IO.pm
19+
lib/IPC/Run/Win32Process.pm
1920
lib/IPC/Run/Win32Pump.pm
2021
LICENSE
2122
Makefile.PL

lib/IPC/Run.pm

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,8 @@ to the systems' shell:
413413
414414
or a list of commands, io operations, and/or timers/timeouts to execute.
415415
Consecutive commands must be separated by a pipe operator '|' or an '&'.
416-
External commands are passed in as array references, and, on systems
417-
supporting fork(), Perl code may be passed in as subs:
416+
External commands are passed in as array references or L<IPC::Run::Win32Process>
417+
objects. On systems supporting fork(), Perl code may be passed in as subs:
418418
419419
run \@cmd;
420420
run \@cmd1, '|', \@cmd2;
@@ -1240,6 +1240,33 @@ sub _search_path {
12401240
croak "Command '$cmd_name' not found in " . join( ", ", @searched_in );
12411241
}
12421242

1243+
# Translate a command or CODE reference (a $kid->{VAL}) to a list of strings
1244+
# suitable for passing to _debug().
1245+
sub _debugstrings {
1246+
my $operand = shift;
1247+
if ( !defined $operand ) {
1248+
return '<undef>';
1249+
}
1250+
1251+
my $ref = ref $operand;
1252+
if ( !$ref ) {
1253+
return length $operand < 50
1254+
? "'$operand'"
1255+
: join( '', "'", substr( $operand, 0, 10 ), "...'" );
1256+
}
1257+
elsif ( $ref eq 'ARRAY' ) {
1258+
return (
1259+
'[ ',
1260+
join( " ", map /[^\w.-]/ ? "'$_'" : $_, @$operand ),
1261+
' ]'
1262+
);
1263+
}
1264+
elsif ( UNIVERSAL::isa( $operand, 'IPC::Run::Win32Process' ) ) {
1265+
return "$operand";
1266+
}
1267+
return $ref;
1268+
}
1269+
12431270
sub _empty($) { !( defined $_[0] && length $_[0] ) }
12441271

12451272
## 'safe' versions of otherwise fun things to do. See also IPC::Run::Win32Helper.
@@ -1375,6 +1402,9 @@ sub _spawn {
13751402
my IPC::Run $self = shift;
13761403
my ($kid) = @_;
13771404

1405+
croak "Can't spawn IPC::Run::Win32Process except on Win32"
1406+
if UNIVERSAL::isa( $kid->{VAL}, 'IPC::Run::Win32Process' );
1407+
13781408
_debug "opening sync pipe ", $kid->{PID} if _debugging_details;
13791409
my $sync_reader_fd;
13801410
( $sync_reader_fd, $self->{SYNC_WRITER_FD} ) = _pipe;
@@ -1730,24 +1760,12 @@ sub harness {
17301760
for ( shift @args ) {
17311761
eval {
17321762
$first_parse = 1;
1733-
_debug(
1734-
"parsing ",
1735-
defined $_
1736-
? ref $_ eq 'ARRAY'
1737-
? ( '[ ', join( ', ', map "'$_'", @$_ ), ' ]' )
1738-
: (
1739-
ref $_
1740-
|| (
1741-
length $_ < 50
1742-
? "'$_'"
1743-
: join( '', "'", substr( $_, 0, 10 ), "...'" )
1744-
)
1745-
)
1746-
: '<undef>'
1747-
) if _debugging;
1763+
_debug( "parsing ", _debugstrings($_) ) if _debugging;
17481764

17491765
REPARSE:
1750-
if ( ref eq 'ARRAY' || ( !$cur_kid && ref eq 'CODE' ) ) {
1766+
if ( ref eq 'ARRAY'
1767+
|| UNIVERSAL::isa( $_, 'IPC::Run::Win32Process' )
1768+
|| ( !$cur_kid && ref eq 'CODE' ) ) {
17511769
croak "Process control symbol ('|', '&') missing" if $cur_kid;
17521770
croak "Can't spawn a subroutine on Win32"
17531771
if Win32_MODE && ref eq "CODE";
@@ -2077,7 +2095,7 @@ sub _open_pipes {
20772095
## Loop through the kids and their OPS, interpreting any that require
20782096
## parent-side actions.
20792097
for my $kid ( @{ $self->{KIDS} } ) {
2080-
unless ( ref $kid->{VAL} eq 'CODE' ) {
2098+
if ( ref $kid->{VAL} eq 'ARRAY' ) {
20812099
$kid->{PATH} = _search_path $kid->{VAL}->[0];
20822100
}
20832101
if ( defined $pipe_read_fd ) {
@@ -2789,14 +2807,8 @@ sub start {
27892807
{ my $ofh = select STDERR; my $of = $|; $| = 1; $| = $of; select $ofh; }
27902808
for my $kid ( @{ $self->{KIDS} } ) {
27912809
$kid->{RESULT} = undef;
2792-
_debug "child: ",
2793-
ref( $kid->{VAL} ) eq "CODE"
2794-
? "CODE ref"
2795-
: (
2796-
"`",
2797-
join( " ", map /[^\w.-]/ ? "'$_'" : $_, @{ $kid->{VAL} } ),
2798-
"`"
2799-
) if _debugging_details;
2810+
_debug "child: ", _debugstrings( $kid->{VAL} )
2811+
if _debugging_details;
28002812
eval {
28012813
croak "simulated failure of fork"
28022814
if $self->{_simulate_fork_failure};
@@ -2807,17 +2819,20 @@ sub start {
28072819
## TODO: Test and debug spawning code. Someday.
28082820
_debug(
28092821
'spawning ',
2810-
join(
2811-
' ',
2812-
map( "'$_'",
2813-
( $kid->{PATH}, @{ $kid->{VAL} }[ 1 .. $#{ $kid->{VAL} } ] ) )
2822+
_debugstrings(
2823+
[
2824+
$kid->{PATH},
2825+
@{ $kid->{VAL} }[ 1 .. $#{ $kid->{VAL} } ]
2826+
]
28142827
)
2815-
) if _debugging;
2828+
) if $kid->{PATH} && _debugging;
28162829
## The external kid wouldn't know what to do with it anyway.
28172830
## This is only used by the "helper" pump processes on Win32.
28182831
_dont_inherit( $self->{DEBUG_FD} );
28192832
( $kid->{PID}, $kid->{PROCESS} ) = IPC::Run::Win32Helper::win32_spawn(
2820-
[ $kid->{PATH}, @{ $kid->{VAL} }[ 1 .. $#{ $kid->{VAL} } ] ],
2833+
ref( $kid->{VAL} ) eq "ARRAY"
2834+
? [ $kid->{PATH}, @{ $kid->{VAL} }[ 1 .. $#{ $kid->{VAL} } ] ]
2835+
: $kid->{VAL},
28212836
$kid->{OPS},
28222837
);
28232838
_debug "spawn() = ", $kid->{PID} if _debugging;
@@ -4170,8 +4185,8 @@ rules|https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#p
41704185
will see an C<argv> that matches the array reference specifying the command.
41714186
Some programs use different rules to parse their command line. Notable examples
41724187
include F<cmd.exe>, F<cscript.exe>, and Cygwin programs called from non-Cygwin
4173-
programs. Use L<Win32::Process>, not IPC::Run, to call these and other
4174-
nonstandard programs.
4188+
programs. Use L<IPC::Run::Win32Process> to call these and other nonstandard
4189+
programs.
41754190
41764191
=item batch files
41774192

lib/IPC/Run/Win32Helper.pm

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,11 @@ sub win32_spawn {
409409

410410
my ( $app, $cmd_line );
411411
my $need_pct = 0;
412-
if ( $cmd->[0] !~ /\.(bat|cmd) *$/i ) {
412+
if ( UNIVERSAL::isa( $cmd, 'IPC::Run::Win32Process' ) ) {
413+
$app = $cmd->{lpApplicationName};
414+
$cmd_line = $cmd->{lpCommandLine};
415+
}
416+
elsif ( $cmd->[0] !~ /\.(bat|cmd) *$/i ) {
413417
$app = $cmd->[0];
414418
$cmd_line = Win32::ShellQuote::quote_native(@$cmd);
415419
}

lib/IPC/Run/Win32Process.pm

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package IPC::Run::Win32Process;
2+
3+
=pod
4+
5+
=head1 NAME
6+
7+
IPC::Run::Win32Process -- deliver nonstandard command lines via IPC::Run.
8+
9+
=head1 SYNOPSIS
10+
11+
use File::Spec ();
12+
use IPC::Run qw(run);
13+
use IPC::Run::Win32Process ();
14+
use Win32 ();
15+
16+
$find_exe = File::Spec->catfile(Win32::GetFolderPath(Win32::CSIDL_SYSTEM),
17+
'find.exe');
18+
run(IPC::Run::Win32Process->new($ENV{COMSPEC}, q{cmd.exe /c echo ""}),
19+
'|', IPC::Run::Win32Process->new($find_exe, q{find_exe """"""}),
20+
'>', \$out);
21+
22+
=head1 DESCRIPTION
23+
24+
This class facilitates executing Windows programs that don't use L<standard
25+
command line parsing
26+
rules|https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#parsing-c-command-line-arguments>.
27+
Notable programs having nonstandard rules include F<cmd.exe>, F<cscript.exe>,
28+
and Cygwin programs called from non-Cygwin programs. IPC::Run will use the two
29+
strings, verbatim, as the lpApplicationName and lpCommandLine arguments of
30+
CreateProcessA(). This furnishes unfiltered control over the child process
31+
command line.
32+
33+
=head1 FUNCTIONS & METHODS
34+
35+
=over
36+
37+
=cut
38+
39+
use strict;
40+
use warnings;
41+
use Carp;
42+
43+
use overload '""' => sub {
44+
my ($self) = @_;
45+
return join(
46+
'',
47+
'IPC::Run::Win32Process(',
48+
$self->{lpApplicationName},
49+
', ',
50+
$self->{lpCommandLine},
51+
')'
52+
);
53+
};
54+
55+
=item new
56+
57+
IPC::Run::Win32Process->new( $lpApplicationName, $lpCommandLine );
58+
IPC::Run::Win32Process->new( $ENV{COMSPEC}, q{cmd.exe /c echo ""} );
59+
60+
Constructor.
61+
62+
=back
63+
64+
=cut
65+
66+
sub new {
67+
my ( $class, $lpApplicationName, $lpCommandLine ) = @_;
68+
$class = ref $class || $class;
69+
70+
croak "missing lpApplicationName" if !defined $lpApplicationName;
71+
croak "missing lpCommandLine" if !defined $lpCommandLine;
72+
73+
my IPC::Run::Win32Process $self = bless {}, $class;
74+
$self->{lpApplicationName} = $lpApplicationName;
75+
$self->{lpCommandLine} = $lpCommandLine;
76+
77+
return $self;
78+
}
79+
80+
1;

t/run.t

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ sub get_warnings {
3838
select STDERR;
3939
select STDOUT;
4040

41-
use Test::More tests => 286;
41+
use Test::More tests => 288;
4242
use IPC::Run::Debug qw( _map_fds );
4343
use IPC::Run qw( :filters :filter_imp start );
4444

@@ -344,6 +344,36 @@ SKIP: {
344344
chdir $initial_cwd;
345345
}
346346

347+
##
348+
## IPC::Run::Win32Process
349+
##
350+
SKIP: {
351+
if ( !IPC::Run::Win32_MODE() ) {
352+
skip( "cmd.exe is specific to Win32", 2 );
353+
}
354+
355+
use File::Spec ();
356+
require Win32;
357+
require IPC::Run::Win32Process;
358+
359+
run(
360+
IPC::Run::Win32Process->new( $ENV{COMSPEC}, q{cmd.exe /c echo ""} ),
361+
'>', \$out
362+
);
363+
eok( $out, qq{""\n} );
364+
365+
my $find_exe = File::Spec->catfile(
366+
Win32::GetFolderPath( Win32::CSIDL_SYSTEM() ),
367+
'find.exe'
368+
);
369+
run(
370+
IPC::Run::Win32Process->new( $ENV{COMSPEC}, q{cmd.exe /c echo ""} ),
371+
'|', IPC::Run::Win32Process->new( $find_exe, q{find_exe """"""} ),
372+
'>', \$out
373+
);
374+
eok( $out, qq{""\n} );
375+
}
376+
347377
##
348378
## A function
349379
##

xt/98_pod_coverage.t

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,16 @@ foreach my $MODULE (@MODULES) {
2525
: plan( skip_all => "$MODULE not available for testing" );
2626
}
2727
}
28-
plan tests => 7;
28+
plan tests => 8;
2929

3030
#my $private_subs = { private => [qr/foo_fizz/]};
3131
#pod_coverage_ok('IPC::Run', $private_subs, "Test IPC::Run that all modules are documented.");
3232

33-
pod_coverage_ok( 'IPC::Run', "Test IPC::Run that all modules are documented." );
34-
pod_coverage_ok( 'IPC::Run::Debug', "Test IPC::Run::Debug that all modules are documented." );
35-
pod_coverage_ok( 'IPC::Run::IO', "Test IPC::Run::IO that all modules are documented." );
36-
pod_coverage_ok( 'IPC::Run::Timer', "Test IPC::Run::Timer that all modules are documented." );
33+
pod_coverage_ok( 'IPC::Run', "Test IPC::Run that all modules are documented." );
34+
pod_coverage_ok( 'IPC::Run::Debug', "Test IPC::Run::Debug that all modules are documented." );
35+
pod_coverage_ok( 'IPC::Run::IO', "Test IPC::Run::IO that all modules are documented." );
36+
pod_coverage_ok( 'IPC::Run::Timer', "Test IPC::Run::Timer that all modules are documented." );
37+
pod_coverage_ok( 'IPC::Run::Win32Process', "Test IPC::Run::Win32Process that all modules are documented." );
3738
TODO: {
3839
local $TODO = "These modules are not fully documented yet.";
3940
pod_coverage_ok( 'IPC::Run::Win32Helper', "Test IPC::Run::Win32Helper that all modules are documented." );

0 commit comments

Comments
 (0)