File Coverage

blib/lib/JIP/Daemon.pm
Criterion Covered Total %
statement 102 102 100.0
branch 66 70 94.2
condition 25 27 92.5
subroutine 14 14 100.0
pod 4 4 100.0
total 211 217 97.2


line stmt bran cond sub pod time code
1             package JIP::Daemon;
2              
3 1     1   1281 use 5.006;
  1         2  
  1         30  
4 1     1   9 use strict;
  1         1  
  1         54  
5 1     1   12 use warnings;
  1         1  
  1         27  
6 1     1   423 use JIP::ClassField;
  1         1126  
  1         4  
7 1     1   469 use POSIX ();
  1         4467  
  1         24  
8 1     1   5 use Carp qw(carp croak);
  1         2  
  1         40  
9 1     1   3 use English qw(-no_match_vars);
  1         1  
  1         13  
10              
11             our $VERSION = '0.03';
12              
13             my $maybe_set_subname = sub { $ARG[1]; };
14              
15             # Will be shipping with Perl 5.22
16             eval {
17             require Sub::Util;
18              
19             if (my $set_subname = Sub::Util->can('set_subname')) {
20             $maybe_set_subname = $set_subname;
21             }
22             };
23              
24             my $default_log_callback = sub {
25             my ($self, @params) = @ARG;
26              
27             if (defined(my $logger = $self->logger)) {
28             my $msg;
29              
30             if (@params == 1) {
31             $msg = shift @params;
32             }
33             elsif (@params) {
34             my $format = shift @params;
35             $msg = sprintf $format, @params;
36             }
37              
38             $logger->info($msg) if defined $msg;
39             }
40             };
41              
42             has $_ => (get => q{+}, set => q{-}) for qw(
43             pid
44             uid
45             gid
46             cwd
47             umask
48             logger
49             dry_run
50             is_detached
51             log_callback
52             on_fork_callback
53             );
54              
55             sub new {
56 36     36 1 34114 my ($class, %param) = @ARG;
57              
58             # Perform a trial run with no changes made (foreground if dry_run)
59 36 100 66     119 my $dry_run = (exists $param{'dry_run'} and $param{'dry_run'}) ? 1 : 0;
60              
61 36         32 my $uid;
62 36 100       56 if (exists $param{'uid'}) {
63 4         6 $uid = $param{'uid'};
64              
65 4 100 100     242 croak q{Bad argument "uid"}
66             unless defined $uid and $uid =~ m{^\d+$}x;
67             }
68              
69 34         27 my $gid;
70 34 100       60 if (exists $param{'gid'}) {
71 4         4 $gid = $param{'gid'};
72              
73 4 100 100     186 croak q{Bad argument "gid"}
74             unless defined $gid and $gid =~ m{^\d+$}x;
75             }
76              
77 32         29 my $cwd;
78 32 100       57 if (exists $param{'cwd'}) {
79 4         4 $cwd = $param{'cwd'};
80              
81 4 100 100     208 croak q{Bad argument "cwd"}
82             unless defined $cwd and length $cwd;
83             }
84              
85 30         22 my $umask;
86 30 100       51 if (exists $param{'umask'}) {
87 4         6 $umask = $param{'umask'};
88              
89 4 100 100     204 croak q{Bad argument "umask"}
90             unless defined $umask and length $umask;
91             }
92              
93 28         23 my $logger;
94 28 100       44 if (exists $param{'logger'}) {
95 3         3 $logger = $param{'logger'};
96              
97 3 100 100     183 croak q{Bad argument "logger"}
      66        
98             unless defined $logger and ref $logger and $logger->can('info');
99             }
100              
101 26         69 my $log_callback;
102 26 100       40 if (exists $param{'log_callback'}) {
103 6         7 $log_callback = $param{'log_callback'};
104              
105 6 100 100     194 croak q{Bad argument "log_callback"}
106             unless defined $log_callback and ref($log_callback) eq 'CODE';
107              
108 4         6 $log_callback = $maybe_set_subname->('custom_log_callback', $log_callback);
109             }
110             else {
111 20         27 $log_callback = $maybe_set_subname->('default_log_callback', $default_log_callback);
112             }
113              
114 24         20 my $on_fork_callback;
115 24 100       40 if (exists $param{'on_fork_callback'}) {
116 3         3 $on_fork_callback = $param{'on_fork_callback'};
117              
118 3 100 100     176 croak q{Bad argument "on_fork_callback"}
119             unless defined $on_fork_callback and ref($on_fork_callback) eq 'CODE';
120              
121 1         1 $on_fork_callback = $maybe_set_subname->('on_fork_callback', $on_fork_callback);
122             }
123              
124 22         79 return bless({}, $class)
125             ->_set_dry_run($dry_run)
126             ->_set_uid($uid)
127             ->_set_gid($gid)
128             ->_set_cwd($cwd)
129             ->_set_umask($umask)
130             ->_set_logger($logger)
131             ->_set_log_callback($log_callback)
132             ->_set_on_fork_callback($on_fork_callback)
133             ->_set_pid($PROCESS_ID)
134             ->_set_is_detached(0);
135             }
136              
137             sub daemonize {
138 9     9 1 1994 my $self = shift;
139              
140 9 100       17 return $self if $self->is_detached;
141              
142             # Fork and kill parent
143 7 100       30 if (not $self->dry_run) {
144 5         19 $self->_log('Daemonizing the process');
145              
146 5         9 my $pid = POSIX::fork(); # returns child pid to the parent and 0 to the child
147              
148 5 100       1212 croak q{Can't fork} if not defined $pid;
149              
150             # fork returned 0, so this branch is the child
151 4 100       8 if ($pid == 0) {
152 2 100       5 POSIX::setsid()
153             or croak(sprintf q{Can't start a new session: %s}, $OS_ERROR);
154              
155 1         221 $self->reopen_std;
156              
157 1         200 $self->_set_pid(POSIX::getpid())->_set_is_detached(1);
158             }
159              
160             # this branch is the parent
161             else {
162 2         5 $self->_log('Spawned process pid=%d. Parent exiting', $pid);
163 2         4 $self->_set_pid($pid)->_set_is_detached(1);
164              
165 2 100       13 if (defined(my $cb = $self->on_fork_callback)) {
166 1         6 $cb->($self);
167             }
168              
169 2         523 POSIX::exit(0);
170             }
171             }
172             else {
173 2         12 $self->_set_pid($PROCESS_ID);
174             }
175              
176 5         890 return $self->drop_privileges;
177             }
178              
179             sub reopen_std {
180 1     1   26 my $self = shift;
181              
182 1 50       26 open STDIN, '
183             or croak(sprintf q{Can't reopen STDIN: %s}, $OS_ERROR);
184 1 50       13 open STDOUT, '>/dev/null'
185             or croak(sprintf q{Can't reopen STDOUT: %s}, $OS_ERROR);
186 1 50       11 open STDERR, '>/dev/null'
187             or croak(sprintf q{Can't reopen STDERR: %s}, $OS_ERROR);
188              
189 1         4 return $self;
190             }
191              
192             sub drop_privileges {
193 9     9   192 my $self = shift;
194              
195 9 100       16 if (defined(my $uid = $self->uid)) {
196 2         10 $self->_log('Set uid=%d', $uid);
197 2 100       4 POSIX::setuid($uid)
198             or croak(sprintf q{Can't set uid "%s": %s}, $uid, $OS_ERROR);
199             }
200              
201 8 100       327 if (defined(my $gid = $self->gid)) {
202 2         7 $self->_log('Set gid=%d', $gid);
203 2 100       4 POSIX::setgid($gid)
204             or croak(sprintf q{Can't set gid "%s": %s}, $gid, $OS_ERROR);
205             }
206              
207 7 100       317 if (defined(my $umask = $self->umask)) {
208 2         9 $self->_log('Set umask=%s', $umask);
209 2 100       5 POSIX::umask($umask)
210             or croak(sprintf q{Can't set umask "%s": %s}, $umask, $OS_ERROR);
211             }
212              
213 6 100       327 if (defined(my $cwd = $self->cwd)) {
214 2         9 $self->_log('Set cwd=%s', $cwd);
215 2 100       4 POSIX::chdir($cwd)
216             or croak(sprintf q{Can't chdir to "%s": %s}, $cwd, $OS_ERROR);
217             }
218              
219 5         392 return $self;
220             }
221              
222             sub try_kill {
223 3     3 1 70 my ($self, $signal) = @ARG;
224              
225 3 100       6 if (defined(my $pid = $self->pid)) {
226             # parameter order in POSIX.pm
227             # CORE::kill($signal, $pid);
228             # POSIX::kill($pid, $signal);
229 2 100       13 return POSIX::kill($pid, defined $signal ? $signal : q{0});
230             }
231             else {
232 1         133 carp q{No subprocess running};
233 1         74 return;
234             }
235             }
236              
237             sub status {
238 1     1 1 27 my $self = shift;
239 1         3 my $pid = $self->pid;
240              
241 1 50       5 return $pid, POSIX::kill($pid, 0) ? 1 : 0, $self->is_detached;
242             }
243              
244             # private methods
245             sub _log {
246 20     20   129 my $self = shift;
247              
248 20         32 $self->log_callback->($self, @ARG);
249              
250 20         339 return $self;
251             }
252              
253             1;
254              
255             __END__