File Coverage

blib/lib/Unix/PID.pm
Criterion Covered Total %
statement 3 209 1.4
branch 2 150 1.3
condition 2 53 3.7
subroutine 1 25 4.0
pod 19 19 100.0
total 27 456 5.9


line stmt bran cond sub pod time code
1             package Unix::PID;
2              
3             # this works with these uncommented, but we leave them commented out to avoid a little time and memory
4             # use strict;
5             # use warnings;
6             $Unix::PID::VERSION = '0.23';
7              
8             sub import {
9 1     1   9 shift;
10 1 50 33     9 my $file = defined $_[0] && $_[0] !~ m{ \A \d+ \. \d+ \. \d+ \z }xms ? shift : '';
11              
12             #### handle use Mod '1.2.3'; here? make it play nice with version.pm ?? ##
13             # my $want = shift;
14             #
15             # if(defined $want && $want !~ m{^\d+\.\d+\.\d+$}) {
16             # require Carp;
17             # Carp::croak "Unix::PID is version $VERSION, you requested $want"
18             # if Unix::PID->VERSION < version->new($want)->numify();
19             # }
20             #### ???? ##
21              
22 1 50 33     20 if ( defined $file && $file ne '' ) {
23 0           require Carp;
24 0 0         Unix::PID->new()->pid_file($file)
25             || Carp::croak("The PID in $file is still running.");
26             }
27             }
28              
29             sub new {
30 0     0 1   my ( $class, $args_ref ) = @_;
31 0 0         $args_ref = {} if ref($args_ref) ne 'HASH';
32 0 0 0       my $self = bless(
    0 0        
33             {
34             'ps_path' => '',
35             'errstr' => '',
36             'minimum_pid' => !exists $args_ref->{'minimum_pid'} || $args_ref->{'minimum_pid'} !~ m{\A\d+\z}ms ? 11 : $args_ref->{'minimum_pid'},
37             'open3' => exists $args_ref->{'use_open3'} && !$args_ref->{'use_open3'} ? 0 : 1,
38             },
39             $class
40             );
41 0 0         require IPC::Open3 if $self->{'open3'};
42              
43 0 0         $self->set_ps_path( $args_ref->{'ps_path'} ) if exists $args_ref->{'ps_path'};
44              
45 0           return $self;
46             }
47              
48             sub get_ps_path {
49 0     0 1   return $_[0]->{'ps_path'};
50             }
51              
52             sub get_errstr {
53 0     0 1   return $_[0]->{'errstr'};
54             }
55              
56             sub non_blocking_wait {
57 0     0 1   my ($self) = @_;
58 0           while ( ( my $zombie = waitpid( -1, 1 ) ) > 0 ) { }
59             }
60              
61             sub set_ps_path {
62 0     0 1   my ( $self, $path ) = @_;
63 0 0         $path = substr( $path, 0, ( length($path) - 1 ) )
64             if substr( $path, -1, 1 ) eq '/';
65 0 0 0       if ( ( -d $path && -x "$path/ps" ) || $path eq '' ) {
      0        
66 0           $self->{'ps_path'} = $path;
67 0           return 1;
68             }
69             else {
70 0           return;
71             }
72             }
73              
74             sub get_pidof {
75 0     0 1   my ( $self, $name, $exact ) = @_;
76 0           my %map;
77 0           for ( $self->_raw_ps( 'axo', 'pid,command' ) ) {
78 0           $_ =~ s{ \A \s* | \s* \z }{}xmsg;
79 0           my ( $pid, $cmd ) = $_ =~ m{ \A (\d+) \s+ (.*) \z }xmsg;
80 0 0 0       $map{$pid} = $cmd if $pid && $pid ne $$ && $cmd;
      0        
81             }
82 0           my @pids =
83             $exact
84 0           ? grep { $map{$_} =~ m/^\Q$name\E$/ } keys %map
85 0 0         : grep { $map{$_} =~ m/\Q$name\E/ } keys %map;
86              
87 0 0         return wantarray ? @pids : $pids[0];
88             }
89              
90             sub kill {
91 0     0 1   my ( $self, $pid, $give_kill_a_chance ) = @_;
92 0           $give_kill_a_chance = int $give_kill_a_chance;
93 0           $pid = int $pid;
94 0           my $min = int $self->{'minimum_pid'};
95 0 0         if ( $pid < $min ) {
96              
97             # prevent bad args from killing the process group (IE '0')
98             # or general low level ones
99 0           warn "kill() called with integer value less than $min";
100 0           return;
101             }
102              
103             # CORE::kill 0, $pid : may be false but still running, see `perldoc -f kill`
104 0 0         if ( $self->is_pid_running($pid) ) {
105              
106             # RC from CORE::kill is not a boolean of if the PID was killed or not, only that it was signaled
107             # so it is not an indicator of "success" in killing $pid
108 0           CORE::kill( 15, $pid ); # TERM
109 0           CORE::kill( 2, $pid ); # INT
110 0           CORE::kill( 1, $pid ); # HUP
111 0           CORE::kill( 9, $pid ); # KILL
112            
113             # give kill() some time to take effect?
114 0 0         if ($give_kill_a_chance) {
115 0           sleep($give_kill_a_chance);
116             }
117 0 0         return if $self->is_pid_running($pid);
118             }
119 0           return 1;
120             }
121              
122             sub get_pid_from_pidfile {
123 0     0 1   my ( $self, $pid_file ) = @_;
124              
125             # if this function is ever changed to use $self as a hash object, update pid_file() to not do a class method call
126 0 0         return 0 if !-e $pid_file;
127              
128 0 0         open my $pid_fh, '<', $pid_file or return;
129 0           chomp( my $pid = <$pid_fh> );
130 0           close $pid_fh;
131              
132 0           return int( abs($pid) );
133             }
134              
135             sub is_pidfile_running {
136 0     0 1   my ( $self, $pid_file ) = @_;
137 0   0       my $pid = $self->get_pid_from_pidfile($pid_file) || return;
138 0 0         return $pid if $self->is_pid_running($pid);
139 0           return;
140             }
141              
142             sub pid_file {
143 0     0 1   my ( $self, $pid_file, $newpid, $retry_conf ) = @_;
144 0 0         $newpid = $$ if !$newpid;
145              
146 0           my $rc = $self->pid_file_no_unlink( $pid_file, $newpid, $retry_conf );
147 0 0 0       if ( $rc && $newpid == $$ ) {
148              
149             # prevent forked childrens' END from killing parent's pid files
150             # 'unlink_end_use_current_pid_only' is undocumented as this may change, feedback welcome!
151             # 'carp_unlink_end' undocumented as it is only meant for testing (rt57462, use Test::Carp to test END behavior)
152 0 0         if ( $self->{'unlink_end_use_current_pid_only'} ) {
153 0           eval 'END { unlink $pid_file if $$ eq ' . $$ . '}';
154 0 0         if ( $self->{'carp_unlink_end'} ) {
155              
156             # eval 'END { require Carp;Carp::carp("[info] $$ !unlink $pid_file (current pid check)") if $$ ne ' . $$ . '}';
157 0           eval 'END { require Carp;Carp::carp("[info] $$ unlink $pid_file (current pid check)") if $$ eq ' . $$ . '}';
158             }
159             }
160             else {
161 0           eval 'END { unlink $pid_file if Unix::PID->get_pid_from_pidfile($pid_file) eq $$ }';
162 0 0         if ( $self->{'carp_unlink_end'} ) {
163              
164             # eval 'END { require Carp;Carp::carp("[info] $$ !unlink $pid_file (pid file check)") if Unix::PID->get_pid_from_pidfile($pid_file) ne $$ }';
165 0           eval 'END { require Carp;Carp::carp("[info] $$ unlink $pid_file (pid file check)") if Unix::PID->get_pid_from_pidfile($pid_file) eq $$ }';
166             }
167             }
168             }
169              
170 0 0         return 1 if $rc == 1;
171 0 0 0       return 0 if defined $rc && $rc == 0;
172 0           return;
173             }
174              
175             sub pid_file_no_unlink {
176 0     0 1   my ( $self, $pid_file, $newpid, $retry_conf ) = @_;
177 0 0         $newpid = $$ if !$newpid;
178              
179 0 0         if ( ref($retry_conf) eq 'ARRAY' ) {
180 0           $retry_conf->[0] = int( abs( $retry_conf->[0] ) );
181 0           for my $idx ( 1 .. scalar( @{$retry_conf} ) - 1 ) {
  0            
182 0 0         next if ref $retry_conf->[$idx] eq 'CODE';
183 0           $retry_conf->[$idx] = int( abs( $retry_conf->[$idx] ) );
184             }
185             }
186             else {
187 0           $retry_conf = [ 3, 1, 2 ];
188             }
189              
190 0           my $passes = 0;
191 0           require Fcntl;
192              
193 0           EXISTS:
194             $passes++;
195 0 0         if ( -e $pid_file ) {
196              
197 0           my $curpid = $self->get_pid_from_pidfile($pid_file);
198              
199             # TODO: narrow even more the race condition where $curpid stops running and a new PID is put in
200             # the file between when we pull in $curpid above and check to see if it is running/unlink below
201              
202 0 0 0       return 1 if int $curpid == $$ && $newpid == $$; # already setup
203 0 0         return if int $curpid == $$; # can't change it while $$ is alive
204 0 0         return if $self->is_pid_running( int $curpid );
205              
206 0           unlink $pid_file; # must be a stale PID file, so try to remove it for sysopen()
207             }
208              
209             # write only if it does not exist:
210 0 0         sysopen( my $pid_fh, $pid_file, Fcntl::O_WRONLY() | Fcntl::O_EXCL() | Fcntl::O_CREAT() ) || do {
211 0 0         return 0 if $passes >= $retry_conf->[0];
212 0 0         if ( ref( $retry_conf->[$passes] ) eq 'CODE' ) {
213 0           $retry_conf->[$passes]->( $self, $pid_file, $passes );
214             }
215             else {
216 0 0         sleep( $retry_conf->[$passes] ) if $retry_conf->[$passes];
217             }
218 0           goto EXISTS;
219             };
220              
221 0           print {$pid_fh} int( abs($newpid) );
  0            
222 0           close $pid_fh;
223              
224 0           return 1;
225             }
226              
227             sub kill_pid_file {
228 0     0 1   my ( $self, $pidfile ) = @_;
229 0           my $rc = $self->kill_pid_file_no_unlink($pidfile);
230 0 0 0       if ( $rc && -e $pidfile ) {
231 0 0         unlink $pidfile or return -1;
232             }
233 0           return $rc;
234             }
235              
236             sub kill_pid_file_no_unlink {
237 0     0 1   my ( $self, $pidfile ) = @_;
238 0 0         if ( -e $pidfile ) {
239 0           my $pid = $self->get_pid_from_pidfile($pidfile);
240 0 0         $self->kill($pid) or return;
241 0           return $pid;
242             }
243 0           return 1;
244             }
245              
246             sub is_running {
247 0     0 1   my ( $self, $check_this, $exact ) = @_;
248 0 0         return $self->is_pid_running($check_this) if $check_this =~ m{ \A \d+ \z }xms;
249 0           return $self->is_command_running( $check_this, $exact );
250             }
251              
252             sub pid_info {
253 0     0 1   my ( $self, $pid ) = @_;
254 0           my @outp = $self->_pid_info_raw($pid);
255 0 0         return wantarray ? split( /\s+/, $outp[1], 11 ) : [ split( /\s+/, $outp[1], 11 ) ];
256             }
257              
258             sub pid_info_hash {
259 0     0 1   my ( $self, $pid ) = @_;
260 0           my @outp = $self->_pid_info_raw($pid);
261 0           my %info;
262 0           @info{ split( /\s+/, $outp[0], 11 ) } = split( /\s+/, $outp[1], 11 );
263 0 0         return wantarray ? %info : \%info;
264             }
265              
266             sub _pid_info_raw {
267 0     0     my ( $self, $pid ) = @_;
268 0           my @info = $self->_raw_ps( 'u', '-p', $pid );
269 0           chomp @info;
270 0 0         return wantarray ? @info : \@info;
271             }
272              
273             sub is_pid_running {
274 0     0 1   my ( $self, $check_pid ) = @_;
275 0           $check_pid = int($check_pid);
276 0 0         return if !$check_pid;
277            
278 0 0 0       return 1 if $> == 0 && CORE::kill( 0, $check_pid ); # if we are superuser we can avoid the the system call. For details see `perldoc -f kill`
279              
280             # If the proc filesystem is available, it's a good test. If not, continue on to system call
281 0 0 0       return 1 if -e "/proc/$$" && -r "/proc/$$" && -r "/proc/$check_pid";
      0        
282            
283             # even if we are superuser, go ahead and call ps just in case CORE::kill 0's false RC was erroneous
284 0           my $info = ( $self->_pid_info_raw($check_pid) )[1];
285 0 0         return 1 if defined $info;
286 0           return;
287             }
288              
289             sub is_command_running {
290 0     0 1   my ( $self, $check_command, $exact ) = @_;
291 0 0         return scalar $self->get_pidof( $check_command, $exact ) ? 1 : 0;
292             }
293              
294             sub wait_for_pidsof {
295 0     0 1   my ( $self, $wait_ref ) = @_;
296              
297 0 0         $wait_ref->{'get_pidof'} = $self->get_command($$) if !$wait_ref->{'get_pidof'};
298 0 0 0       $wait_ref->{'max_loops'} = 5
299             if !defined $wait_ref->{'max_loops'}
300             || $wait_ref->{'max_loops'} !~ m{ \A \d+ \z }xms;
301              
302             $wait_ref->{'hit_max_loops'} = sub {
303 0     0     die 'Hit max loops in wait_for_pidsof()';
304             }
305 0 0         if ref $wait_ref->{'hit_max_loops'} ne 'CODE';
306              
307 0           my @got_pids;
308 0 0         if ( ref $wait_ref->{'pid_list'} eq 'ARRAY' ) {
309 0 0         @got_pids = grep { defined } map { $self->is_pid_running($_) ? $_ : undef } @{ $wait_ref->{'pid_list'} };
  0            
  0            
  0            
310             }
311             else {
312 0           @got_pids = $self->get_pidof( $wait_ref->{'get_pidof'} );
313             }
314              
315 0 0 0       if ( $wait_ref->{'use_hires_usleep'} || $wait_ref->{'use_hires_nanosleep'} ) {
316 0           require Time::HiRes;
317             }
318              
319 0           my $lcy = '';
320 0           my $fib = '';
321 0 0         if ( ref $wait_ref->{'sleep_for'} ) {
322 0 0         if ( ref $wait_ref->{'sleep_for'} eq 'ARRAY' ) {
323 0           require List::Cycle;
324 0           $lcy = List::Cycle->new( { 'values' => $wait_ref->{'sleep_for'} } );
325             }
326 0 0         if ( $wait_ref->{'sleep_for'} eq 'HASH' ) {
327 0 0         if ( exists $wait_ref->{'sleep_for'}->{'fibonacci'} ) {
328 0           require Math::Fibonacci::Phi;
329 0           $fib = 1;
330             }
331             }
332             }
333 0 0         $wait_ref->{'sleep_for'} = 60 if !defined $wait_ref->{'sleep_for'};
334              
335 0           my $loop_cnt = 0;
336              
337 0           while ( scalar @got_pids ) {
338 0           $loop_cnt++;
339              
340 0 0         $wait_ref->{'pre_sleep'}->( $loop_cnt, \@got_pids )
341             if ref $wait_ref->{'pre_sleep'} eq 'CODE';
342              
343 0 0         my $period =
    0          
344             $lcy ? $lcy->next()
345             : $fib ? Math::Fibonacci::term($loop_cnt)
346             : $wait_ref->{'sleep_for'};
347              
348 0 0         if ( $wait_ref->{'use_hires_nanosleep'} ) {
    0          
349 0           Time::HiRes::nanosleep($period);
350             }
351             elsif ( $wait_ref->{'use_hires_usleep'} ) {
352 0           Time::HiRes::usleep($period);
353             }
354             else {
355 0           sleep $period;
356             }
357              
358 0 0         if ( ref $wait_ref->{'pid_list'} eq 'ARRAY' ) {
359 0 0         @got_pids = grep { defined } map { $self->is_pid_running($_) ? $_ : undef } @{ $wait_ref->{'pid_list'} };
  0            
  0            
  0            
360             }
361             else {
362 0           @got_pids = $self->get_pidof( $wait_ref->{'get_pidof'} );
363             }
364              
365 0 0         if ( $loop_cnt >= $wait_ref->{'max_loops'} ) {
366 0           $wait_ref->{'hit_max_loops'}->( $loop_cnt, \@got_pids );
367 0           last;
368             }
369             }
370             }
371              
372             sub _raw_ps {
373 0     0     my ( $self, @ps_args ) = @_;
374 0           my $path = $self->get_ps_path();
375 0           $self->{'errstr'} = '';
376              
377 0 0         if ( !$path ) {
378 0           for (
379             qw( /usr/local/bin /usr/local/sbin
380             /usr/bin /usr/sbin
381             /bin /sbin
382             )
383             ) {
384 0 0         if ( -x "$_/ps" ) {
385 0           $self->set_ps_path($_);
386 0           $path = $self->get_ps_path();
387 0           last;
388             }
389             }
390             }
391              
392 0 0         my $ps = $path ? "$path/ps" : 'ps';
393 0           my @out;
394              
395 0 0         if ( $self->{'open3'} ) {
396 0           local $SIG{'CHLD'} = 'IGNORE';
397              
398             # IPC::Open3 says: If CHLD_ERR is false, or the same file descriptor as CHLD_OUT, then STDOUT and STDERR of the child are on the same filehandle (this means that an autovivified lexical cannot be used for the STDERR filehandle, see SYNOPSIS).
399 0           my $err_fh = \*Unix::PID::PS_ERR;
400 0           my $pid = IPC::Open3::open3( my $in_fh, my $out_fh, $err_fh, $ps, @ps_args );
401              
402 0           @out = <$out_fh>;
403 0           $self->{'errstr'} = join '', <$err_fh>;
404              
405 0           close $in_fh;
406 0           close $out_fh;
407 0           close $err_fh;
408 0           waitpid( $pid, 0 );
409             }
410             else {
411              
412             # command's STDERR is not captured by backticks so we silence it, if you want finer grained control do not disable open3
413 0           @out = `$ps @ps_args 2>/dev/null`; # @ps_args will interpolate in these backticks like it does in double quotes
414             }
415              
416 0 0         return wantarray ? @out : join '', @out;
417             }
418              
419             sub AUTOLOAD {
420 0     0     my ( $self, $pid ) = @_;
421              
422             # return if $Unix::PID::AUTOLOAD eq 'Unix::PID::DESTROY'; # don't try to autoload this one ...
423              
424 0           my $subname = $Unix::PID::AUTOLOAD . '=';
425 0           $subname =~ s/.*:://;
426 0           $subname =~ s{\A get\_ }{}xms;
427              
428 0           my $data = $self->_raw_ps( '-p', $pid, '-o', $subname );
429 0           $data =~ s{ \A \s* | \s* \z }{}xmsg;
430 0           return $data;
431             }
432              
433 0     0     sub DESTROY { } # just to avoid trying to autoload this one ...
434              
435             1;