File Coverage

blib/lib/IPC/PidStat.pm
Criterion Covered Total %
statement 102 118 86.4
branch 34 60 56.6
condition 1 6 16.6
subroutine 20 22 90.9
pod 6 10 60.0
total 163 216 75.4


line stmt bran cond sub pod time code
1             # See copyright, etc in below POD section.
2             ######################################################################
3              
4             package IPC::PidStat;
5             require 5.004;
6              
7 3     3   77174 use IPC::Locker;
  3         5  
  3         91  
8 3     3   13 use Socket;
  3         4  
  3         1081  
9 3     3   15 use Time::HiRes qw(gettimeofday tv_interval);
  3         5  
  3         11  
10 3     3   250 use IO::Socket;
  3         4  
  3         11  
11 3     3   2244 use Sys::Hostname;
  3         5  
  3         108  
12 3     3   1177 use Net::Domain;
  3         11539  
  3         131  
13 3     3   1277 use POSIX;
  3         15526  
  3         12  
14              
15 3     3   6750 use strict;
  3         5  
  3         80  
16 3     3   13 use vars qw($VERSION $Debug $Stat_Of_Pid_Supported %Local_Hostnames);
  3         16  
  3         154  
17 3     3   13 use Carp;
  3         4  
  3         3411  
18              
19             our @_Local_Responses;
20              
21             ######################################################################
22             #### Configuration Section
23              
24             # Other configurable settings.
25             $Debug = 0;
26              
27             $VERSION = '1.502';
28              
29             # True if pid existance can be detected by looking at /proc filesystem
30             $Stat_Of_Pid_Supported = -e "/proc/1";
31              
32             %Local_Hostnames = ('localhost' => 1,
33             hostname() => 1,
34             hostfqdn() => 1);
35              
36             ######################################################################
37             #### Creator
38              
39             sub new {
40             # Establish the server
41 1 50   1 1 1005263 @_ >= 1 or croak 'usage: IPC::PidStat->new ({options})';
42 1         18 my $proto = shift;
43 1   33     39 my $class = ref($proto) || $proto;
44 1         34 my $self = {
45             socket=>undef, # IO::Socket handle of open socket
46             tries=>5,
47             # Documented
48             port=>$IPC::Locker::Default_PidStat_Port,
49             # Internal
50             _host_ips => {}, # Resolved IP address of hosts
51             @_,};
52 1         11 bless $self, $class;
53 1         14 return $self;
54             }
55              
56             sub open_socket {
57 10     10 0 11 my $self = shift;
58             # Open the socket
59 10 100       32 return if $self->{_socket_fh};
60 1 50       35 $self->{_socket_fh} = IO::Socket::INET->new( Proto => 'udp')
61             or die "$0: %Error, socket: $!";
62             }
63              
64             sub fh {
65 6     6 0 8 my $self = shift;
66             # Return socket file handle, for external select loops
67 6         14 $self->open_socket(); #open if not already
68 6         44 return $self->{_socket_fh};
69             }
70              
71             sub pid_request {
72 4     4 1 7 my $self = shift;
73 4         51 my %params = (host=>'localhost',
74             pid=>$$,
75             return_exist=>1,
76             return_doesnt=>1,
77             return_unknown=>1,
78             @_);
79              
80 4         10 $self->open_socket(); #open if not already
81              
82 4         433 my $res;
83 4 100       15 if ($Local_Hostnames{$params{host}}) {
84             # No need to go via server, instead check locally
85 2         13 my $res = $self->_local_response($params{pid}, $params{host});
86 2 50       19 push @_Local_Responses, $res if $res;
87             # If unknown (undef response), forward to the server
88             }
89              
90 4 50       9 if (!defined $res) {
91             my $reqval = (($params{return_exist}?1:0)
92             | ($params{return_doesnt}?2:0)
93 4 50       33 | ($params{return_unknown}?4:0));
    50          
    50          
94 4         18 my $out_msg = "PIDR $params{pid} $params{host} $reqval\n";
95              
96 4         5 my $ipnum = $self->{_host_ips}->{$params{host}};
97 4 100       13 if (!$ipnum) {
98             # inet_aton("name") calls gethostbyname(), which chats with the
99             # NS cache socket and NIS server. Too costly in a polling loop.
100             $ipnum = inet_aton($params{host})
101 2 50       163 or die "%Error: Can't find host $params{host}\n";
102 2         12 $self->{_host_ips}->{$params{host}} = $ipnum;
103             }
104 4         31 my $dest = sockaddr_in($self->{port}, $ipnum);
105 4         76 $self->fh->send($out_msg,0,$dest);
106             }
107             }
108              
109             sub recv_stat {
110 4     4 1 5 my $self = shift;
111              
112 4         4 my $in_msg;
113 4 100       10 if ($#_Local_Responses >= 0) {
114 2         4 $in_msg = shift @_Local_Responses;
115 2 50       58 print "Got local response $in_msg\n" if $Debug;
116             } else {
117 2 50       5 $self->fh->recv($in_msg, 8192)
118             or return undef;
119 2 50       120 print "Got server response $in_msg\n" if $Debug;
120             }
121 4 50       38 if ($in_msg =~ /^EXIS (\d+) (\d+) (\S+)/) { # PID server response
    0          
122 4         16 my $pid=$1; my $exists = $2; my $hostname = $3;
  4         18  
  4         8  
123 4 50       30 print " Pid $pid Exists on $hostname? $exists\n" if $Debug;
124 4         88 return ($pid, $exists, $hostname);
125             } elsif ($in_msg =~ /^UNKN (\d+) (\s+) (\S+)/) { # PID not determinate
126 0         0 return undef;
127             }
128 0         0 return undef;
129             }
130              
131             sub pid_request_recv {
132 4     4 1 1486 my $self = shift;
133 4         20 my @params = @_;
134 4         15 for (my $try=0; $try<$self->{tries}; $try++) {
135 4         18 $self->pid_request(@params);
136 4         335 my @recved;
137 4         5 eval {
138 4     0   102 local $SIG{ALRM} = sub { die "Timeout\n"; };
  0         0  
139 4         22 alarm(1);
140 4         11 @recved = $self->recv_stat();
141 4         59 alarm(0);
142             };
143 4 50       10 alarm(0) if $@;
144 4 50       20 return @recved if defined $recved[0];
145             }
146 0         0 return undef;
147             }
148              
149             ######################################################################
150             #### Status checking
151              
152             sub ping_status {
153 0     0 0 0 my $self = shift;
154             my %params = (pid => 1, # Init.
155             host => $self->{host},
156 0         0 @_,
157             );
158             # Return OK and status message, for nagios like checks
159 0         0 my $start_time = [gettimeofday()];
160 0         0 my ($epid, $eexists, $ehostname) = eval {
161 0         0 return $self->pid_request_recv(%params);
162             };
163 0         0 my $elapsed = tv_interval ( $start_time, [gettimeofday]);
164              
165 0 0       0 if (!$eexists) {
166 0         0 return ({ok=>undef,status=>"No response from pidstatd on $self->{host}:$self->{port}"});
167             } else {
168 0         0 return ({ok=>1,status=>sprintf("%1.3f second response on $self->{host}:$self->{port}", $elapsed)});
169             }
170             }
171              
172             ######################################################################
173             #### Local messages
174              
175             sub _local_response {
176 2     2   8 my $self = shift;
177 2         3 my $pid = shift;
178 2         3 my $host = shift;
179              
180 2         13 my $exists = IPC::PidStat::local_pid_exists($pid);
181 2 100       13 if ($exists) {
    50          
182 1         6 return "EXIS $pid $exists $host"; # PID response
183             } elsif (defined $exists) { # Known not to exist
184 1         15 return "EXIS $pid $exists $host"; # PID response
185             } else { # Perhaps we're not running as root?
186 0         0 return undef;
187             }
188             }
189              
190             ######################################################################
191             #### Static Accessors
192              
193             our $_Hostfqdn;
194             sub hostfqdn {
195             # Return hostname() including domain name
196 14 100   14 0 90 $_Hostfqdn = Net::Domain::hostfqdn() if !defined $_Hostfqdn;
197 14         837 return $_Hostfqdn;
198             }
199              
200             ######################################################################
201             #### Utilities
202              
203             sub local_pid_doesnt_exist {
204 1     1 1 2 my $result = local_pid_exists(@_);
205             # Return 0 if a pid exists, 1 if not, undef (or second argument) if unknown
206 1 50       6 return undef if !defined $result;
207 1         5 return !$result;
208             }
209              
210             sub local_pid_exists {
211 4     4 1 494 my $pid = shift;
212             # Return 1 if a pid exists, 0 if not, undef (or second argument) if unknown
213             # We can't just call kill, because if there's a different user running the
214             # process, we'll get an error instead of a result.
215 4         12 $! = undef;
216 4 100       40 my $exists = (kill (0,$pid))?1:0;
217 4 100       18 if ($!) {
218 1 50 0     8 if ($! == POSIX::ESRCH) {
    0          
219 1         3 $exists = 0;
220             } elsif ($! == POSIX::EPERM # Sigh, different user?
221             && $Stat_Of_Pid_Supported ) { # This system supports /proc
222 0 0       0 $exists = (-e "/proc/$pid") ? 1:0;
223             } else {
224 0         0 $exists = undef; # Unknown reason
225             }
226             }
227 4         9 return $exists;
228             }
229              
230             ######################################################################
231             #### Package return
232             1;
233             =pod
234              
235             =head1 NAME
236              
237             IPC::PidStat - Process ID existence test
238              
239             =head1 SYNOPSIS
240              
241             use IPC::PidStat;
242              
243             my $exister = new IPC::PidStat(
244             port=>1234,
245             );
246             $exister->pid_request(host=>'foo', pid=>$pid)
247             while (1) { # Poll receiving callbacks
248             my ($epid, $eexists, $ehostname) = $exister->recv_stat();
249             print "Pid $epid ",($eexists?'exists':'dead'),"\n" if $ehostname;
250             }
251              
252             =head1 DESCRIPTION
253              
254             L allows remote requests to be made to the
255             L, to determine if a PID is running on the daemon's machine.
256              
257             PidStat uses UDP, and as such results are fast but may be unreliable.
258             Furthermore, the pidstatd may not even be running on the remote machine,
259             so responses should never be required before an application program makes
260             progress.
261              
262             =head1 METHODS
263              
264             =over 4
265              
266             =item new ([parameter=>value ...]);
267              
268             Creates a new object for later use. See the PARAMETERS section.
269              
270             =item pid_request (host=>$host, pid=>$pid);
271              
272             Sends a request to the specified host's server to see if the specified PID
273             exists.
274              
275             The optional parameters return_exist=>0, return_doesnt=>0 and
276             return_unknown=>0 improve performance by suppressing return messages if the
277             specified pid exists, doesn't exist, or has unknown state respectively.
278             Pidstatd versions before 1.480 ignore this flag, so the return code from
279             recv_stat should not assume the undesired return types will be suppressed.
280              
281             =item pid_request_recv (host=>$host, pid=>$pid);
282              
283             Calls pid_request and returns the recv_stat reply. If the response fails
284             to return in one second, it is retried up to 5 times, then undef is
285             returned.
286              
287             =item recv_stat()
288              
289             Blocks waiting for any return from the server. Returns undef if none is
290             found, or a 2 element array with the PID and existence flag. Generally
291             this would be called inside a IO::Poll loop.
292              
293             =back
294              
295             =head1 STATIC METHODS
296              
297             =over 4
298              
299             =item local_pid_doesnt_exist()
300              
301             Static call, not a method call. Return 0 if a pid exists, 1 if not.
302             Return undef if it can't be determined.
303              
304             =item local_pid_exists()
305              
306             Static call, not a method call. Return 1 if a pid exists, 0 if not.
307             Return undef if it can't be determined.
308              
309             =back
310              
311             =head1 PARAMETERS
312              
313             =over 4
314              
315             =item port
316              
317             The port number (INET) of the pidstatd server. Defaults to 'pidstatd'
318             looked up via /etc/services, else 1752.
319              
320             =back
321              
322             =head1 DISTRIBUTION
323              
324             The latest version is available from CPAN and from L.
325              
326             Copyright 2002-2022 by Wilson Snyder. This package is free software; you
327             can redistribute it and/or modify it under the terms of either the GNU
328             Lesser General Public License Version 3 or the Perl Artistic License Version 2.0.
329              
330             =head1 AUTHORS
331              
332             Wilson Snyder
333              
334             =head1 SEE ALSO
335              
336             L, L, L, L
337              
338             L
339              
340             =cut
341             ######################################################################