File Coverage

blib/lib/Proc/Short.pm
Criterion Covered Total %
statement 21 139 15.1
branch 2 54 3.7
condition 0 18 0.0
subroutine 5 14 35.7
pod 5 6 83.3
total 33 231 14.2


line stmt bran cond sub pod time code
1             ######################################################################
2             package Proc::Short;
3             ######################################################################
4             # Copyright 1999 by John Hanju Kim, all rights reserved.
5             #
6             # This program is free software, you can redistribute it and/or
7             # modify it under the same terms as Perl itself.
8             #
9             # The newest version of this module is available on
10             # your favourite CPAN site under
11             # CPAN/modules/by-author/id/JHKIM
12             #
13             ######################################################################
14              
15             =head1 NAME
16              
17             Proc::Short -- return short system calls with options
18              
19             =head1 SYNOPSIS
20              
21             use Proc::Short;
22              
23             $myproc = Proc::Short->new(); # create a new process object
24              
25             # settings below represent defaults
26             $myproc->maxtime(300); # set final timeout after 5 minutes
27             $myproc->num_tries(5); # try 5 times at most
28             $myproc->time_per_try(30); # time per try 30 sec
29             $myproc->time_btw_tries(5); # time between tries 5 sec
30              
31             # additional options
32             $myproc->accept_no_error(); # Re-try if any STDERR output
33             $myproc->pattern_stdout($pat); # require STDOUT to match regex $pat
34             $myproc->pattern_stderr($pat); # require STDERR to match regex $pat
35             $myproc->allow_shell(0); # never use shell for operation
36             $myproc->allow_shell(1); # allowed to use shell for op
37              
38             $myproc->run("shell-command-line"); # Launch a shell process
39             $myproc->run(sub { ... }); # Launch a perl subroutine
40             $myproc->run(\&subroutine); # Launch a perl subroutine
41              
42             Proc::Short::debug($level); # Turn debug on
43              
44             =head1 DESCRIPTION
45              
46             The Proc::Short is intended to be an extension of the backticks
47             operator in PERL which incorporates a number of options, including
48             collecting STDOUT and STDERR separately -- plus timeout and
49             automatic retries. A new process object is created by
50              
51             $myproc = Proc::Short->new();
52              
53             The default will timeout after 30 seconds (I) for each
54             attempt, will try a process up to 10 times, with 5 seconds
55             between each try. Either shell-like command lines or references
56             to perl subroutines can be specified for launching a process in
57             background. A simple list process, for example, can be started
58             via the shell as
59              
60             ($out, $in) = $myproc->run("ls");
61              
62             or, as a perl subroutine, with
63              
64             $myproc->run(sub { return <*>; });
65              
66             The I Method will try to run the named process. If the
67             process times out (after I seconds) or has a
68             error defined as unacceptable, it will wait (for I
69             seconds) and try again. This can repeat until I
70             seconds or I tries of the process to be run.
71              
72             The user can specify what constitutes an unacceptable error
73             of STDOUT or STDERR output -- i.e. demanding a retry. One
74             common shorthand is to have the I method retry if there
75             is any return from STDERR.
76              
77             $myproc->accept_no_error(); # Re-try if any STDERR
78             $myproc->pattern_stdout($pat); # require STDOUT to match regex $pat
79             $myproc->pattern_stderr($pat); # require STDERR to match regex $pat
80              
81             =cut
82              
83             require 5.003;
84 1     1   860 use strict;
  1         2  
  1         43  
85 1     1   6 use Carp;
  1         1  
  1         101  
86 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %SIG $AUTOLOAD);
  1         6  
  1         1674  
87              
88             require Exporter;
89              
90             @ISA = qw(Exporter AutoLoader);
91             @EXPORT = qw( );
92             $VERSION = '0.01';
93              
94             ######################################################################
95             # Globals: Debug and the mysterious waitpid nohang constant.
96             ######################################################################
97             my $Debug = 0;
98             my $alarm_msg = "Proc::Short: child timed out";
99             my $WNOHANG = _get_system_nohang();
100             my %intdefaults= ( "maxtime"=>300, "num_tries"=>5,
101             "time_per_try"=>30, "time_btw_tries"=>5,
102             "allow_shell"=>1 );
103              
104             ######################################################################
105              
106             =head1 METHODS
107              
108             The following methods are available:
109              
110             =over 4
111              
112             =item new (Constructor)
113              
114             Create a new instance of this class by writing either
115              
116             $proc = new Proc::Short; or $proc = Proc::Short->new();
117              
118             The I method takes no arguments.
119              
120             =cut
121              
122             ######################################################################
123             # $proc_obj=Proc::Short->new(); - Constructor
124             ######################################################################
125             sub new {
126 0     0 1 0 my $proto = shift;
127 0   0     0 my $class = ref($proto) || $proto;
128 0         0 my $self= { %intdefaults };
129              
130             # Object parameters defining operation
131 0         0 $self->{"pattern_stdout"}= undef; # required regex match on STDOUT
132 0         0 $self->{"pattern_stderr"}= undef; # required regex match on STDOUT
133 0         0 $self->{"allow_shell"}= 1;
134              
135             # Output fields
136 0         0 $self->{"stdout"}= undef;
137 0         0 $self->{"stderr"}= undef;
138 0         0 $self->{"status"}= undef;
139              
140 0         0 return bless($self, $class);
141             }
142              
143             ######################################################################
144              
145             =item findexec
146              
147             Find where the named executables are in the path, or die if
148             any are not found.
149              
150             ($fullpath) = $proc->needexec("ssh");
151              
152             The I method ...
153              
154             =cut
155              
156             ######################################################################
157             # find where named executables are
158             ######################################################################
159             sub findexec {
160 0     0 1 0 my $self= shift;
161 0         0 my ($needed, $found); # executable name we're looking for
162 0         0 my @path= split(':',$ENV{PATH});
163 0         0 foreach $needed (@_) {
164 0         0 foreach (@path) {
165 0         0 $_.="/$needed";
166 0 0 0     0 if (-f && -x) {
167 0         0 $found=$_;
168 0         0 last; # break out of loop over directories
169             } else {
170 0         0 $found= undef;
171             }
172             } # end of loop over @path directories
173 0         0 $_ = $found; # set input list to found
174             } # end of loop over executables needed
175 0 0       0 return $found if ((scalar @_) == 1);
176 0 0       0 return @_ if (wantarray());
177 0         0 return scalar(@_); # return number of elements found
178             }
179             ######################################################################
180              
181             =item unixhelp
182              
183             Try various ways to get help on a given executable.
184              
185             ($helpmsg) = $proc->unixhelp("ssh");
186              
187             =cut
188              
189             ######################################################################
190             # tries various ways to get help, and return shortest message
191             ######################################################################
192             sub unixhelp {
193 0     0 1 0 my ($self, $exec)= @_;
194 0 0       0 return undef unless findexec($exec);
195             # try -help, -h, -H, -hh
196 0         0 my @list;
197 0         0 foreach ("help", "h", "H", "hh") {
198 0         0 my ($out, $err)= run("$exec -v");
199             }
200             # look for "Usage:"
201              
202             }
203             ######################################################################
204              
205             =item unixversion
206              
207             Try various ways to get version number on a given executable.
208              
209             ($helpmsg) = $proc->unixhelp("ssh");
210              
211             =cut
212              
213             ######################################################################
214             # tries various ways to get version number
215             ######################################################################
216             sub unixversion {
217 0     0 1 0 my $exec= shift;
218 0         0 needexec("$exec");
219             # try -help -h -H -V --version -version
220 0         0 my $out= run("$exec -v");
221              
222             # look for "version #.#.#" "name (...) #.#.#
223              
224             }
225              
226             ######################################################################
227              
228             =item run
229              
230             Run a new process and collect the standard output and standard
231             error via separate pipes. By default, it forks off another
232             process and collects the output when it is done. There is a
233             time limit of
234              
235             ($out, $err, $status) = $proc->run("program-name");
236              
237             There are a number of options. You can start execution of an
238             independent Perl function (like "eval" except with timeout,
239             retries, etc.). Simply provide the function reference like
240              
241             ($out, $err, $status) = $proc->run(\&perl_function);
242              
243             or supply an unnamed subroutine:
244              
245             ($out, $err, $status) = $proc->run( sub { sleep(1) } );
246              
247             The I Method returns after the the function finishes,
248             one way or another.
249              
250             =cut
251              
252             ######################################################################
253             # ($out, $err, $status) = $proc_obj->run("prg"); - Run process
254             ######################################################################
255             sub run {
256 0     0 1 0 my $self = shift;
257 0         0 my $cmd = shift;
258              
259 0         0 my ($pid, $t, $out, $err, $status) = (undef) x 5;
260              
261 0         0 my $ntry= 0;
262 0         0 my $starttime= time();
263 0         0 my $endtime= time() + $self->maxtime();
264 0         0 my $time_per_try= $self->time_per_try();
265              
266 0         0 my $patout= $self->pattern_stdout();
267 0         0 my $paterr= $self->pattern_stdout();
268              
269 0         0 ATTEMPT: {
270 0         0 $self->_dprt("ATTEMPT $ntry: \"$cmd\" ");
271              
272             # set up pipes to collect STDOUT and STDERR from child process
273 0         0 pipe(GETSTDOUT,PUTSTDOUT);
274 0         0 pipe(GETSTDERR,PUTSTDERR);
275 0         0 pipe(GETSTATUS,PUTSTATUS);
276             # fork starts a child process, returns 1 for parent, 0 for child
277 0 0       0 if (($pid = fork()) == 0) { # if child process
278 0         0 $t= $endtime - time();
279 0 0       0 $t= $time_per_try if ($time_per_try < $t);
280             # Define procedure for when the child process times out
281 0     0   0 $SIG{'ALRM'} = sub { die $alarm_msg; };
  0         0  
282 0         0 eval {
283 0         0 alarm($t);
284 0 0       0 open(STDOUT, ">&=PUTSTDOUT") or croak
285             "Couldn't redirect STDOUT: $!";
286 0         0 $|= 1; # forces autoflushing of buffer
287 0 0       0 open(STDERR, ">&=PUTSTDERR") or croak
288             "Couldn't redirect STDERR: $!";
289 0         0 $|= 1;
290 0         0 close(GETSTDOUT); close(GETSTDERR); close(GETSTATUS);
  0         0  
  0         0  
291 0 0       0 if(ref($cmd) eq "CODE") {
292 0         0 $status= &$cmd; # Start perl subroutine
293             } else {
294 0         0 $status= system($cmd); # Start Shell-Process
295             }
296 0         0 print PUTSTATUS $status;
297 0         0 exit; # end of child process
298             } # end of eval
299             } # end of fork block
300              
301             # as parent, wait for child to finish
302 0 0       0 if (defined $WNOHANG) {
303 0         0 while (waitpid(-1, $WNOHANG) > 0) { 1; }
  0         0  
304             } else {
305 0         0 wait();
306             }
307             # continue with parent process procedure
308 0         0 close(PUTSTDOUT); close(PUTSTDERR); close(PUTSTATUS);
  0         0  
  0         0  
309 0         0 while () { $out .= $_; }
  0         0  
310 0         0 while () { $err .= $_; }
  0         0  
311 0         0 while () { $status .= $_; }
  0         0  
312 0         0 close(GETSTDOUT); close(GETSTDERR); close(GETSTATUS);
  0         0  
  0         0  
313              
314             # Now try to figure out if anything went wrong
315 0         0 $ntry++;
316 0         0 my $redo= 0;
317 0 0 0     0 if ($@ =~ /$alarm_msg\s*$/) {
    0 0        
    0          
    0          
318 0         0 $err .= "Timed out after $t seconds\n";
319 0         0 $redo++;
320             } elsif ((not defined($pid)) and ($! =~ /No more process/)) {
321 0         0 $err .= "PERL fork error: $!\n";
322 0         0 $redo++;
323             } elsif (not defined($pid)) {
324 0         0 $err .= "PERL fork error: $!\n";
325 0         0 $redo++;
326             } elsif (defined($patout) or defined($paterr)) {
327 0 0       0 $redo++ unless ($out =~ /$patout/);
328 0 0       0 $redo++ unless ($err =~ /$paterr/);
329             }
330 0         0 $self->_dprt("STDOUT\n$out");
331 0         0 $self->_dprt("STDERR\n$err");
332 0         0 $self->_dprt("RETURNVALUE $status");
333 0 0 0     0 if (($ntry < $self->num_tries) or (time() < $endtime)) {
    0          
334 0         0 $err .= "Final attempt after $ntry tries and $t seconds\n";
335             } elsif ($redo) {
336 0         0 sleep $self->time_btw_tries;
337 0         0 redo ATTEMPT;
338             }
339             } # end of ATTEMPT block
340 0 0       0 return $out unless (wantarray());
341 0         0 return ($out, $err, $status);
342             }
343              
344             ######################################################################
345             =item debug
346              
347             Switches debug messages on and off -- Proc::Short::debug(1) switches
348             them on, Proc::Short::debug(0) keeps Proc::Short quiet.
349              
350             =cut
351              
352             sub debug { $Debug = shift; } # debug($level) - Turn debug on/off
353              
354             ######################################################################
355             =item accept_no_error
356              
357             Switches debug messages on and off -- Proc::Short::debug(1) switches
358             them on, Proc::Short::debug(0) keeps Proc::Short quiet.
359              
360             =cut
361              
362 0     0 0 0 sub debug { $Debug = shift; } # debug($level) - Turn debug on/off
363              
364              
365             ######################################################################
366             =item maxtime
367             Return or set the maximum time in seconds per I method call.
368             Default is 300 seconds (i.e. 5 minutes).
369             =cut
370              
371             =item num_tries
372             Return or set the maximum number of tries the I method will
373             attempt an operation if there are unallowed errors. Default is 5.
374             =cut
375              
376             =item time_per_try
377             Return or set the maximum time in seconds for each attempt which
378             I makes of an operation. Multiple tries in case of error
379             can go longer than this. Default is 30 seconds.
380             =cut
381              
382             =item time_btw_tries
383             Return or set the time in seconds between attempted operations
384             in case of unacceptable error. Default is 5 seconds.
385             =cut
386              
387             sub AUTOLOAD {
388 0     0   0 my $self= shift;
389 0 0       0 my $type= ref($self) or croak("$self is not an object");
390 0         0 my $name= $AUTOLOAD;
391 0         0 $name =~ s/.*://; # strip qualified call, i.e. Geometry::that
392 0 0       0 unless (exists $self->{$name}) {
393 0         0 croak("Can't access `$name' field in object of class $type");
394             }
395 0 0       0 if (@_) {
396 0         0 my $val= @_;
397 0 0 0     0 if (defined($intdefaults{$name}) and not ($val =~ /\d+/)) {
398 0         0 croak "Invalid $name initializer $val";
399             }
400 0         0 $self->{$name}= $val;
401             }
402 0         0 return $self->{$name};
403             }
404              
405             ######################################################################
406             # Internal debug print function
407             ######################################################################
408             sub _dprt {
409 0 0   0   0 return unless $Debug;
410 0 0       0 if (ref($_[0])) {
411 0         0 warn ref(shift()), "> @_\n";
412             } else {
413 0         0 warn "> @_\n";
414             }
415             }
416              
417             ######################################################################
418             # This is for getting the WNOHANG constant of the system: a magic
419             # flag for the "waitpid" command which guards against certain errors
420             # which could hang the system.
421             #
422             # Since the waitpid(-1, &WNOHANG) command isn't supported on all Unix
423             # systems, and we still want Proc::Short to run on every system, we
424             # have to quietly perform some tests to figure out if -- or if not.
425             # The function returns the constant, or undef if it's not available.
426             ######################################################################
427             sub _get_system_nohang {
428 1     1   1 my $nohang;
429 1         32 open(SAVEERR, ">&STDERR");
430             # If the system doesn't even know /dev/null, forget about it.
431 1 50       48 open(STDERR, ">/dev/null") || return undef;
432             # Close stderr, since some weirdo POSIX modules write nasty
433             # error messages
434 1         6 close(STDERR);
435             # Check for the constant
436 1     1   808 eval 'use POSIX ":sys_wait_h"; $nohang = &WNOHANG;';
  1         7715  
  1         8  
  1         66  
437             # Re-open STDERR
438 1         28 open(STDERR, ">&SAVEERR");
439 1         5 close(SAVEERR);
440             # If there was an error, return undef
441 1 50       6 return undef if $@;
442 1         5 return $nohang;
443             }
444              
445             1;
446              
447             __END__