File Coverage

lib/Ubic/Daemon.pm
Criterion Covered Total %
statement 186 306 60.7
branch 77 168 45.8
condition 10 21 47.6
subroutine 22 28 78.5
pod 3 3 100.0
total 298 526 56.6


line stmt bran cond sub pod time code
1             package Ubic::Daemon;
2             $Ubic::Daemon::VERSION = '1.60';
3 33     33   502004 use strict;
  33         47  
  33         713  
4 33     33   97 use warnings;
  33         33  
  33         610  
5              
6             # ABSTRACT: daemon management utilities
7              
8              
9 33     33   536 use IO::Handle;
  33         4122  
  33         861  
10 33     33   12376 use IO::Select;
  33         35267  
  33         1288  
11 33     33   5369 use POSIX qw(setsid :sys_wait_h);
  33         53332  
  33         184  
12 33     33   18748 use Time::HiRes qw(sleep);
  33         11818  
  33         138  
13 33     33   3088 use Params::Validate qw(:all);
  33         44  
  33         3604  
14 33     33   130 use Carp;
  33         33  
  33         1333  
15 33     33   121 use Config;
  33         32  
  33         974  
16              
17 33     33   3237 use Ubic::Lockf;
  33         36  
  33         1239  
18 33     33   3035 use Ubic::AccessGuard;
  33         60  
  33         716  
19 33     33   8779 use Ubic::Daemon::Status;
  33         59  
  33         767  
20 33     33   8529 use Ubic::Daemon::PidState;
  33         46  
  33         753  
21              
22 33     33   155 use parent qw(Exporter);
  33         18  
  33         144  
23             our @EXPORT_OK = qw(start_daemon stop_daemon check_daemon);
24             our %EXPORT_TAGS = (
25             all => \@EXPORT_OK,
26             );
27              
28             our $OS;
29             sub import {
30 41     41   176 my %module = (
31             linux => 'Linux',
32             );
33              
34             # UBIC_DAEMON_OS support is here only for tests
35 41   50     315 my $module = $ENV{UBIC_DAEMON_OS} || $module{$^O} || 'POSIX';
36              
37 41         9062 require "Ubic/Daemon/OS/$module.pm";
38 41         1487 $OS = eval "Ubic::Daemon::OS::$module->new";
39 41 50       379 unless ($OS) {
40 0         0 die "failed to initialize OS-specific module $module: $@";
41             }
42 41         4836 __PACKAGE__->export_to_level(1, @_);
43             }
44              
45             {
46             my @signame;
47             sub _signame {
48 0     0   0 my $signum = shift;
49 0 0       0 unless (@signame) {
50 0         0 @signame = split /\s+/, $Config{sig_name};
51             }
52 0         0 return $signame[$signum];
53              
54             }
55             }
56              
57             sub _log {
58 51     51   102 my $fh = shift;
59 51 100       163 return unless defined $fh;
60 39         37 print {$fh} '[', scalar(localtime), "]\t$$\t", @_, "\n";
  39         2479  
61             }
62              
63             sub _log_exit_code {
64 0     0   0 my ($fh, $code, $pid) = @_;
65 0 0       0 if ($code == 0) {
66 0         0 _log($fh, "daemon $pid exited");
67 0         0 return;
68             }
69              
70 0         0 my $msg = "daemon $pid failed with \$? = $?";
71 0 0       0 if (my $signal = $? & 127) {
    0          
    0          
72 0         0 my $signame = _signame($signal);
73 0 0       0 if (defined $signame) {
74 0         0 $msg = "daemon $pid failed with signal $signame ($signal)";
75             }
76             else {
77 0         0 $msg = "daemon $pid failed with signal $signal";
78             }
79             }
80             elsif ($? & 128) {
81 0         0 $msg = "daemon $pid failed, core dumped";
82             }
83             elsif (my $code = $? >> 8) {
84 0         0 $msg = "daemon $pid failed, exit code $code";
85             }
86 0         0 _log($fh, $msg);
87             }
88              
89             sub stop_daemon($;@) {
90 86     86 1 34031992 my ($pidfile, @tail) = validate_pos(@_, { type => SCALAR }, 0);
91 83         2270 my $options = validate(@tail, {
92             timeout => { default => 30, regex => qr/^\d+$/ },
93             });
94 80 50       910 my $timeout = $options->{timeout} if defined $options->{timeout};
95              
96             # TODO - move this check into Ubic::Daemon::PidState
97 80         1396 my $pid_state = Ubic::Daemon::PidState->new($pidfile);
98 80 100       293 return 'not running' if $pid_state->is_empty;
99              
100 70         404 my $piddata = $pid_state->read;
101 70 50       308 unless ($piddata) {
102 0         0 return 'not running';
103             }
104 70         202 my $pid = $piddata->{pid};
105              
106 70 50       338 unless (check_daemon($pidfile)) {
107 0         0 return 'not running';
108             }
109 70         2966 kill 15 => $pid;
110 70         139 my $trial = 1;
111             {
112 70         81 my $sleep = 0.1;
  70         100  
113 70         83 my $total_sleep = 0;
114 70         67 while (1) {
115 212 100       1380 unless (check_daemon($pidfile)) {
116 67         985 return 'stopped';
117             }
118 145 100       613 last if $total_sleep >= $timeout;
119 142         47416784 sleep($sleep);
120 142         719 $total_sleep += $sleep;
121 142 100       2873 $sleep += 0.1 * $trial if $sleep < 1;
122 142         389 $trial++;
123             }
124             }
125 3 50       9 unless (check_daemon($pidfile)) {
126 0         0 return 'stopped';
127             }
128 3         81 die "failed to stop daemon with pidfile '$pidfile' (pid $pid, timeout $timeout, trials $trial)";
129             }
130              
131             sub start_daemon($) {
132 163     163 1 29823759 my %options = validate(@_, {
133             bin => { type => SCALAR | ARRAYREF, optional => 1 },
134             function => { type => CODEREF, optional => 1 },
135             name => { type => SCALAR, optional => 1 },
136             pidfile => { type => SCALAR },
137             stdout => { type => SCALAR, default => '/dev/null' },
138             stderr => { type => SCALAR, default => '/dev/null' },
139             ubic_log => { type => SCALAR, optional => 1 },
140             term_timeout => { type => SCALAR, default => 10, regex => qr/^\d+$/ },
141             cwd => { type => SCALAR, optional => 1 },
142             env => { type => HASHREF, optional => 1 },
143             proxy_logs => { type => BOOLEAN, optional => 1 },
144             credentials => { isa => 'Ubic::Credentials', optional => 1 },
145             start_hook => { type => CODEREF, optional => 1 },
146             kill_child_signal => { type => SCALAR, default => -15, optional => 1 },
147            
148             });
149             my ($bin, $function, $name, $pidfile, $stdout, $stderr, $ubic_log, $term_timeout, $cwd, $env, $credentials, $start_hook, $proxy_logs, $kill_child_signal)
150 162         2824 = @options{qw/ bin function name pidfile stdout stderr ubic_log term_timeout cwd env credentials start_hook proxy_logs kill_child_signal/};
151 162 50 66     864 if (not defined $bin and not defined $function) {
152 0         0 croak "One of 'bin' and 'function' should be specified";
153             }
154 162 50 66     999 if (defined $bin and defined $function) {
155 0         0 croak "Only one of 'bin' and 'function' should be specified";
156             }
157 162 100       501 unless (defined $name) {
158 126 100       324 if (ref $bin) {
159 76         301 $name = join ' ', @$bin;
160             }
161             else {
162 50   50     143 $name = $bin || 'anonymous';
163             }
164             }
165              
166 162 100       601 if (check_daemon($pidfile)) {
167 12         3948 croak "Daemon with pidfile $pidfile already running, can't start";
168             }
169              
170 150         479 my $pid_state = Ubic::Daemon::PidState->new($pidfile);
171 150         645 $pid_state->init;
172              
173 150 50       3966 pipe my ($read_pipe, $write_pipe) or die "pipe failed";
174 150         275 my $child;
175              
176 150 100       84892 unless ($child = fork) {
177 17 50       993 die "fork failed" unless defined $child;
178              
179 17         159 my $ubic_fh;
180             my $lock;
181             my $instant_exit = sub {
182 0 0   0   0 close($ubic_fh) if $ubic_fh;
183 0         0 STDOUT->flush;
184 0         0 STDERR->flush;
185 0         0 undef $lock;
186 0         0 POSIX::_exit(0); # don't allow any cleanup to happen - this process was forked from unknown environment, don't want to run unknown destructors
187 17         1008 };
188              
189 17         575 eval {
190 17 50       577 close($read_pipe) or die "Can't close read pipe: $!";
191             # forking child - will reopen standard streams, daemonize itself, fork into daemon binary and wait for it
192              
193             {
194 17 50       86 my $tmp_pid = fork() and POSIX::_exit(0); # detach from parent process
  17         9616  
195 17 50       839 die "fork failed" unless defined $tmp_pid;
196             }
197              
198             # Close all inherited filehandles except $write_pipe (it will be closed explicitly).
199             # Do not close fh if uses 'function' option instead of 'bin'
200             # ('function' support should be removed altogether because of this, actually; it's evil).
201 17 100       331 if ($bin) {
202 15         195 my $write_pipe_fd_num = fileno($write_pipe);
203 15         852 $OS->close_all_fh($write_pipe_fd_num); # except pipe
204             }
205              
206             my $open_handles_sub = sub {
207 17     17   76 my $guard;
208 17 50       124 $guard = Ubic::AccessGuard->new($credentials) if $credentials;
209 17 50       1627 open STDOUT, ">>", $stdout or die "Can't write to '$stdout': $!";
210 17 50       516 open STDERR, ">>", $stderr or die "Can't write to '$stderr': $!";
211 17         1234 STDOUT->autoflush(1);
212 17         2511 STDERR->autoflush(1);
213 17 100       417 if (defined $ubic_log) {
214 13 50       826 open $ubic_fh, ">>", $ubic_log or die "Can't write to '$ubic_log': $!";
215 13         106 $ubic_fh->autoflush(1);
216             }
217 17         598 };
218 17         159 $open_handles_sub->();
219 17         392 my $stdin = '/dev/null';
220 17 50       368 open STDIN, "<", $stdin or die "Can't read from '$stdin': $!";
221              
222 17         403 $SIG{HUP} = 'ignore';
223 17         343 $0 = "ubic-guardian $name";
224 17         473 setsid; # ubic-daemon gets it's own session
225 17         267 _log($ubic_fh, "guardian name: $0");
226              
227 17         107 _log($ubic_fh, "obtaining lock...");
228              
229             # We're passing 'timeout' option to lockf call to get rid of races.
230             # There should be no races when Ubic::Daemon is used in context of
231             # ubic service, because services have an additional lock, but
232             # Ubic::Daemon can be useful without services as well.
233 17 50       272 $lock = $pid_state->lock(5) or die "Can't lock $pid_state";
234              
235 17         139 $pid_state->remove;
236 17         93 _log($ubic_fh, "got lock");
237              
238 17         319 my %daemon_pipes;
239 17 100       107 if (defined $proxy_logs) {
240 2         14 for my $handle (qw/stdout stderr/) {
241 4 50       77 pipe my ($read, $write) or die "pipe for daemon $handle failed";
242 4         36 $daemon_pipes{$handle} = {read => $read, write => $write};
243             }
244             }
245 17         48 my $child;
246 17 50       10405 if ($child = fork) {
247             # guardian
248              
249 0         0 _log($ubic_fh, "guardian pid: $$");
250 0         0 _log($ubic_fh, "daemon pid: $child");
251              
252 0         0 my $child_guid = $OS->pid2guid($child);
253 0 0       0 unless ($child_guid) {
254 0 0       0 if ($OS->pid_exists($child)) {
255 0         0 die "Can't detect guid";
256             }
257 0         0 $? = 0;
258 0 0       0 unless (waitpid($child, WNOHANG) == $child) {
259 0         0 die "No pid $child but waitpid didn't collect $child status";
260             }
261 0         0 _log_exit_code($ubic_fh, $?, $child);
262 0         0 $pid_state->remove();
263 0         0 die "daemon exited immediately";
264             }
265 0         0 _log($ubic_fh, "child guid: $child_guid");
266 0         0 $pid_state->write({ pid => $child, guid => $child_guid });
267              
268             my $kill_sub = sub {
269 0 0   0   0 if ($term_timeout) {
270 0         0 _log($ubic_fh, "SIGTERM timeouted after $term_timeout second(s)");
271             }
272 0         0 _log($ubic_fh, "sending SIGKILL to $child");
273 0         0 kill -9 => $child;
274 0         0 _log($ubic_fh, "daemon $child probably killed by SIGKILL");
275 0         0 $pid_state->remove();
276 0         0 $instant_exit->();
277 0         0 };
278              
279 0         0 my $sigterm_sent;
280             $SIG{TERM} = sub {
281 0 0   0   0 if ($term_timeout > 0) {
282 0         0 $SIG{ALRM} = $kill_sub;
283 0         0 alarm($term_timeout);
284 0         0 _log($ubic_fh, "sending SIGTERM to $child");
285 0         0 kill $kill_child_signal => $child;
286 0         0 $sigterm_sent = 1;
287             }
288             else {
289 0         0 $kill_sub->();
290             }
291 0         0 };
292 0 0       0 print {$write_pipe} "pidfile written\n" or die "Can't write to pipe: $!";
  0         0  
293 0 0       0 close $write_pipe or die "Can't close pipe: $!";
294 0         0 undef $write_pipe;
295              
296 0 0       0 if (defined $proxy_logs) {
297             $SIG{HUP} = sub {
298 0     0   0 eval { $open_handles_sub->() };
  0         0  
299 0 0       0 if ($@) {
300 0         0 _log($ubic_fh, "failed to reopen stdout/stderr handles: $@");
301 0         0 $kill_sub->();
302             }
303             else {
304 0         0 _log($ubic_fh, "reopened stdout/stderr");
305             }
306 0         0 };
307              
308 0         0 for my $handle (qw/stdout stderr/) {
309 0 0       0 close($daemon_pipes{$handle}{write}) or do {
310 0         0 _log($ubic_fh, "Can't close $handle write: $!");
311 0         0 die "Can't close $handle write: $!"
312             };
313             }
314 0         0 my $sel = IO::Select->new();
315 0         0 $sel->add($daemon_pipes{stdout}{read}, $daemon_pipes{stderr}{read});
316 0         0 my $BUFF_SIZE = 4096;
317             READ:
318 0         0 while ($OS->pid_exists($child)) { # this loop is needed because of timeout in can_read
319 0         0 while (my @ready = $sel->can_read(1)) {
320 0         0 my $exhausted = 0;
321 0         0 for my $handle (@ready) {
322 0         0 my $data;
323 0         0 my $bytes_read = sysread($handle, $data, $BUFF_SIZE);
324 0 0       0 die "Can't poll $handle: $!" unless defined $bytes_read; # handle EWOULDBLOCK?
325 0 0       0 $exhausted += 1 if $bytes_read == 0;
326 0 0       0 if (fileno $handle == fileno $daemon_pipes{stdout}{read}) {
327 0         0 print STDOUT $data;
328             }
329 0 0       0 if (fileno $handle == fileno $daemon_pipes{stderr}{read}) {
330 0         0 print STDERR $data;
331             }
332             }
333 0 0       0 last READ if $exhausted == @ready;
334             }
335             }
336             }
337              
338 0         0 $? = 0;
339 0         0 waitpid($child, 0);
340 0         0 my $code = $?;
341 0 0 0     0 if ($sigterm_sent and ($code & 127) == &POSIX::SIGTERM) {
342             # it's ok, we probably sent this signal ourselves
343 0         0 _log($ubic_fh, "daemon $child exited by sigterm");
344             }
345             else {
346 0         0 _log_exit_code($ubic_fh, $code, $child);
347             }
348 0         0 $pid_state->remove;
349             }
350             else {
351             # daemon
352              
353 17 50       378 die "fork failed" unless defined $child;
354              
355             # start new process group - become immune to kills at parent group and at the same time be able to kill all processes below
356 17         708 setpgrp;
357 17         894 $0 = "ubic-daemon $name";
358              
359 17 50       231 if (defined $cwd) {
360 0 0       0 chdir $cwd or die "chdir to '$cwd' failed: $!";
361             }
362 17 50       158 if (defined $env) {
363 0         0 for my $key (keys %{ $env }) {
  0         0  
364 0         0 $ENV{$key} = $env->{$key};
365             }
366             }
367 17 100       235 $start_hook->() if $start_hook;
368 17 50       255 $credentials->set() if $credentials;
369              
370 17 100       406 close($ubic_fh) if defined $ubic_fh;
371 17         661 $lock->dissolve;
372              
373 17 50       61 print {$write_pipe} "execing into daemon\n" or die "Can't write to pipe: $!";
  17         368  
374 17 50       643 close($write_pipe) or die "Can't close pipe: $!";
375 17         226 undef $write_pipe;
376              
377 17 100       102 if (defined $proxy_logs) {
378             # redirecting standard streams to pipes
379 2   50     65 close($daemon_pipes{$_}{read}) or die "Can't close $_ read: $!" for qw/stdout stderr/;
380 2 50       96 open STDOUT, '>&=', $daemon_pipes{stdout}{write} or die "Can't open stdout write: $!";
381 2 50       30 open STDERR, '>&=', $daemon_pipes{stderr}{write} or die "Can't open stderr write: $!";
382             }
383              
384             # finally, run underlying binary
385 17 100       563 if (ref $bin) {
    100          
386 12 0       0 exec(@$bin) or die "exec failed: $!";
387             }
388             elsif ($bin) {
389 3 0       0 exec($bin) or die "exec failed: $!";
390             }
391             else {
392 2         39 $function->();
393             }
394             }
395             };
396 0 0       0 if ($write_pipe) {
397 0         0 print {$write_pipe} "Error: $@\n";
  0         0  
398 0         0 $write_pipe->flush;
399             }
400 0         0 $instant_exit->();
401             }
402 133         170406 waitpid($child, 0); # child should've exited immediately
403 133 50       1739 close($write_pipe) or die "Can't close write_pipe: $!";
404              
405 133         1007 my $out = '';
406 133         258500 while ( my $data = <$read_pipe>) {
407 266         28811831 $out .= $data;
408             }
409 133 50       2744 close($read_pipe) or die "Can't close read_pipe: $!";
410 133 100 66     4885 if ($out =~ /^execing into daemon$/m and $out =~ /^pidfile written$/m) {
411             # TODO - check daemon's name to make sure that exec happened
412 122         7671 return;
413             }
414 11         704 die "Failed to create daemon: '$out'";
415             }
416              
417             sub check_daemon {
418 649     649 1 12004190 my $pidfile = shift;
419 649         10541 my $options = validate(@_, {
420             quiet => { optional => 1 },
421             });
422              
423             my $print = sub {
424 10 50   10   25 print @_, "\n" unless $options->{quiet};
425 649         4651 };
426              
427 649         6808 my $pid_state = Ubic::Daemon::PidState->new($pidfile);
428 649 100       1787 return undef if $pid_state->is_empty;
429              
430 336         1527 my $lock = $pid_state->lock;
431 336         1546 my $piddata = $pid_state->read;
432 336 100       918 unless ($lock) {
433             # locked => daemon is alive
434 331         3754 return Ubic::Daemon::Status->new({ pid => $piddata->{daemon}, guardian_pid => $piddata->{pid} });
435             }
436              
437 5 50       20 unless ($piddata) {
438 0         0 return undef;
439             }
440              
441             # acquired lock when pidfile exists
442             # checking whether just ubic-guardian died or whole process group
443 5 50 33     145 if ($piddata->{format} and $piddata->{format} eq 'old') {
444 0         0 die "deprecated pidfile format detected\n";
445             }
446 5 50       20 unless ($piddata->{daemon}) {
447 33     33   87005 use Data::Dumper;
  33         155360  
  33         7894  
448 0         0 die "pidfile $pidfile exists, but daemon pid is not saved in it, so existing unguarded daemon can't be killed (piddata: ".Dumper($piddata).")";
449             }
450 5 50       55 unless ($OS->pid_exists($piddata->{daemon})) {
451 0         0 $pid_state->remove;
452 0         0 $print->("pidfile $pidfile removed - daemon with cached pid $piddata->{daemon} not found");
453 0         0 return undef;
454             }
455              
456             # TODO - wrap in eval and return undef if pid2cmd fails?
457 5         30 my $daemon_cmd = $OS->pid2cmd($piddata->{daemon});
458              
459 5         85 my $guid = $OS->pid2guid($piddata->{daemon});
460 5 50       20 unless ($guid) {
461 0         0 $print->("daemon '$daemon_cmd' from $pidfile just disappeared");
462 0         0 return undef;
463             }
464 5 50       20 if ($guid eq $piddata->{guid}) {
465 5         30 $print->("killing unguarded daemon '$daemon_cmd' with pid $piddata->{daemon} from $pidfile");
466 5         70 kill -9 => $piddata->{daemon};
467 5         25 $pid_state->remove;
468 5         25 $print->("pidfile $pidfile removed");
469 5         45 return undef;
470             }
471 0           $print->("daemon pid $piddata->{daemon} cached in pidfile $pidfile, ubic-guardian not found");
472 0           $print->("current process '$daemon_cmd' with pid $piddata->{daemon} has wrong guid ($piddata->{guid}, expected $guid) and will not be killed");
473 0           $print->("removing pidfile $pidfile");
474 0           $pid_state->remove;
475 0           return undef;
476             }
477              
478              
479             1;
480              
481             __END__