File Coverage

blib/lib/Working/Daemon.pm
Criterion Covered Total %
statement 39 283 13.7
branch 6 122 4.9
condition 0 10 0.0
subroutine 12 56 21.4
pod 0 46 0.0
total 57 517 11.0


line stmt bran cond sub pod time code
1             package Working::Daemon;
2              
3 2     2   198878 use 5.008;
  2         8  
  2         83  
4 2     2   13 use strict;
  2         7  
  2         75  
5 2     2   13 use warnings;
  2         9  
  2         86  
6 2     2   2567 use Data::Dumper;
  2         96059  
  2         181  
7 2     2   3173 use File::Copy;
  2         17356  
  2         181  
8 2     2   28759 use Getopt::Long;
  2         81107  
  2         16  
9 2     2   415 use Carp;
  2         4  
  2         44734  
10              
11             our $VERSION = 0.31;
12             our $SVN = 5236;
13             our %config;
14              
15             #these are all default configs
16              
17             # perl really need the protocols file to function
18 0     0 0 0 sub chroot_files { return ("/etc/protocols") }
19              
20 0     0 0 0 sub chroot_dirs { return ("/etc/") }
21              
22 0     0 0 0 sub default_action { return "start" }
23              
24 0     0 0 0 sub exit_success { exit(0) }
25              
26 0     0 0 0 sub exit_error { exit(1) }
27              
28             sub default_options {
29             return (
30 0     0 0 0 "help" => undef() => "This help",
31             "version" => undef() => "Version number",
32             "loglevel=i" => undef() => "The higher the loglevel, the more detailed messages. Default to 0",
33             "daemon!" => undef() => "Set to --no-daemon if you don't want it to daemonize. Default is true",
34             "chroot!" => undef() => "Set to --no-chroot if you don't want it to chroot. Default is true",
35             "foreground" => undef() => "Inverse of daemonize, default is off",
36             "user=s" => undef() => "User to run this app as. Default is 'nobody'",
37             "group=s" => undef() => "Group to run this app as. Default is 'nobody'",
38             "pidfile=s" => undef() => "Where to store the pidfile. Default is /var/run/\$name.pid",
39             "name=s" => undef() => "Name of this app")
40             }
41              
42              
43             sub tmpdir {
44 0     0 0 0 my $self = shift;
45 0         0 return "/tmp/" . $self->name . ".$$";
46             }
47              
48              
49             # end of config methods
50              
51             sub standard {
52 0     0 0 0 my $self = shift;
53 0         0 $self->parse_options(@_);
54 0         0 $self->do_action();
55 0         0 $self->change_root();
56 0         0 $self->drop_privs();
57             }
58              
59             sub new {
60 2     2 0 325 my $class = shift;
61 2         8 my $self = bless {}, $class;
62 2         8 return $self;
63             }
64              
65              
66             sub do_action {
67 0     0 0 0 my $self = shift;
68 0   0     0 my $action = shift @ARGV || $self->default_action;
69 0         0 my $action_method = "action_$action";
70 0 0       0 $self->print_version if($self->options->{version});
71 0 0       0 if ($self->options->{help}) {
72 0         0 $self->show_help;
73 0         0 exit;
74             }
75              
76 0 0       0 if($self->can($action_method)) {
77 0         0 my $exit_value = $self->$action_method;
78 0 0 0     0 exit $exit_value unless ($action eq 'start' || $action eq 'restart');
79             } else {
80 0         0 print STDERR "Unknown command '$action'\n";
81 0         0 $self->show_help;
82 0         0 exit;
83             }
84             }
85              
86             sub show_help {
87 0     0 0 0 my $self = shift;
88 0         0 my %options_desc = %{$self->options_desc};
  0         0  
89 0 0       0 %options_desc = $self->default_options if (!%options_desc);
90 0         0 my $max_length = 0;
91 0         0 my @commands;
92             my @desc;
93 0         0 my @values;
94 0         0 foreach my $option (keys %options_desc) {
95 0         0 my $command = $option;
96 0 0       0 if($command =~s/\=(.)%?//g) {
97 0 0       0 $command .= "=str" if($1 eq 's');
98 0 0       0 $command .= "=int" if($1 eq 'i');
99             }
100 0 0       0 $command = "no-$command" if($command =~s/\!$//);
101 0 0       0 $max_length = length($command) if(length($command) > $max_length);
102 0         0 push @commands, $command;
103 0         0 push @desc, $options_desc{$option};
104 0         0 $option =~s/(\w+)/$1/;
105 0         0 my $raw_option = $1;
106 0 0       0 if ($self->can($raw_option)) {
107 0         0 push @values, $self->$raw_option;
108             } else {
109 0   0     0 push @values, ($self->options->{$raw_option}||"");
110             }
111             }
112 0         0 $max_length += 4;
113 0         0 print STDERR "[start | stop | restart | status]\n";
114 0         0 foreach my $command (@commands) {
115 0         0 my $cmd = sprintf(" --%-${max_length}s", $command);
116 0         0 my $desc = shift @desc;
117 0         0 my $value = shift @values;
118 0         0 print STDERR "$cmd$desc: $value\n";
119             }
120 0         0 exit;
121             }
122              
123             sub parse_options {
124 0     0 0 0 my $self = shift;
125              
126 0         0 my %options;
127             my %option_keys;
128 0         0 my @options = ($self->default_options, @_);
129 0         0 while(@options) {
130 0         0 my $option = shift @options;
131 0         0 my $default_value = shift @options;
132 0         0 my $help = shift @options;
133 0         0 $option_keys{$option} = $help;
134 0         0 my ($key) = $option =~/(\w+)/;
135 0 0       0 $options{$key} = $default_value if(defined $default_value);
136             }
137 0         0 GetOptions(\%options, keys %option_keys);
138 0         0 $self->options(\%options);
139 0         0 $self->options_desc(\%option_keys);
140 0         0 $self->assign_options(qw(user group name chroot foreground daemon pidfile));
141 0         0 $self->init();
142 0         0 return \%options;
143              
144             }
145              
146 0     0 0 0 sub init {}
147              
148             sub print_version {
149 0     0 0 0 my $self = shift;
150 0         0 my $name = $self->name;
151 0         0 my $version = $self->version;
152 0         0 print STDERR "$name $version (Working::Daemon: $VERSION)\n";
153             }
154              
155              
156             sub assign_options {
157 0     0 0 0 my ($self, @options) = @_;
158 0         0 foreach my $option (@options) {
159 0 0       0 $self->$option($self->options->{$option})
160             if (exists $self->options->{$option});
161             }
162             }
163              
164              
165             sub change_root {
166 0     0 0 0 my $self = shift;
167 0 0       0 return unless $self->chroot;
168              
169 0         0 my $tmpdir = $self->tmpdir;
170 0 0       0 mkdir ($tmpdir)
171             || croak "Cannot create directory '$tmpdir': $!";
172              
173 0 0       0 chown($self->uid,$self->gid, $tmpdir)
174             || croak("Cannot chown $tmpdir to (". $self->uid . ":". $self->gid . "): $!");
175              
176 0         0 my $dirs = $self->{__PACKAGE__}->{chroot_clean_dirs} = [];
177 0         0 my $files = $self->{__PACKAGE__}->{chroot_clean_files} = [];
178              
179 0         0 foreach my $dir ($self->chroot_dirs) {
180 0         0 push @$dirs, "$tmpdir/$dir";
181 0 0       0 mkdir("$tmpdir/$dir")
182             || croak "Cannot create $tmpdir/$dir: $!";
183             }
184              
185 0         0 foreach my $file_to_copy ($self->chroot_files) {
186 0         0 push @$files, "$tmpdir/$file_to_copy";
187 0 0       0 copy("$file_to_copy", "$tmpdir/$file_to_copy")
188             || croak "Cannot copy $file_to_copy -> $tmpdir/$file_to_copy: $!";
189             }
190              
191 0 0       0 chroot("$tmpdir/")
192             || croak ("Can't chroot to $tmpdir: $!");
193 0 0       0 chdir("/")
194             || croak ("Can't chdir to '/': $!");
195             }
196              
197             sub version {
198 0     0 0 0 my $self = shift;
199 0         0 my $caller = caller(2);
200 2     2   34 no strict 'refs';
  2         5  
  2         2521  
201 0         0 my $varname = "${caller}::VERSION";
202 0         0 my $version = $$varname;
203 0   0     0 return $version || "";
204             }
205              
206             sub write_pidfile {
207 0     0 0 0 my $self = shift;
208 0         0 my $pidfile = $self->pidfile;
209 0 0       0 open(my $pidfh, "+>$pidfile") || croak "Cannot open '$pidfile': $!";
210 0         0 print $pidfh "$$";
211 0         0 close $pidfh;
212             }
213              
214              
215             sub delete_pidfile {
216 0     0 0 0 my $self = shift;
217 0 0       0 unlink($self->pidfile) || croak "Cannot remove pidfile '".$self->pidfile."': $!";
218             }
219              
220              
221 0     0 0 0 sub cleanup_chroot {
222             # unlink("/tmp/glbdns.$pid/etc/protocols") || die "$!";
223             # rmdir("/tmp/glbdns.$pid/etc/") || die;
224             # rmdir("/tmp/glbdns.$pid/") || die;
225             # unlink($config{pidfile}) || die $!;
226             }
227              
228             sub action_start {
229 0     0 0 0 my $self = shift;
230 0         0 my $name = $self->name;
231 0 0       0 if(my $pid = $self->get_pid) {
232 0         0 $self->log(0, "fatal", "Cannot start '$name' because it is already running at $pid");
233 0         0 $self->exit_error;
234             }
235 0         0 $self->log(0, 'info', "Starting '$name'");
236 0         0 $self->daemonize;
237 0         0 $self->spawn_worker_child;
238             }
239              
240             sub spawn_worker_child {
241 0     0 0 0 my $self = shift;
242 0 0       0 if(my $pid = fork()) {
243 0         0 my $name = $self->name;
244             # this is the master session
245             # it makes sure to cleanup from the slave
246             # it stays as superuser
247              
248              
249 0         0 $self->write_pidfile;
250              
251 0         0 $self->openlog;
252 0         0 $self->log(1, 'info', "started master session $name - child is $pid");
253 0     0   0 $SIG{INT} = sub { kill(2,$pid) };
  0         0  
254 0         0 $0 = "$name - waiting for child $pid";
255 0         0 $self->wait_for_worker_child($pid);
256 0         0 $self->log(1, 'info', "exiting master session $name - child is $pid");
257              
258 0         0 $self->cleanup_chroot;
259              
260 0         0 $self->delete_pidfile;
261 0         0 exit;
262             }
263              
264 0         0 return 1;
265             }
266              
267             sub wait_for_worker_child {
268 0     0 0 0 my ($self, $pid) = @_;
269 0         0 waitpid($pid, 0);
270             }
271              
272             sub action_restart {
273 0     0 0 0 my $self = shift;
274 0 0       0 if ($self->is_running) {
275 0         0 $self->action_stop
276             }
277 0         0 $self->action_start;
278             }
279              
280             sub action_status {
281 0     0 0 0 my $self = shift;
282 0 0       0 if (my $pid = $self->is_running) {
283 0         0 print STDERR $self->name . " is running on $pid\n";
284 0         0 return 0;
285             } else {
286 0         0 print STDERR $self->name . " is not running\n";
287 0         0 return 1;
288             }
289             }
290              
291             sub action_stop {
292 0     0 0 0 my $self = shift;
293 0         0 my $pid = $self->is_running;
294 0 0       0 if ($pid) {
295 0         0 while($self->is_running) {
296 0         0 kill(2, $pid);
297 0         0 $self->log(0, 'info', "sent SIGINT to $pid - waiting on stopped pid $pid");
298 0         0 sleep 1;
299             }
300 0         0 $self->log(0, 'info',"Stopped " . $self->name . " on $pid");
301             } else {
302 0         0 $self->log(0, 'info', $self->name . " is not running");
303             }
304 0         0 return 0;
305             }
306              
307             sub is_running {
308 0     0 0 0 my $self = shift;
309 0         0 my $pid = $self->get_pid;
310 0 0       0 return $pid
311             if($self->check_pid($pid));
312 0         0 return 0;
313             }
314              
315 0     0 0 0 sub openlog {
316             # openlog("$config{name}", 'ndelay,pid', LOG_DAEMON) if($config{syslog});}
317             }
318              
319              
320             sub get_pid {
321             # pid code needs serious overhaul to use flock
322 0     0 0 0 my $self = shift;
323 0         0 my $pidfile = $self->pidfile;
324 0 0       0 if(-r $pidfile) {
325 0 0       0 open(my $pidfh, "<$pidfile") || croak "Cannot open pidfile ($pidfile): $!";
326 0         0 my $line = <$pidfh>;
327 0         0 close($pidfh);
328 0         0 $line =~/(\d+)/;
329 0 0       0 if(my $pid_to_check = $1) {
330 0         0 $ENV{PATH} = '';
331 0 0       0 return $pid_to_check if($self->check_pid($pid_to_check));
332             }
333             }
334 0         0 return 0;
335             }
336              
337              
338             sub check_pid {
339 0     0 0 0 my $self = shift;
340 0         0 my $pid = shift;
341 0 0       0 return 0 unless $pid;
342 0         0 my $grep = "/bin/grep";
343 0 0       0 $grep = "/usr/bin/grep" if ($^O eq 'darwin');
344 0         0 my $name = $self->name;
345 0         0 my $rv = qx{/bin/ps ax | $grep $pid | $grep -v grep | $grep $name};
346 0         0 $rv =~s/\s+$//;
347 0         0 print STDERR "$rv\n";
348 0         0 return !$?;
349             }
350              
351              
352             sub daemonize {
353 0     0 0 0 my $self = shift;
354 0 0       0 return 0 unless $self->daemon;
355 2     2   32719 use POSIX qw(setsid);
  2         84551  
  2         20  
356 0         0 my $name = $self->name;
357 0 0       0 defined(my $pid = fork) || croak "Can't fork: $!";
358 0 0       0 if ($pid) {
359 0         0 print "$name started on $pid\n";
360 0         0 exit 0;
361             }
362 0 0       0 setsid() || croak "Can't start a new session: $!";
363 0 0       0 open (STDIN , '/dev/null') || croak "Can't read /dev/null: $!";
364 0 0       0 open (STDOUT, '>/dev/null') || croak "Can't write to /dev/null: $!";
365 0 0       0 open (STDERR, '>/dev/null') || croak "Can't write to /dev/null: $!";
366 0         0 return 1;
367             }
368              
369              
370             sub log {
371 0     0 0 0 my ($self, $level, $prio, $msg) = @_;
372 0 0       0 return if ($level > $self->log_level);
373 0         0 $self->do_log($prio, $msg);
374             }
375              
376              
377             sub do_log {
378 0     0 0 0 my ($self, $prio, $msg) = @_;
379 0         0 print STDERR "$prio - $msg\n";
380             }
381              
382              
383             sub drop_privs {
384 0     0 0 0 my $self = shift;
385             # drop user
386 0         0 $< = $self->uid;
387 0         0 $> = $self->uid;
388             # drop group
389 0         0 $( = $self->gid;
390 0         0 $) = $self->gid;
391             }
392              
393              
394             sub uid {
395 0     0 0 0 my $self = shift;
396 0         0 return scalar getpwnam($self->user);
397             }
398              
399              
400             sub gid {
401 0     0 0 0 my $self = shift;
402 0         0 return scalar getpwnam($self->group);
403             }
404              
405              
406              
407             # accessors
408             # yes they are nearly identical
409              
410             sub user {
411 0     0 0 0 my $self = shift;
412 0 0       0 if (@_) {
    0          
413 0         0 return $self->{__PACKAGE__}->{user} = shift;
414             } elsif (exists($self->{__PACKAGE__}->{user})) {
415 0         0 return $self->{__PACKAGE__}->{user};
416             } else {
417 0         0 return "nobody";
418             }
419             }
420              
421              
422             sub pidfile {
423 0     0 0 0 my $self = shift;
424 0 0       0 if (@_) {
    0          
425 0         0 return $self->{__PACKAGE__}->{pidfile} = shift;
426             } elsif (exists($self->{__PACKAGE__}->{pidfile})) {
427 0         0 return $self->{__PACKAGE__}->{pidfile};
428             } else {
429 0         0 return "/var/run/". $self->name . ".pid";
430             }
431             }
432              
433              
434             sub daemon {
435 10     10 0 26 my $self = shift;
436 10 100       56 if (@_) {
    100          
437 2         13 return $self->{__PACKAGE__}->{daemon} = shift;
438             } elsif (exists($self->{__PACKAGE__}->{daemon})) {
439 4         28 return $self->{__PACKAGE__}->{daemon};
440             } else {
441 4         23 return 1;
442             }
443             }
444              
445              
446             sub foreground {
447 5     5 0 13 my $self = shift;
448 5 100       20 if (@_) {
449 1         4 return $self->daemon(!$_[0]);
450             } else {
451 4         14 return !$self->daemon;
452             }
453             }
454              
455              
456             sub chroot {
457 0     0 0   my $self = shift;
458 0 0         if (@_) {
    0          
459 0           return $self->{__PACKAGE__}->{chroot} = shift;
460             } elsif (exists($self->{__PACKAGE__}->{chroot})) {
461 0           return $self->{__PACKAGE__}->{chroot};
462             } else {
463 0           return 1;
464             }
465             }
466              
467              
468             sub log_level {
469 0     0 0   my $self = shift;
470 0 0         if (@_) {
    0          
471 0           return $self->{__PACKAGE__}->{log_level} = shift;
472             } elsif (exists($self->{__PACKAGE__}->{log_level})) {
473 0           return $self->{__PACKAGE__}->{log_level};
474             } else {
475 0           return 1;
476             }
477             }
478              
479              
480             sub group {
481 0     0 0   my $self = shift;
482 0 0         if (@_) {
    0          
483 0           return $self->{__PACKAGE__}->{group} = shift;
484             } elsif (exists($self->{__PACKAGE__}->{group})) {
485 0           return $self->{__PACKAGE__}->{group};
486             } else {
487 0           return "nobody";
488             }
489             }
490              
491              
492             sub name {
493 0     0 0   my $self = shift;
494 0 0         if (@_) {
    0          
495 0           return $self->{__PACKAGE__}->{name} = shift;
496             } elsif (exists($self->{__PACKAGE__}->{name})) {
497 0           return $self->{__PACKAGE__}->{name};
498             } else {
499 0           return "unnamed app";
500             }
501             }
502              
503              
504             sub options {
505 0     0 0   my $self = shift;
506 0 0         if (@_) {
    0          
507 0           return $self->{__PACKAGE__}->{options} = shift;
508             } elsif (exists($self->{__PACKAGE__}->{options})) {
509 0           return $self->{__PACKAGE__}->{options};
510             } else {
511 0           return {};
512             }
513             }
514              
515             sub options_desc {
516 0     0 0   my $self = shift;
517 0 0         if (@_) {
    0          
518 0           return $self->{__PACKAGE__}->{options_desc} = shift;
519             } elsif (exists($self->{__PACKAGE__}->{options_desc})) {
520 0           return $self->{__PACKAGE__}->{options_desc};
521             } else {
522 0           return {};
523             }
524             }
525              
526              
527              
528              
529              
530              
531             # Preloaded methods go here.
532              
533             # Autoload methods go after =cut, and are processed by the autosplit program.
534              
535             1;
536             __END__