File Coverage

lib/Proc/DaemonLite.pm
Criterion Covered Total %
statement 33 122 27.0
branch 1 50 2.0
condition 1 18 5.5
subroutine 11 30 36.6
pod 9 11 81.8
total 55 231 23.8


line stmt bran cond sub pod time code
1             ############################################################
2             #
3             # $Id: DaemonLite.pm 537 2006-05-29 19:04:33Z nicolaw $
4             # Proc::DaemonLite - Simple server daemonisation module
5             #
6             # Copyright 2006 Nicola Worthington
7             #
8             # Licensed under the Apache License, Version 2.0 (the "License");
9             # you may not use this file except in compliance with the License.
10             # You may obtain a copy of the License at
11             #
12             # http://www.apache.org/licenses/LICENSE-2.0
13             #
14             # Unless required by applicable law or agreed to in writing, software
15             # distributed under the License is distributed on an "AS IS" BASIS,
16             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17             # See the License for the specific language governing permissions and
18             # limitations under the License.
19             #
20             ############################################################
21              
22             package Proc::DaemonLite;
23             # vim:ts=4:sw=4:tw=78
24              
25 2     2   5512 use strict;
  2         4  
  2         61  
26 2     2   6 use Exporter;
  2         3  
  2         78  
27 2     2   6 use Carp qw(croak cluck carp);
  2         2  
  2         108  
28 2     2   973 use POSIX qw(:signal_h setsid WNOHANG);
  2         10195  
  2         10  
29             #use Carp::Heavy; # Is this really needed?
30 2     2   2344 use File::Basename qw(basename);
  2         3  
  2         103  
31 2     2   855 use IO::File;
  2         12875  
  2         210  
32 2     2   12 use Cwd qw(getcwd);
  2         2  
  2         84  
33 2     2   1354 use Sys::Syslog qw(:DEFAULT setlogsock);
  2         17299  
  2         281  
34              
35 2 50 33 2   11 use constant PIDPATH => -d '/var/run' && -w _ ? '/var/run' : '/var/tmp';
  2         3  
  2         204  
36 2     2   9 use constant FACILITY => 'local0';
  2         2  
  2         79  
37              
38 2     2   7 use vars qw($VERSION $DEBUG @EXPORT @EXPORT_OK %EXPORT_TAGS @ISA %CHILDREN);
  2         3  
  2         2327  
39              
40             $VERSION = '0.00_1' || sprintf('%d', q$Revision: 537 $ =~ /(\d+)/g);
41             $DEBUG = $ENV{DEBUG} ? 1 : 0;
42              
43             @ISA = qw(Exporter);
44             @EXPORT_OK = qw(&init_server &kill_children &launch_child
45             &do_relaunch &log_debug &log_notice &log_warn &log_die &log_info %CHILDREN);
46             @EXPORT = qw(&init_server);
47             %EXPORT_TAGS = (all => \@EXPORT_OK);
48              
49             # These are private
50             my ($pid, $pidfile, $saved_dir, $CWD);
51              
52             sub init_server {
53 0     0 1   my ($user, $group);
54 0           ($pidfile, $user, $group) = @_;
55 0   0       $pidfile ||= _getpidfilename();
56 0           my $fh = _open_pid_file($pidfile);
57 0           _become_daemon();
58 0           print $fh $$;
59 0           close $fh;
60 0           _init_log();
61 0 0 0       _change_privileges($user, $group) if defined $user && defined $group;
62 0           return $pid = $$;
63             }
64              
65             sub _become_daemon {
66 0 0   0     croak "Can't fork" unless defined(my $child = fork);
67 0 0         exit(0) if $child; # parent dies;
68 0           POSIX::setsid(); # become session leader
69 0           open(STDIN, "
70 0           open(STDOUT, ">/dev/null");
71 0           open(STDERR, ">&STDOUT");
72 0           $CWD = Cwd::getcwd; # remember working directory
73 0           chdir '/'; # change working directory
74 0           umask(0); # forget file mode creation mask
75 0           $ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin';
76 0           delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
77 0           $SIG{CHLD} = \&_reap_child;
78             }
79              
80             sub _change_privileges {
81 0     0     my ($user, $group) = @_;
82 0 0         my $uid = getpwnam($user) or die "Can't get uid for $user\n";
83 0 0         my $gid = getgrnam($group) or die "Can't get gid for $group\n";
84 0           $) = "$gid $gid";
85 0           $( = $gid;
86 0           $> = $uid; # change the effective UID (but not the real UID)
87             }
88              
89             sub launch_child {
90 0     0 1   my $callback = shift;
91 0           my $home = shift;
92 0           my $signals = POSIX::SigSet->new(SIGINT, SIGCHLD, SIGTERM, SIGHUP);
93 0           sigprocmask(SIG_BLOCK, $signals); # block inconvenient signals
94 0 0         log_die("Can't fork: $!") unless defined(my $child = fork());
95 0 0         if ($child) {
96 0   0       $CHILDREN{$child} = $callback || 1;
97             } else {
98 0           $SIG{HUP} = $SIG{INT} = $SIG{CHLD} = $SIG{TERM} = 'DEFAULT';
99 0           _prepare_child($home);
100             }
101 0           sigprocmask(SIG_UNBLOCK, $signals); # unblock signals
102 0           return $child;
103             }
104              
105             sub _prepare_child {
106 0     0     my $home = shift;
107 0 0         if ($home) {
108 0           local ($>, $<) = ($<, $>); # become root again (briefly)
109 0 0         chdir($home) || croak "chdir(): $!";
110 0 0         chroot($home) || croak "chroot(): $!";
111             }
112 0           $< = $>; # set real UID to effective UID
113             }
114              
115             sub _reap_child {
116 0     0     while ((my $child = waitpid(-1, WNOHANG)) > 0) {
117 0 0         $CHILDREN{$child}->($child) if ref $CHILDREN{$child} eq 'CODE';
118 0           delete $CHILDREN{$child};
119             }
120             }
121              
122             sub kill_children {
123 0     0 1   kill TERM => keys %CHILDREN;
124              
125             # wait until all the children die
126 0           sleep while %CHILDREN;
127             }
128              
129             sub do_relaunch {
130 0     0 1   $> = $<; # regain privileges
131 0 0         chdir $1 if $CWD =~ m!([./a-zA-z0-9_-]+)!;
132 0 0         croak "bad program name" unless $0 =~ m!([./a-zA-z0-9_-]+)!;
133 0           my $program = $1;
134 0 0         my $port = $1 if $ARGV[0] =~ /(\d+)/;
135 0           unlink($pidfile);
136 0 0         exec('perl', '-T', $program, $port) or croak "Couldn't exec: $!";
137             }
138              
139             sub _init_log {
140 0     0     Sys::Syslog::setlogsock('unix');
141 0           my $basename = File::Basename::basename($0);
142 0           openlog($basename, 'pid', FACILITY);
143 0           $SIG{__WARN__} = \&log_warn;
144 0           $SIG{__DIE__} = \&log_die;
145             }
146              
147 0     0 1   sub log_debug { syslog('debug', _msg(@_)) }
148 0     0 1   sub log_notice { syslog('notice', _msg(@_)) }
149 0     0 1   sub log_warn { syslog('warning', _msg(@_)) }
150 0     0 1   sub log_info { syslog('info', _msg(@_)) }
151              
152             sub log_die {
153 0 0   0 1   Sys::Syslog::syslog('crit', _msg(@_)) unless $^S;
154 0           die @_;
155             }
156              
157             sub _msg {
158 0   0 0     my $msg = join('', @_) || "Something's wrong";
159 0           my ($pack, $filename, $line) = caller(1);
160 0 0         $msg .= " at $filename line $line\n" unless $msg =~ /\n$/;
161 0           $msg;
162             }
163              
164             sub _getpidfilename {
165 0     0     my $basename = File::Basename::basename($0, '.pl');
166 0           return PIDPATH . "/$basename.pid";
167             }
168              
169             sub _open_pid_file {
170 0     0     my $file = shift;
171 0 0         if (-e $file) { # oops. pid file already exists
172 0   0       my $fh = IO::File->new($file) || return;
173 0           my $pid = <$fh>;
174 0 0         croak "Invalid PID file" unless $pid =~ /^(\d+)$/;
175 0 0         croak "Server already running with PID $1" if kill 0 => $1;
176 0           cluck "Removing PID file for defunct server process $pid.\n";
177 0 0 0       croak "Can't unlink PID file $file" unless -w $file && unlink $file;
178             }
179 0 0         return IO::File->new($file, O_WRONLY | O_CREAT | O_EXCL, 0644)
180             or die "Can't create $file: $!\n";
181             }
182              
183             END {
184             $> = $<; # regain privileges
185             unlink $pidfile if defined $pid and $$ == $pid;
186             }
187              
188             sub TRACE {
189 0 0   0 0   return unless $DEBUG;
190 0           warn(shift());
191             }
192              
193             sub DUMP {
194 0 0   0 0   return unless $DEBUG;
195 0           eval {
196 0           require Data::Dumper;
197 0           warn(shift().': '.Data::Dumper::Dumper(shift()));
198             }
199             }
200              
201             1;
202              
203             =pod
204              
205             =head1 NAME
206              
207             Proc::DaemonLite - Simple server daemonisation module
208              
209             =head1 SYNOPSIS
210              
211             use strict;
212             use Proc::DaemonLite qw(:all);
213            
214             my $pid = init_server();
215             log_warn("Forked in to background PID $pid");
216            
217             $SIG{__WARN__} = \&log_warn;
218             $SIG{__DIE__} = \&log_die;
219            
220             for my $cid (1..4) {
221             my $child = launch_child();
222             if ($child == 0) {
223             log_warn("I am child PID $$") while sleep 2;
224             exit;
225             } else {
226             log_warn("Spawned child number $cid, PID $child");
227             }
228             }
229            
230             sleep 20;
231             kill_children();
232              
233             =head1 DESCRIPTION
234              
235             Proc::DaemonLite is a basic server daemonisation module that trys
236             to cater for most basic Perl daemon requirements.
237              
238             The POD for this module is incomplete, as is some of the tidying
239             of the code. It is however fully functional. This is a pre-release
240             in order to reserve the namespace before it becomes unavailable.
241              
242             =head1 EXPORTS
243              
244             By default only I is exported. The export tag I will export
245             the following: I, I,
246             I, I, I, I,
247             I, I, I and I<%CHILDREN>.
248              
249             =head2 init_server()
250              
251             my $pid = init_server($pidfile, $user, $group);
252              
253             =head2 launch_child()
254              
255             my $child_pid = launch_child($callback, $home);
256              
257             =head2 kill_children()
258              
259             kill_children();
260              
261             Terminate all children with a I signal.
262              
263             =head2 do_relaunch()
264              
265             do_relaunch()
266              
267             Attempt to start a new incovation of the current script.
268              
269             =head2 log_debug()
270              
271             log_debug(@messages);
272              
273             =head2 log_info()
274              
275             log_info(@messages);
276              
277             =head2 log_notice()
278              
279             log_notice(@messages);
280              
281             =head2 log_warn()
282              
283             log_warn(@messages);
284              
285             =head2 log_die()
286              
287             log_die(@messages);
288              
289             =head2 %CHILDREN
290              
291             I<%CHILDREN> is a hash of all child processes keyed by PID. Children
292             with registered callbacks will contain a reference to their callback
293             in this hash.
294              
295             =head1 SEE ALSO
296              
297             L, L, L,
298             L, L, L,
299             L, L,
300             L
301              
302             =head1 VERSION
303              
304             $Id: DaemonLite.pm 537 2006-05-29 19:04:33Z nicolaw $
305              
306             =head1 AUTHOR
307              
308             Nicola Worthington
309              
310             L
311              
312             Original code written by Lincoln D. Stein, featured in "Network Programming
313             with Perl". L
314              
315             Released with permission of Lincoln D. Stein.
316              
317             =head1 COPYRIGHT
318              
319             Copyright 2006 Nicola Worthington.
320              
321             This software is licensed under The Apache Software License, Version 2.0.
322              
323             L
324              
325             =cut
326              
327              
328             __END__