File Coverage

blib/lib/Any/Daemon.pm
Criterion Covered Total %
statement 24 145 16.5
branch 0 64 0.0
condition 0 22 0.0
subroutine 8 21 38.1
pod 3 4 75.0
total 35 256 13.6


line stmt bran cond sub pod time code
1             # Copyrights 2011-2018 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Any-Daemon. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Any::Daemon;
10 1     1   515 use vars '$VERSION';
  1         2  
  1         42  
11             $VERSION = '0.95';
12              
13              
14 1     1   4 use warnings;
  1         1  
  1         17  
15 1     1   4 use strict;
  1         1  
  1         15  
16              
17 1     1   343 use Log::Report::Optional 'any-daemon';
  1         65315  
  1         5  
18              
19 1     1   236 use POSIX qw(setsid setuid setgid :sys_wait_h);
  1         2  
  1         6  
20 1     1   530 use English qw/$EUID $EGID $PID/;
  1         1191  
  1         4  
21 1     1   179 use File::Spec ();
  1         2  
  1         18  
22              
23             use constant
24 1         1147 { SLEEP_FOR_SOME_TIME => 10
25             , ERROR_RECOVERY_SLEEP => 5
26             , SLOW_WARN_AGAIN_AFTER => 300
27 1     1   3 };
  1         2  
28              
29             # One program can only run one daemon
30             my %childs;
31              
32              
33 0     0 1   sub new(@) {my $class = shift; (bless {}, $class)->init({@_})}
  0            
34              
35             sub init($)
36 0     0 0   { my ($self, $args) = @_;
37 0           $self->{AD_pidfn} = $args->{pid_file};
38              
39 0           my $user = $args->{user};
40 0 0         if(defined $user)
    0          
41 0 0         { if($user =~ m/[^0-9]/)
42 0           { my $uid = $self->{AD_uid} = getpwnam $user;
43 0 0         defined $uid
44             or error __x"user {name} does not exist", name => $user;
45             }
46 0           else { $self->{AD_uid} = $user }
47             }
48             elsif($EUID==0)
49 0           { warning __"running daemon as root is dangerous: please specify user";
50             }
51              
52 0           my $group = $args->{group};
53 0 0         if(defined $group)
54 0 0         { if($group =~ m/[^0-9]/)
55 0           { my $gid = $self->{AD_gid} = getgrnam $group;
56 0 0         defined $gid
57             or error __x"group {name} does not exist", name => $group;
58             }
59             }
60              
61 0           $self->{AD_wd} = $args->{workdir};
62 0           $self;
63             }
64              
65             #--------------------
66              
67 0     0 1   sub workdir() {shift->{AD_wd}}
68              
69             #--------------------
70              
71             sub run(@)
72 0     0 1   { my ($self, %args) = @_;
73              
74 0           my $wd = $self->workdir;
75 0 0         if($wd)
76 0 0 0       { -d $wd or mkdir $wd, 0700
77             or fault __x"cannot create working directory {dir}", dir => $wd;
78              
79 0 0         chdir $wd
80             or fault __x"cannot change to working directory {dir}", dir => $wd;
81             }
82              
83 0 0         my $bg = exists $args{background} ? $args{background} : 1;
84 0 0         if($bg)
85 0           { trace "backgrounding managing daemon";
86              
87 0           my $kid = fork;
88 0 0         if($kid)
    0          
89             { # starting parent is ready to leave
90 0           exit 0;
91             }
92             elsif(!defined $kid)
93 0           { fault __x"cannot start the managing daemon";
94             }
95              
96 0 0         dispatcher('list') >= 1
97             or error __x"you need to have a dispatcher to send log to";
98             }
99              
100 0           my $pidfn = $self->{AD_pidfn};
101 0 0         if(defined $pidfn)
102 0           { local *PIDF;
103 0 0         if(open PIDF, '>', $pidfn)
104 0           { print PIDF "$PID\n";
105 0           close PIDF;
106             }
107             }
108              
109 0   0       my $gid = $self->{AD_gid} || $EGID;
110 0   0       my $uid = $self->{AD_uid} || $EUID;
111 0 0 0       if($gid!=$EGID && $uid!=$EUID)
112 0 0         { chown $uid,$gid, $wd if $wd;
113              
114 0 0         eval { if($] > 5.015007) { setgid $gid; setuid $uid }
  0            
  0            
  0            
115             else
116             { # in old versions of Perl, the uid and gid gets cached
117 0           $EGID = $gid;
118 0           $EUID = $uid;
119             }
120             };
121              
122 0 0         $@ and error __x"cannot switch to user/group to {uid}/{gid}: {err}"
123             , uid => $uid, gid => $gid, err => $@;
124             }
125              
126 0           my $sid = setsid;
127              
128 0   0       my $reconfig = $args{reconfig} || \&_reconfig_daemon;
129 0   0       my $kill_childs = $args{kill_childs} || \&_kill_childs;
130 0   0       my $child_died = $args{child_died} || \&_child_died;
131 0   0       my $max_childs = $args{max_childs} || 10;
132 0   0       my $child_task = $args{child_task} || \&_child_task;
133              
134             my $run_child = sub
135             { # re-seed the random number sequence per process
136 0     0     srand(time+$$);
137              
138             # unhandled errors are to be treated seriously.
139 0           my $rc = try { $child_task->(@_) };
  0            
140 0 0         if(my $e = $@->wasFatal) { $e->throw(reason => 'ALERT'); $rc = 1 }
  0            
  0            
141 0           $rc;
142 0           };
143              
144 0     0     $SIG{CHLD} = sub { $child_died->($max_childs, $run_child) };
  0            
145             $SIG{HUP} = sub
146 0     0     { notice "daemon received signal HUP";
147 0           $reconfig->(keys %childs);
148 0           $child_died->($max_childs, $run_child);
149 0           };
150              
151             $SIG{TERM} = $SIG{INT} = sub
152 0     0     { my $signal = shift;
153 0           notice "daemon terminated by signal $signal";
154              
155 0           $SIG{TERM} = $SIG{CHLD} = 'IGNORE';
156 0           $max_childs = 0;
157 0           $kill_childs->(keys %childs);
158 0           sleep 2; # give childs some time to stop
159 0           kill TERM => -$sid;
160 0 0         unlink $pidfn if $pidfn;
161 0 0         my $intrnr = $signal eq 'INT' ? 2 : 9;
162 0           exit $intrnr+128;
163 0           };
164              
165 0 0         if($bg)
166             { # no standard die and warn output anymore (Log::Report)
167 0           dispatcher close => 'default';
168              
169             # to devnull to avoid write errors in third party modules
170 0           open STDIN, '<', File::Spec->devnull;
171 0           open STDOUT, '>', File::Spec->devnull;
172 0           open STDERR, '>', File::Spec->devnull;
173             }
174              
175 0           notice __x"daemon started; proc={proc} uid={uid} gid={gid} childs={max}"
176             , proc => $PID, uid => $EUID, gid => $EGID, max => $max_childs;
177              
178 0           $child_died->($max_childs, $run_child);
179              
180             # child manager will never die
181 0           sleep 60 while 1;
182             }
183              
184             sub _reconfig_daemon(@)
185 0     0     { my @childs = @_;
186 0           notice "HUP: reconfigure deamon not implemented";
187             }
188              
189             sub _child_task()
190 0     0     { notice "No child_task implemented yet. I'll sleep for some time";
191 0           sleep SLEEP_FOR_SOME_TIME;
192             }
193              
194             sub _kill_childs(@)
195 0     0     { my @childs = @_;
196 0           notice "killing ".@childs." children";
197 0           kill TERM => @childs;
198             }
199              
200             # standard implementation for starting new childs.
201             sub _child_died($$)
202 0     0     { my ($max_childs, $run_child) = @_;
203              
204             # Clean-up zombies
205              
206             ZOMBIE:
207 0           while(1)
208 0           { my $kid = waitpid -1, WNOHANG;
209 0 0         last ZOMBIE if $kid <= 0;
210              
211 0 0         if($? != 0)
212 0 0         { my $err = WIFEXITED($?) ? "errno ".WEXITSTATUS($?) : "sig $?";
213 0           notice "$kid process terminated with $err";
214             # when children start to die, do not respawn too fast,
215             # because usually this means serious troubles with the
216             # server (like database) or implementation.
217 0           sleep ERROR_RECOVERY_SLEEP;
218             }
219              
220 0           delete $childs{$kid};
221             }
222              
223             # Start enough childs
224 0           my $silence_warn = 0;
225              
226             BIRTH:
227 0           while(keys %childs < $max_childs)
228 0           { my $kid = fork;
229 0 0         unless(defined $kid)
230 0 0         { alert "cannot fork new children" unless $silence_warn++;
231 0           sleep 1; # wow, back down! Probably too busy.
232 0 0         $silence_warn = 0 if $silence_warn==SLOW_WARN_AGAIN_AFTER;
233 0           next BIRTH;
234             }
235              
236 0 0         if($kid==0)
237             { # new child
238             $SIG{HUP} = $SIG{TERM} = $SIG{INT}
239 0     0     = sub {info 'child says bye'; exit 0};
  0            
  0            
240              
241             # I'll not handle my parent's kids!
242 0           $SIG{CHLD} = 'IGNORE';
243 0           %childs = ();
244              
245 0           my $rc = $run_child->();
246 0           exit $rc;
247             }
248              
249             # parent
250 0           $childs{$kid}++;
251             }
252             }
253              
254             1;