File Coverage

blib/lib/JIP/Daemon.pm
Criterion Covered Total %
statement 131 135 97.0
branch 80 88 90.9
condition 32 36 88.8
subroutine 16 16 100.0
pod 4 4 100.0
total 263 279 94.2


line stmt bran cond sub pod time code
1             package JIP::Daemon;
2              
3 1     1   3038 use 5.006;
  1         2  
4 1     1   5 use strict;
  1         1  
  1         23  
5 1     1   18 use warnings;
  1         1  
  1         66  
6 1     1   401 use JIP::ClassField 0.05;
  1         1879  
  1         3  
7 1     1   44 use File::Spec;
  1         2  
  1         15  
8 1     1   386 use POSIX ();
  1         5207  
  1         68  
9 1     1   7 use Carp qw(carp croak);
  1         2  
  1         55  
10 1     1   5 use English qw(-no_match_vars);
  1         1  
  1         5  
11              
12             our $VERSION = '0.041';
13              
14             my $maybe_set_subname = sub { $ARG[1]; };
15              
16             # Supported on Perl 5.22+
17             eval {
18             require Sub::Util;
19              
20             if (my $set_subname = Sub::Util->can('set_subname')) {
21             $maybe_set_subname = $set_subname;
22             }
23             };
24              
25             my $default_log_callback = sub {
26             my ($self, @params) = @ARG;
27              
28             if (defined(my $logger = $self->logger)) {
29             my $msg;
30              
31             if (@params == 1) {
32             $msg = shift @params;
33             }
34             elsif (@params) {
35             my $format = shift @params;
36             $msg = sprintf $format, @params;
37             }
38              
39             $logger->info($msg) if defined $msg;
40             }
41             };
42              
43             has [qw(
44             pid
45             uid
46             gid
47             cwd
48             umask
49             logger
50             dry_run
51             is_detached
52             log_callback
53             on_fork_callback
54             stdout
55             stderr
56             program_name
57             )] => (get => q{+}, set => q{-});
58              
59             has devnull => (get => q{+}, set => q{-}, default => File::Spec->devnull);
60              
61             sub new {
62 45     45 1 72601 my ($class, %param) = @ARG;
63              
64             # Perform a trial run with no changes made (foreground if dry_run)
65 45 100 66     130 my $dry_run = (exists $param{'dry_run'} and $param{'dry_run'}) ? 1 : 0;
66              
67 45         48 my $uid;
68 45 100       73 if (exists $param{'uid'}) {
69 4         14 $uid = $param{'uid'};
70              
71 4 100 100     237 croak q{Bad argument "uid"}
72             unless defined $uid and $uid =~ m{^\d+$}x;
73             }
74              
75 43         45 my $gid;
76 43 100       56 if (exists $param{'gid'}) {
77 4         6 $gid = $param{'gid'};
78              
79 4 100 100     148 croak q{Bad argument "gid"}
80             unless defined $gid and $gid =~ m{^\d+$}x;
81             }
82              
83 41         44 my $cwd;
84 41 100       65 if (exists $param{'cwd'}) {
85 4         18 $cwd = $param{'cwd'};
86              
87 4 100 100     142 croak q{Bad argument "cwd"}
88             unless defined $cwd and length $cwd;
89             }
90              
91 39         38 my $umask;
92 39 100       52 if (exists $param{'umask'}) {
93 4         5 $umask = $param{'umask'};
94              
95 4 100 100     141 croak q{Bad argument "umask"}
96             unless defined $umask and length $umask;
97             }
98              
99 37         40 my $logger;
100 37 100       55 if (exists $param{'logger'}) {
101 3         5 $logger = $param{'logger'};
102              
103 3 100 100     143 croak q{Bad argument "logger"}
      66        
104             unless defined $logger and ref $logger and $logger->can('info');
105             }
106              
107 35         77 my $log_callback;
108 35 100       65 if (exists $param{'log_callback'}) {
109 6         7 $log_callback = $param{'log_callback'};
110              
111 6 100 100     148 croak q{Bad argument "log_callback"}
112             unless defined $log_callback and ref($log_callback) eq 'CODE';
113              
114 4         17 $log_callback = $maybe_set_subname->('custom_log_callback', $log_callback);
115             }
116             else {
117 29         132 $log_callback = $maybe_set_subname->('default_log_callback', $default_log_callback);
118             }
119              
120 33         39 my $on_fork_callback;
121 33 100       51 if (exists $param{'on_fork_callback'}) {
122 3         5 $on_fork_callback = $param{'on_fork_callback'};
123              
124 3 100 100     140 croak q{Bad argument "on_fork_callback"}
125             unless defined $on_fork_callback and ref($on_fork_callback) eq 'CODE';
126              
127 1         5 $on_fork_callback = $maybe_set_subname->('on_fork_callback', $on_fork_callback);
128             }
129              
130 31         33 my $stdout;
131 31 100       46 if (exists $param{'stdout'}) {
132 2         3 $stdout = $param{'stdout'};
133              
134 2 50 66     137 croak q{Bad argument "stdout"}
135             unless defined $stdout and length $stdout;
136             }
137              
138 29         31 my $stderr;
139 29 100       42 if (exists $param{'stderr'}) {
140 2         2 $stderr = $param{'stderr'};
141              
142 2 50 66     135 croak q{Bad argument "stderr"}
143             unless defined $stderr and length $stderr;
144             }
145              
146 27         42 my $program_name = $PROGRAM_NAME;
147 27 100       35 if (exists $param{'program_name'}) {
148 4         6 $program_name = $param{'program_name'};
149              
150 4 100 100     171 croak q{Bad argument "program_name"}
151             unless defined $program_name and length $program_name;
152             }
153              
154 25         68 return bless({}, $class)
155             ->_set_dry_run($dry_run)
156             ->_set_uid($uid)
157             ->_set_gid($gid)
158             ->_set_cwd($cwd)
159             ->_set_umask($umask)
160             ->_set_logger($logger)
161             ->_set_log_callback($log_callback)
162             ->_set_on_fork_callback($on_fork_callback)
163             ->_set_pid($PROCESS_ID)
164             ->_set_is_detached(0)
165             ->_set_stdout($stdout)
166             ->_set_stderr($stderr)
167             ->_set_program_name($program_name)
168             ->_set_devnull;
169             }
170              
171             sub daemonize {
172 9     9 1 2820 my $self = shift;
173              
174 9 100       18 return $self if $self->is_detached;
175              
176             # Fork and kill parent
177 7 100       33 if (not $self->dry_run) {
178 5         23 $self->_log('Daemonizing the process');
179              
180 5         9 my $pid = POSIX::fork(); # returns child pid to the parent and 0 to the child
181              
182 5 100       1725 croak q{Can't fork} if not defined $pid;
183              
184             # fork returned 0, so this branch is the child
185 4 100       9 if ($pid == 0) {
186 2 100       5 POSIX::setsid()
187             or croak(sprintf q{Can't start a new session: %s}, $OS_ERROR);
188              
189 1         321 $self->reopen_std;
190 1         316 $self->change_program_name;
191              
192 1         317 $self->_set_pid(POSIX::getpid())->_set_is_detached(1);
193             }
194              
195             # this branch is the parent
196             else {
197 2         6 $self->_log('Spawned process pid=%d. Parent exiting', $pid);
198 2         4 $self->_set_pid($pid)->_set_is_detached(1);
199              
200 2 100       17 if (defined(my $cb = $self->on_fork_callback)) {
201 1         6 $cb->($self);
202             }
203              
204 2         808 POSIX::exit(0);
205             }
206             }
207             else {
208 2         11 $self->_set_pid($PROCESS_ID);
209             }
210              
211 5         1532 return $self->drop_privileges;
212             }
213              
214             sub reopen_std {
215 1     1   1493 my $self = shift;
216              
217 1         5 my $stdin = q{<}. $self->devnull;
218              
219 1         6 my $stdout;
220 1 50       3 if (defined $self->stdout) {
221 0         0 $stdout = $self->stdout;
222 0         0 $self->_log('Reopen STDOUT to: %s', $stdout);
223             }
224             else {
225 1         7 $stdout = q{+>}. $self->devnull;
226             }
227              
228 1         5 my $stderr;
229 1 50       3 if (defined $self->stderr) {
230 0         0 $stderr = $self->stderr;
231 0         0 $self->_log('Reopen STDERR to: %s', $stderr);
232             }
233             else {
234 1         7 $stderr = q{+>}. $self->devnull;
235             }
236              
237 1 50       32 open STDIN, $stdin or croak(sprintf q{Can't reopen STDIN: %s}, $OS_ERROR);
238 1 50       23 open STDOUT, $stdout or croak(sprintf q{Can't reopen STDOUT: %s}, $OS_ERROR);
239 1 50       22 open STDERR, $stderr or croak(sprintf q{Can't reopen STDERR: %s}, $OS_ERROR);
240              
241 1         7 return $self;
242             }
243              
244             sub drop_privileges {
245 9     9   386 my $self = shift;
246              
247 9 100       16 if (defined(my $uid = $self->uid)) {
248 2         13 $self->_log('Set uid=%d', $uid);
249 2 100       5 POSIX::setuid($uid)
250             or croak(sprintf q{Can't set uid "%s": %s}, $uid, $OS_ERROR);
251             }
252              
253 8 100       600 if (defined(my $gid = $self->gid)) {
254 2         9 $self->_log('Set gid=%d', $gid);
255 2 100       5 POSIX::setgid($gid)
256             or croak(sprintf q{Can't set gid "%s": %s}, $gid, $OS_ERROR);
257             }
258              
259 7 100       550 if (defined(my $umask = $self->umask)) {
260 2         11 $self->_log('Set umask=%s', $umask);
261 2 100       5 POSIX::umask($umask)
262             or croak(sprintf q{Can't set umask "%s": %s}, $umask, $OS_ERROR);
263             }
264              
265 6 100       574 if (defined(my $cwd = $self->cwd)) {
266 2         10 $self->_log('Set cwd=%s', $cwd);
267 2 100       4 POSIX::chdir($cwd)
268             or croak(sprintf q{Can't chdir to "%s": %s}, $cwd, $OS_ERROR);
269             }
270              
271 5         553 return $self;
272             }
273              
274             sub try_kill {
275 3     3 1 142 my ($self, $signal) = @ARG;
276              
277 3 100       6 if (defined(my $pid = $self->pid)) {
278             # parameter order in POSIX.pm
279             # CORE::kill($signal, $pid);
280             # POSIX::kill($pid, $signal);
281 2 100       14 return POSIX::kill($pid, defined $signal ? $signal : q{0});
282             }
283             else {
284 1         117 carp q{No subprocess running};
285 1         73 return;
286             }
287             }
288              
289             sub status {
290 1     1 1 50 my $self = shift;
291 1         3 my $pid = $self->pid;
292              
293 1 50       6 return $pid, POSIX::kill($pid, 0) ? 1 : 0, $self->is_detached;
294             }
295              
296             sub change_program_name {
297 3     3   157 my $self = shift;
298              
299 3         4 my $old_program_name = $PROGRAM_NAME;
300 3         6 my $new_program_name = $self->program_name;
301              
302 3 100       15 if ($new_program_name ne $old_program_name) {
303 2         5 $self->_log(
304             'The program name changed from %s to %s',
305             $old_program_name,
306             $new_program_name,
307             );
308 2         31 $PROGRAM_NAME = $new_program_name;
309             }
310              
311 3         10 return $self;
312             }
313              
314             # private methods
315             sub _log {
316 22     22   211 my $self = shift;
317              
318 22         38 $self->log_callback->($self, @ARG);
319              
320 22         453 return $self;
321             }
322              
323             1;
324              
325             __END__