File Coverage

lib/IPC/OpenAny.pm
Criterion Covered Total %
statement 65 71 91.5
branch 21 36 58.3
condition n/a
subroutine 12 13 92.3
pod 2 2 100.0
total 100 122 81.9


line stmt bran cond sub pod time code
1 5     5   337234 use strict;
  5         13  
  5         144  
2 5     5   25 use warnings;
  5         127  
  5         258  
3             package IPC::OpenAny;
4             {
5             $IPC::OpenAny::VERSION = '0.005';
6             }
7              
8             # ABSTRACT: Run a process with control over any FDs it may use.
9              
10 5     5   4205 use English qw(-no_match_vars);
  5         7711  
  5         32  
11 5     5   1977 use Carp;
  5         8  
  5         411  
12 5     5   22 use autodie;
  5         7  
  5         43  
13 5     5   24459 use Data::Dumper;
  5         13  
  5         305  
14 5     5   4689 use Params::Util qw(_STRING _ARRAYLIKE _CODELIKE);
  5         15764  
  5         469  
15              
16 5     5   35 use parent 'Exporter';
  5         10  
  5         48  
17             our @EXPORT_OK = qw(openany);
18              
19             our $DEBUG = 0;
20              
21             # TODO: Figure out how to send IO to/from scalar vals.
22             # It should probably be done in another module wrapping this one.
23             # TODO: validate all fds to make sure they are either a filehandle or undef.
24             # TODO: validate cmd spec as well... etc, etc.
25              
26             sub run {
27 7     7 1 93694 my (undef, %opt) = @_;
28 7 50       51 my $cmd_spec = delete $opt{cmd_spec} or die "cmd_spec parameter is required!\n";
29 7         21 my $fds = delete $opt{fds};
30 7         19 my $env = delete $opt{env};
31 7         17 my $pwd = delete $opt{pwd};
32 7         25 my $pid = __fork_cmd($cmd_spec, $fds, $env, $pwd);
33 4 50       1175681 waitpid $pid, 0 if $opt{wait};
34 4         210 return $pid;
35             }
36              
37 0     0 1 0 sub openany { __PACKAGE__->run(@_) }
38              
39             # fork a child process in which to run the command/sub
40             sub __fork_cmd {
41 7     7   17 my ($cmd_spec, $fds, $env, $pwd) = @_;
42              
43 7         35 my $pid = fork();
44 7 100       14486 return $pid if $pid;
45              
46             ### now in child process...
47              
48             # set up working directory
49 3 50       243 chdir $pwd if $pwd;
50              
51             # set up environment
52 3         229 $ENV{$_} = $env->{$_} for keys %$env;
53              
54             # go!
55 3         242 __setup_child_fds($fds);
56 3         21 __exec_cmd($cmd_spec);
57              
58 0         0 die "pid $PID should never have gotten here.";
59             }
60              
61             # do all the file-descriptor magic that the user asked for...
62             sub __setup_child_fds {
63 3     3   70 my ($fds) = @_;
64              
65             # close all fds that are explicitly mapped to undef...
66 3         453 for my $fd ( grep { ! defined $fds->{$_} } keys %$fds) {
  10         81  
67 2 50       114 defined POSIX::close($fd) or die "Couldn't close descriptor [$fd] in pid [$PID]: $!\n";
68 2         35 next;
69             }
70              
71             # figure out what gets mapped to what (parent => client)
72 8         11543 my %p_map =
73 10         48 map { fileno($fds->{$_}) => $_ }
74 3         16 grep { defined $fds->{$_} }
75             keys %$fds;
76              
77 3         275 my %c_map = reverse %p_map;
78              
79             # setup file descriptors in child, closing and duping etc.
80 3         10 my %redir_fds;
81 3         55 while ( my ($c_fd, $p_fd) = each %c_map ) {
82              
83 8         31 delete $p_map{$p_fd};
84 8 100       151 next if $c_fd == $p_fd;
85              
86 6 100       45 $redir_fds{$c_fd} = POSIX::dup($c_fd) if $p_map{$c_fd};
87 6 100       38 $p_fd = $redir_fds{$p_fd} if $redir_fds{$p_fd};
88              
89 6 50       87 defined POSIX::close($c_fd) or die "Couldn't close descriptor [$c_fd] in pid [$PID]: $!\n";
90 6 50       155 defined POSIX::dup2($p_fd, $c_fd) or die "Couldn't dup2 [$p_fd],[$c_fd] in pid [$PID]: $!\n";
91 6 50       222 print STDOUT "Dup2 [$p_fd], [$c_fd]\n" if $DEBUG;
92             }
93              
94             #my $tmp = POSIX::dup(1);
95             #POSIX::dup2(2,1);
96             #POSIX::dup2($tmp,2);
97             #print "TMP: [$tmp]\n";
98              
99             }
100              
101             # finally, exec the command or sub.
102             sub __exec_cmd {
103 3     3   13 my ($cmd_spec) = @_;
104              
105 3 50       117 print Dumper $cmd_spec if $DEBUG;
106              
107 3 50       38 if (_STRING($cmd_spec)) {
108 0 0       0 exec $cmd_spec or die "Cannot exec [$cmd_spec]: $!\n";
109             }
110              
111 3 100       163 if (_CODELIKE($cmd_spec)) {
112 2         44 exit $cmd_spec->();
113             }
114              
115 1 50       4 if (_ARRAYLIKE($cmd_spec)) {
116              
117 1 50       4 if (_CODELIKE($cmd_spec->[0])) {
118 0           my $code = shift @$cmd_spec;
119 0           exit $code->(@$cmd_spec);
120             }
121              
122 1 0         exec(@$cmd_spec) or die "Cannot exec [$cmd_spec->[0]]: $!\n";
123             }
124              
125 0           croak "Invalid cmd_spec!\n";
126             }
127              
128             1 && q{this statement is true};
129              
130              
131             =pod
132              
133             =head1 NAME
134              
135             IPC::OpenAny - Run a process with control over any FDs it may use.
136              
137             =head1 VERSION
138              
139             version 0.005
140              
141             =head1 SYNOPSIS
142              
143             use IPC::OpenAny qw(openany);
144              
145             open my $fh, '>', 'fd3_out.txt';
146              
147             my $cmd_sub = sub {
148             print STDOUT "foo1\n";
149             print STDERR "foo2\n";
150             my $fd3_fh = IO::Handle->new_from_fd(3, '>');
151             print $fd3_fh "foo3\n";
152             };
153              
154             # call the class method
155             my $pid = IPC::OpenAny->run(
156             cmd_spec => $cmd_sub,
157             fds => {
158             0 => undef, # close this
159             1 => \*STDERR, # foo1
160             2 => \*STDOUT, # foo2
161             3 => $fh, # foo3
162             },
163             wait => 1,
164             );
165              
166              
167             # OR use the exported sub
168             open my $fd1_fh, '<', $0;
169             my $pid2 = openany(
170             cmd_spec => [qw(tr a-zA-Z n-za-mN-ZA-M)],
171             fds => {
172             0 => $fd1_fh,
173             },
174             );
175              
176             =head1 DESCRIPTION
177              
178             B
179             OR FEATURE REQUESTS>
180              
181             In the spirit of L and L, which give you 2 and 3 handles
182             to a child process, IPC::OpenAny makes it easy to start a process with any
183             file descriptors you want connected to whatever handles you want.
184              
185             =head1 METHODS
186              
187             =head2 run
188              
189             Runs the given command or code-ref in a separate process, with its
190             file descriptors mapped to handles or closed (or just left alone)
191             however the user may choose.
192              
193             Accepts the following parameters:
194              
195             =over 4
196              
197             =item cmd_spec
198              
199             This specifies the command or code to be executed.
200             If it is a string, it will be passed to L() which
201             will invoke it via the shell. If it is a coderef, that
202             coderef will be executed in a sepearate process just
203             like a system command. If it is an arrayref, the first
204             element will be used as the system command to execute,
205             and the remaining elements will be the arguments passed
206             to it. (I | I | I)
207              
208             =item fds
209              
210             Set this to a hashref where the keys are file descriptor
211             numbers in the child process and the values are either
212             perl file handles or undef. (I)
213              
214             =item env
215              
216             Set this to a hashref where the keys are the names of environment
217             variables and the values are the values you want set for those env
218             vars when the process is executed. (I)
219              
220             =item pwd
221              
222             Set this to the path you want to be the working directory of the
223             process that will be executed. (I)
224              
225             =back
226              
227             =head1 FUNCTIONS
228              
229             =head2 openany
230              
231             This exportable sub is just a thin wrapper around the L
232             method above. It takes the exact same parameters.
233              
234             =head1 SEE ALSO
235              
236             =over 4
237              
238             =item *
239              
240             L
241              
242             =item *
243              
244             L
245              
246             =item *
247              
248             L
249              
250             =item *
251              
252             L
253              
254             =back
255              
256             =head1 CAVEATS
257              
258             May not work on Win32, and I don't have a windows box with which to
259             develop and test it. Patches welcome!
260              
261             As usual, please report any other issues you may encounter!
262              
263             =head1 AUTHOR
264              
265             Stephen R. Scaffidi
266              
267             =head1 COPYRIGHT AND LICENSE
268              
269             This software is copyright (c) 2012 by Stephen R. Scaffidi.
270              
271             This is free software; you can redistribute it and/or modify it under
272             the same terms as the Perl 5 programming language system itself.
273              
274             =cut
275              
276              
277             __END__