File Coverage

blib/lib/JIP/Daemon.pm
Criterion Covered Total %
statement 97 97 100.0
branch 60 64 93.7
condition 19 21 90.4
subroutine 14 14 100.0
pod 4 4 100.0
total 194 200 97.0


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