File Coverage

blib/lib/System2.pm
Criterion Covered Total %
statement 94 117 80.3
branch 30 58 51.7
condition 0 3 0.0
subroutine 13 14 92.8
pod 0 4 0.0
total 137 196 69.9


line stmt bran cond sub pod time code
1             package System2;
2              
3 2     2   19878 use strict;
  2         4  
  2         74  
4 2     2   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  2         2  
  2         172  
5 2     2   8 use POSIX qw(:sys_wait_h :limits_h);
  2         10  
  2         10  
6 2     2   1106 use Fcntl;
  2         4  
  2         468  
7 2     2   10 use Carp;
  2         4  
  2         142  
8              
9             require Exporter;
10             require AutoLoader;
11              
12             @ISA = qw(Exporter AutoLoader);
13             @EXPORT = qw( &system2 );
14             $VERSION = '0.84';
15              
16 2     2   24 use vars qw/ $debug /;
  2         2  
  2         686  
17              
18             # set to nonzero for diagnostics.
19             $debug=0;
20              
21             #---------------------------------
22              
23             my @handle = qw(C_OUT C_ERR);
24             my $sigchld; # previous SIGCHLD handler
25             my @args;
26             my %buf = ();
27             my %fn = ();
28             my ($rin, $win, $ein);
29             my ($rout, $wout, $eout);
30             my $pid;
31              
32             my $path;
33              
34             #---------------------------------
35             sub system2
36             {
37 2     2 0 1056848 @args = @_;
38              
39             # fake named parameters
40 2         22 my $named_param_check=0;
41 2 50       48 if ( $#args % 2 )
42             {
43 0         0 my %param = @args;
44             # look for arg0 path args
45 0 0 0     0 if ((exists $param{'args'}) && ( ref ($param{'args'}) eq 'ARRAY') )
46             {
47 0         0 @args = @{$param{'args'}};
  0         0  
48 0         0 $path = $param{'path'};
49 0 0       0 unshift @args, exists $param{'arg0'} ? $param{'arg0'} : $path;
50 0         0 $named_param_check++;
51             }
52             }
53              
54             # if we didn't find useful named parameters, treat as the legacy interface
55 2 50       38 if (! $named_param_check)
56             {
57 2 50       22 if (ref($args[0]) eq 'ARRAY')
58             {
59 0         0 my $arg0;
60 0         0 ($path, $arg0) = @{ shift @args };
  0         0  
61 0         0 unshift @args, $arg0;
62 2         12 } else { $path = $args[0]; }
63             }
64              
65             # set up handles to talk to forked process
66 2 50       84 pipe(P_IN, C_IN) || croak "can't pipe IN: $!";
67 2 50       44 pipe(C_OUT, P_OUT) || croak "can't pipe OUT: $!";
68 2 50       60 pipe(C_ERR, P_ERR) || croak "can't pipe ERR: $!";
69              
70             # prep filehandles. get file numbers, set to non-blocking.
71              
72 2         22 ($rin, $win, $ein) = ('') x 3;
73 2         10 ($rout, $wout, $eout) = ('') x 3;
74 2     2   10 no strict 'refs';
  2         2  
  2         324  
75 2         12 foreach( @handle )
76             {
77             # set to non-blocking
78 4         10 my $ret=0;
79 4 50       44 fcntl($_, F_GETFL, $ret) || croak "can't fcntl F_GETFL $_";
80 4         6 $ret |= O_NONBLOCK;
81 4 50       38 fcntl($_, F_SETFL, $ret) || croak "can't fcntl F_SETFL $_";
82              
83             # prep fd masks for select()
84 4         20 $fn{$_} = fileno($_);
85 4         174 vec($rin, $fn{$_}, 1) = 1;
86 4         42 $buf{$fn{$_}} = '';
87             }
88 2     2   8 use strict 'refs';
  2         2  
  2         982  
89              
90 2 50       32 $debug && carp "fork/exec: [$path] [".join('] [', @args)."]";
91              
92             # temporarily disable SIGCHLD handler
93 2 50       44 $sigchld = (defined $SIG{'CHLD'}) ? $SIG{'CHLD'} : 'DEFAULT';
94 2         28 $SIG{'CHLD'} = 'DEFAULT';
95              
96 2         1978 $pid = fork();
97 2 50       173 croak "can't fork [@args]: $!" unless defined $pid;
98              
99 2 100       113 &child if (!$pid); # child
100 1         65 my @res = &parent; # parent
101              
102 1         46 $SIG{'CHLD'} = $sigchld; # restore SIGCHLD handler
103              
104 1         31 @res; # return output from child process
105             }
106              
107             #---------------------------------
108              
109             sub child
110             {
111 1 50   1 0 17 $debug && carp "child pid: $$";
112              
113             # close unneeded handles, dup as necessary.
114 1 50       83 close C_IN || croak "child: can't close IN: $!";
115 1 50       18 close C_OUT || croak "child: can't close OUT: $!";
116 1 50       95 close C_ERR || croak "child: can't close ERR: $!";
117              
118 1 50       481 open(STDOUT, '>&P_OUT') || croak "child: can't dup STDOUT: $!";
119 1 50       32 open(STDERR, '>&P_ERR') || croak "child: can't dup STDERR: $!";
120              
121 1         69 select C_OUT; $|=1;
  1         47  
122 1         18 select C_ERR; $|=1;
  1         54  
123              
124             # from perldiag(1):
125             # Statement unlikely to be reached
126             # (W) You did an exec() with some statement after it
127             # other than a die(). This is almost always an error,
128             # because exec() never returns unless there was a
129             # failure. You probably wanted to use system() instead,
130             # which does return. To suppress this warning, put the
131             # exec() in a block by itself.
132              
133 1         7 { exec { $path } @args; }
  1         5  
  1         0  
134              
135 0         0 croak "can't exec [$path] [".join('] [', @args)."]: $!";
136             }
137              
138             #---------------------------------
139              
140             # parent
141              
142             sub parent
143             {
144             # close unneeded handles
145 1 50   1 0 53 close P_IN || croak "can't close IN: $!";
146 1 50       19 close P_OUT || croak "can't close OUT: $!";
147 1 50       105 close P_ERR || croak "can't close ERR: $!";
148              
149             # default exit status of child (we fail unless we succeed)
150 1         8 my $status = (1<<8);
151              
152             # get data from filehandles, append to appropriate buffers.
153 1         9 my $nfound = 0;
154 1         10 while ($nfound != -1)
155             {
156 254         751003 $nfound = select($rout=$rin, $wout=$win, $eout=$ein, 1.0);
157 254 50       876 if ($nfound == -1) { carp "select() said $!\n"; last }
  0         0  
  0         0  
158              
159 2     2   10 no strict 'refs';
  2         2  
  2         352  
160 254         404 foreach( @handle )
161             {
162 508 100       1219 if (vec($rout, $fn{$_}, 1))
163             {
164 435         363 my $read;
165 435         672 my $len = length($buf{$fn{$_}});
166 435         660 my $FD = $fn{$_};
167              
168 435         3712 while ($read = sysread ($_, $buf{$FD}, PIPE_BUF, $len))
169             {
170 1968 50       3521 if (!defined $read) { carp "read() said $!\n"; last }
  0         0  
  0         0  
171 1968 50       3181 if ($read == 0) { carp "read() said eof\n"; last }
  0         0  
  0         0  
172 1968         2010 $len += $read;
173 1968 50       21874 $debug && carp "read $read from $_ (len $len)";
174             }
175             }
176             }
177 2     2   6 use strict 'refs';
  2         4  
  2         302  
178              
179             # check for dead child
180              
181             # pid of exiting child; the waitpid returns -1 if
182             # we waitpid again...
183 254         1418 my $child = waitpid($pid, WNOHANG);
184              
185 254 100       623 last if ($child == -1); # child already exited
186             #next unless $child; # no stopped or exited children
187              
188             # Is it possible for me to have data in a buffer after the
189             # child has exited? Yep...
190              
191 253         636 $status = $?;
192             }
193              
194 1         10 $? = $status; # exit with child's status
195              
196 1         4801 ($buf{$fn{'C_OUT'}}, $buf{$fn{'C_ERR'}});
197             }
198              
199             #---------------------------------
200             sub exit_status
201             {
202 0     0 0   my $s = shift;
203              
204 0           my $exit_value = $s >> 8;
205 0           my $signal_num = $s & 127;
206 0           my $dumped_core = $s & 128;
207              
208 0           ($exit_value, $signal_num, $dumped_core);
209             }
210              
211             # Autoload methods go after =cut, and are processed by the autosplit program.
212              
213             1;
214             __END__