File Coverage

lib/Beekeeper/WorkerPool/Daemon.pm
Criterion Covered Total %
statement 15 258 5.8
branch 0 146 0.0
condition 0 19 0.0
subroutine 5 35 14.2
pod 0 26 0.0
total 20 484 4.1


line stmt bran cond sub pod time code
1             package Beekeeper::WorkerPool::Daemon;
2              
3 1     1   1086 use strict;
  1         2  
  1         31  
4 1     1   5 use warnings;
  1         2  
  1         42  
5              
6             our $VERSION = '0.08';
7              
8 1     1   603 use POSIX;
  1         6727  
  1         5  
9 1     1   3008 use Fcntl qw(:DEFAULT :flock);
  1         2  
  1         362  
10 1     1   898 use Getopt::Long;
  1         10923  
  1         4  
11              
12             my $PID_FILE_DIR = "/var/run";
13             my $LOG_FILE_DIR = "/var/log";
14              
15              
16             sub new {
17 0     0 0   my $class = shift;
18 0           my $self = {
19             options => {},
20             config => {},
21             daemonized => 0,
22             };
23 0           bless $self, $class;
24 0           $self->configure(@_);
25 0           return $self;
26             }
27              
28             sub configure {
29 0     0 0   my ($self, %config) = @_;
30 0           foreach (keys %config) {
31 0           $self->{config}->{$_} = $config{$_};
32             }
33             }
34              
35             sub config {
36 0     0 0   my ($self, $key) = @_;
37 0           $self->{config}->{$key};
38             }
39              
40             sub parse_options {
41 0     0 0   my $self = shift;
42              
43             # Parse command line options using Getopt::Long
44              
45 0           my @options_spec = ( "foreground", "user=s", "group=s", "help" );
46              
47 0           my $extra_options = $self->{config}->{get_options};
48 0 0         if ($extra_options) {
49 0           foreach my $opt (@$extra_options) {
50 0 0         next if (grep { $opt eq $_ } @options_spec);
  0            
51 0           push @options_spec, $opt;
52             }
53             }
54              
55             # GetOptions cannot be done twice
56 0 0         return if keys %{$self->{options}};
  0            
57              
58 0           my %options;
59 0 0         GetOptions(\%options, @options_spec) or CORE::exit(1);
60 0           $self->{options} = \%options;
61             }
62              
63             sub option {
64 0     0 0   my ($self, $option) = @_;
65 0           $self->{options}->{$option};
66             }
67              
68             sub daemon_name {
69 0     0 0   my $self = shift;
70 0           my $daemon_name = $self->{config}->{daemon_name};
71 0 0         unless ($daemon_name) {
72 0           $daemon_name = $0;
73 0           $daemon_name =~ s|.*/||;
74             }
75 0           return $daemon_name;
76             }
77              
78             sub daemon_description {
79 0     0 0   my $self = shift;
80 0 0         $self->{config}->{description} || 'daemon';
81             }
82              
83              
84             #------------------------------------------------------------------------------
85              
86             sub run {
87 0     0 0   my $self = shift;
88              
89 0           $self->parse_options;
90              
91 0           my $cmd = $ARGV[0];
92              
93 0 0 0       $cmd = 'help' if (!$cmd || $self->{options}->{help});
94              
95 0 0         if ($cmd eq 'start') {
    0          
    0          
    0          
    0          
    0          
96 0           $self->cmd_start;
97             #goto &cmd_start;
98             }
99             elsif ($cmd eq 'stop') {
100 0           $self->cmd_stop;
101             }
102             elsif ($cmd eq 'restart') {
103 0           $self->cmd_stop;
104 0           $self->cmd_start;
105             #goto &cmd_start;
106             }
107             elsif ($cmd eq 'reload') {
108 0           $self->cmd_reload;
109             }
110             elsif ($cmd eq 'check') {
111 0           $self->cmd_check;
112             }
113             elsif ($cmd eq 'help') {
114 0           $self->cmd_help;
115             }
116             else {
117 0           print "Unknown command '$cmd'.\n";
118 0           $self->cmd_help;
119             }
120             }
121              
122             sub cmd_start {
123 0     0 0   my $self = shift;
124              
125 0           print "Starting " . $self->daemon_description . ": " . $self->daemon_name;
126              
127 0 0         if ($self->daemon_is_running) {
128 0           print " is already running.\n";
129 0           return;
130             }
131              
132 0 0         print ".\n" if ($self->{options}->{foreground});
133              
134 0           $self->daemonize;
135             #goto &daemonize;
136              
137 0           print ".\n";
138             }
139              
140             sub cmd_stop {
141 0     0 0   my $self = shift;
142              
143 0           print "Stopping " . $self->daemon_description . ": " . $self->daemon_name;
144              
145 0 0         unless ($self->daemon_is_running) {
146 0           print " was not running.\n";
147 0           return;
148             }
149              
150 0           $self->stop_daemon;
151 0           print ".\n";
152             }
153              
154             sub cmd_reload {
155 0     0 0   my $self = shift;
156              
157 0           print "Reloading " . $self->daemon_description . ": " . $self->daemon_name;
158              
159 0           $self->hup_daemon;
160 0           print ".\n";
161             }
162              
163             sub cmd_check {
164 0     0 0   my $self = shift;
165              
166 0           print $self->daemon_name;
167              
168 0 0         if ($self->daemon_is_running) {
169 0           print " is running.\n";
170             }
171             else {
172 0           print " is not running.\n";
173             }
174             }
175              
176             sub cmd_help {
177 0     0 0   my $self = shift;
178              
179 0           my $progname = $0;
180 0           $progname =~ s|.*/||;
181              
182 0           print "Usage: $progname [options] {start|stop|restart|reload|check}\n";
183 0           print " --foreground Run in foreground (do not daemonize)\n";
184 0           print " --user Run as specified user\n";
185 0           print " --group Run as specified group\n";
186 0           print " --help Shows this message\n";
187             }
188              
189              
190             #------------------------------------------------------------------------------
191              
192             # DAEMONIZE
193              
194             sub daemonize {
195 0     0 0   my $self = shift;
196              
197 0 0         unless ($self->{options}->{foreground}) {
198              
199             # Fork and exit parent
200 0 0         _fork() && return;
201              
202             # Detach ourselves from the terminal
203 0 0         POSIX::setsid() or die("Cannot detach from controlling terminal");
204              
205             # Prevent possibility of acquiring a controling terminal
206 0           $SIG{'HUP'} = 'IGNORE';
207 0 0         _fork() && CORE::exit(0);
208              
209             # Change working directory
210 0           chdir "/";
211              
212             # Clear file creation mask
213 0           umask 0;
214              
215             # Close open file descriptors
216 0           my $openmax = POSIX::sysconf( &POSIX::_SC_OPEN_MAX );
217 0 0 0       $openmax = 64 if (!defined($openmax) || $openmax < 0);
218 0           foreach my $i (0..$openmax) { POSIX::close($i); }
  0            
219              
220 0           $self->redirect_output;
221              
222 0           $self->{daemonized} = 1;
223             }
224              
225 0           $self->write_pid_file;
226              
227 0           $self->change_effective_user;
228              
229 0           $self->main;
230              
231 0           CORE::exit(0);
232             }
233              
234             sub _fork {
235             FORK: {
236 0 0   0     if (defined(my $pid = fork())) {
  0 0          
237 0           return $pid;
238             }
239             elsif ($! =~ /No more process/) {
240 0           sleep(5);
241 0           redo FORK;
242             }
243             else {
244 0           die("Can't fork: $!");
245             }
246             }
247             }
248              
249              
250             sub redirect_output {
251 0     0 0   my $self = shift;
252              
253 0           my $logfile = $self->{config}->{log_file};
254              
255 0 0         unless ($logfile) {
256 0           my $dir = $LOG_FILE_DIR;
257 0           my $user = getpwuid($<);
258 0           my $file = $self->daemon_name . '.log';
259 0 0         $logfile = (-d "$dir/$user") ? "$dir/$user/$file" : "$dir/$file";
260             }
261              
262 0 0         die unless ($logfile =~ m/\.log$/);
263              
264 0 0         open(LOG, '>>', $logfile) or die("Can't open log file '$logfile': $!");
265              
266 0 0 0       open(STDERR, '>&', \*LOG) or (print "Can't redirect STDERR to log file: $!" && CORE::exit(1));
267 0 0         open(STDOUT, '>&', \*LOG) or die("Can't redirect STDOUT to log file: $!");
268 0 0         open(STDIN, '<', '/dev/null') or die("Can't reopen STDIN to /dev/null: $!");
269              
270             # Autoflush after each write
271 0           $| = 1;
272             }
273              
274              
275             sub change_effective_user {
276 0     0 0   my $self = shift;
277              
278             # Note that privileges are not permanently dropped and can be restored.
279             # If you need to drop privileges permanently, override this method and
280             # use the module Unix::SetUser which allows to do that (or think about
281             # using 'su' to start your daemon as a non root user)
282              
283             # Only root can swith user
284 0 0         return unless ($> == 0);
285              
286 0   0       my $as_user = $self->{options}->{user} || "nobody";
287 0   0       my $as_group = $self->{options}->{group} || "nogroup";
288              
289 0           my $uid = getpwnam($as_user);
290 0           my $gid = getgrnam($as_group);
291              
292 0 0         unless (defined $uid) {
293 0           die("Cannot switch to a non existent user '$as_user'");
294             }
295 0 0         unless (defined $gid) {
296 0           die("Cannot switch to a non existent group '$as_group'");
297             }
298 0 0         unless ($uid > 0) {
299 0           die("Cannot run daemon as root");
300             }
301              
302             # Change the effective gid
303 0 0         $) = $gid or die("Cannot switch to group '$as_group': $!");
304              
305             # Change the effective uid
306 0 0         $> = $uid or die("Cannot switch to user '$as_user': $!");
307             }
308              
309             sub restore_effective_user {
310 0     0 0   my $self = shift;
311              
312             # Only root can swith user
313 0 0         return unless ($< == 0);
314            
315             # Restore the effective uid to the real uid
316 0           $> = $<;
317              
318             # Restore the effective gid to the real gid
319 0           $) = $(;
320             }
321              
322              
323             #------------------------------------------------------------------------------
324              
325             # PIDFILE HANDLING
326              
327             sub pid_file {
328 0     0 0   my $self = shift;
329              
330 0           my $pidfile = $self->{config}->{pidfile};
331              
332 0 0         unless ($pidfile) {
333 0           my $dir = $PID_FILE_DIR;
334 0           my $user = getpwuid($<);
335 0           my $file = $self->daemon_name . '.pid';
336 0 0         $pidfile = (-d "$dir/$user") ? "$dir/$user/$file" : "$dir/$file";
337             }
338              
339 0           return $pidfile;
340             }
341              
342             sub write_pid_file {
343 0     0 0   my $self = shift;
344 0           my $pidfile = $self->pid_file;
345              
346 0 0         die unless ($pidfile =~ m/\.pid$/);
347              
348             # Open the pidfile in exclusive mode, to avoid race conditions
349 0 0         sysopen(my $fh, $pidfile, O_RDWR|O_CREAT) or die("Cannot open pid file '$pidfile': $!");
350 0 0         flock($fh, LOCK_EX | LOCK_NB) or die("Pid file '$pidfile' is already locked");
351              
352             # Read the content of the pidfile
353 0           my $pid = <$fh>;
354              
355 0 0 0       if ($pid && $pid =~ m/^(\d+)/ && $pid != $$) {
      0        
356             # File already exists and contains a process id. Check then if that
357             # process id actually belong to a running instance of this daemon
358 0 0         if ($self->verify_daemon_process($pid)) {
359 0           close($fh);
360 0           die("Cannot write pid file: alredy running");
361             }
362             }
363              
364             # Write our process id to the file
365 0 0         sysseek($fh, 0, 0) or die("Cannot seek in pid file '$pidfile': $!");
366 0 0         truncate($fh, 0) or die("Cannot truncate pid file '$pidfile': $!");
367 0 0         syswrite($fh, "$$\n", length("$$\n")) or die("Cannot write to pid file '$pidfile': $!");
368 0           close($fh);
369             }
370              
371             sub read_pid_file {
372 0     0 0   my $self = shift;
373 0           my $pidfile = $self->pid_file;
374              
375 0 0         unless (-e $pidfile) {
376             # Pidfile does not exists
377 0           return;
378             }
379              
380             # Read the content of the pidfile
381 0 0         open(my $fh, '<', $pidfile) or die("Cannot open pid file '$pidfile': $!");
382 0           my ($pid) = <$fh> =~ /^(\d+)/;
383 0           close($fh);
384              
385 0           return $pid;
386             }
387              
388             sub delete_pid_file {
389 0     0 0   my $self = shift;
390              
391 0           my $pid = $self->read_pid_file;
392              
393 0 0         unless ($pid) {
394             # Do not delete file, it does not exist or does not contain a process id
395 0           return;
396             }
397              
398 0 0         unless ($pid == $$) {
399             # Do not delete file, it was not created by this process
400 0           return;
401             }
402              
403 0           my $pidfile = $self->pid_file;
404 0 0         die unless ($pidfile =~ m/\.pid$/);
405 0 0         unlink($pidfile) or warn("Cannot unlink pid file '$pidfile' : $!");
406             }
407              
408             sub verify_daemon_process {
409 0     0 0   my ($self, $pid) = @_;
410              
411             # Verify that the process identifed by the pid is actually running and
412             # is an instance of this daemon. This is necessary because the process id
413             # written to the pidfile by an instance of the daemon may coincidentally
414             # be reused by another process after a system restart, thus making the
415             # daemon think it's already running and preventing it from start at boot
416             # time. This implementation checks the 'ps' output.
417              
418 0 0         unless (kill(0, $pid)) {
419             # Process is not running
420 0           return 0;
421             }
422              
423 0 0         unless ($^O =~ m/linux|freebsd/i) {
424             # The ps verification will only work for Linux and FreeBSD
425 0           return 1;
426             }
427              
428 0           my $me = $0;
429 0           $me =~ s|.*/||;
430              
431 0 0         die unless ($pid =~ m/^\d+$/); # paranoid security check
432 0 0         my $ps_output = `ps -fp $pid` or die("ps utility not available: $!");
433              
434 0           my @ps_lines = split("$/", $ps_output);
435 0 0         return 0 unless (scalar @ps_lines == 2);
436 0           s/^\s+// foreach (@ps_lines); # trim leading spaces
437 0           my @ps_header = split(/\s+/, $ps_lines[0]);
438 0           my $columns_count = scalar @ps_header;
439 0           my @ps_cols = split(/\s+/, $ps_lines[1], $columns_count);
440 0           my $command = $ps_cols[$columns_count - 1]; # last column
441              
442 0           my $me_regex = quotemeta($me);
443 0 0         return ($command =~ m/$me_regex/) ? 1 : 0;
444             }
445              
446             sub daemon_is_running {
447 0     0 0   my $self = shift;
448              
449 0           my $pid = $self->read_pid_file;
450              
451             # Daemon is not running if not pidfile exist
452 0 0         return 0 unless ($pid);
453              
454             # Verify that the process identifed by the readed pid is actually
455             # running and is an instance of this daemon
456 0 0         return 0 unless ($self->verify_daemon_process($pid));
457              
458 0           return $pid;
459             }
460              
461              
462             #------------------------------------------------------------------------------
463              
464             # PROCESS TERMINATION
465              
466             sub stop_daemon {
467 0     0 0   my $self = shift;
468              
469 0           my $pid = $self->daemon_is_running;
470              
471             # Nothing to do if daemon is not running
472 0 0         return unless ($pid);
473              
474 0           my $send_SIGINT = 120; # seconds #TODO: This should be configurable
475 0           my $send_SIGKILL = 130; # seconds
476 0           my $give_up = 140; # seconds
477              
478 0           my $start_time = time();
479 0           local $| = 1;
480              
481             # Send SIGTERM (terminate request) signal
482 0 0         if (kill( SIGTERM, $pid )) {
483             WAIT: {
484 0           sleep(1);
  0            
485 0 0         return unless kill(0, $pid);
486 0           my $elapsed = time() - $start_time;
487 0 0         redo if ($elapsed < $send_SIGINT);
488             }
489             }
490              
491             # Send SIGINT (interrupt request) signal
492 0 0         if (kill( SIGINT, $pid )) {
493 0           print "\nSending SIGINT to process $pid...";
494             WAIT: {
495 0           sleep(1);
  0            
496 0 0         return unless kill(0, $pid);
497 0           my $elapsed = time() - $start_time;
498 0 0         redo if ($elapsed < $send_SIGKILL);
499             }
500             }
501              
502             # Send SIGKILL (terminate immediately) signal
503 0 0         if (kill( SIGKILL, $pid )) {
504 0           print "\nSending SIGKILL to process $pid...";
505             WAIT: {
506 0           sleep(1);
  0            
507 0 0         return unless kill(0, $pid);
508 0           my $elapsed = time() - $start_time;
509 0 0         redo if ($elapsed < $give_up);
510             }
511             }
512              
513 0           print "\nGiving up, cannot kill process $pid.\n";
514 0           CORE::exit(1);
515             }
516              
517             sub hup_daemon {
518 0     0 0   my $self = shift;
519              
520 0           my $pid = $self->daemon_is_running;
521              
522             # Nothing to do if daemon is not running
523 0 0         return unless ($pid);
524              
525 0           kill( SIGHUP, $pid );
526             }
527              
528             sub DESTROY {
529 0     0     my $self = shift;
530              
531 0           $self->restore_effective_user;
532              
533 0           $self->delete_pid_file;
534             }
535              
536              
537             #------------------------------------------------------------------------------
538              
539             # main() method is intended to be overrided, this is just a placeholder
540              
541             sub main {
542 0     0 0   my $self = shift;
543              
544 0           print "\nStarted...\n";
545              
546 0           my $quit = 0;
547              
548 0     0     $SIG{'TERM'} = sub { $quit = 1 }; # SIGTERM terminate request
  0            
549 0     0     $SIG{'INT'} = sub { $quit = 1 }; # SIGINT interrupt request, Ctrl-C
  0            
550              
551 0           while (!$quit) {
552             # Do something here...
553 0           sleep 1;
554             }
555              
556 0           print "Stopped\n";
557             }
558              
559             1;
560              
561             __END__