File Coverage

blib/lib/PkgForge/Daemon.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package PkgForge::Daemon; # -*- perl -*-
2 2     2   3067 use strict;
  2         4  
  2         79  
3 2     2   12 use warnings;
  2         3  
  2         109  
4              
5             # $Id: Daemon.pm.in 16191 2011-02-28 19:51:24Z squinney@INF.ED.AC.UK $
6             # $Source:$
7             # $Revision: 16191 $
8             # $HeadURL: https://svn.lcfg.org/svn/source/tags/PkgForge-Server/PkgForge_Server_1_1_10/lib/PkgForge/Daemon.pm.in $
9             # $Date: 2011-02-28 19:51:24 +0000 (Mon, 28 Feb 2011) $
10              
11             our $VERSION = '1.1.10';
12              
13 2     2   826 use English qw(-no_match_vars);
  2         2469  
  2         17  
14 2     2   969 use File::Spec ();
  2         4  
  2         37  
15 2     2   905 use IO::File ();
  2         11456  
  2         50  
16 2     2   1038 use POSIX qw(SIGINT SIGTERM SIGKILL);
  2         7644  
  2         16  
17              
18 2     2   1922 use Moose;
  2         7  
  2         16  
19 2     2   16448 use MooseX::Types::Moose qw(Bool Int Str);
  2         77712  
  2         24  
20              
21 2     2   11949 use PkgForge::PidFile;
  2         7  
  2         86  
22 2     2   1020 use PkgForge::Types qw(UID Octal);
  0            
  0            
23              
24             with 'MooseX::Getopt';
25              
26             has 'pidfile' => (
27             is => 'ro',
28             isa => 'PkgForge::PidFile',
29             coerce => 1,
30             builder => 'init_pidfile',
31             documentation => 'The PID file',
32             );
33              
34             sub init_pidfile {
35             my ($self) = @_;
36             return PkgForge::PidFile->new( basedir => $self->pidfile_dir,
37             progname => $self->progname );
38             }
39              
40             has 'pidfile_dir' => (
41             is => 'ro',
42             isa => Str,
43             default => '/var/run/pkgforge',
44             documentation => 'The directory in which PID files should be stored',
45             );
46              
47             has 'workdir' => (
48             is => 'ro',
49             isa => Str,
50             default => q{/},
51             documentation => 'The directory within which to run',
52             );
53              
54             has 'umask' => (
55             is => 'ro',
56             isa => Octal,
57             default => 0,
58             documentation => 'The umask to set before starting',
59             );
60              
61             has 'chroot' => (
62             is => 'ro',
63             isa => Str,
64             documentation => 'chroot during startup',
65             );
66              
67             has 'progname' => (
68             is => 'rw',
69             isa => Str,
70             lazy => 1,
71             default => sub { return (File::Spec->splitpath( $PROGRAM_NAME ) )[-1] },
72             documentation => 'The name of the daemon',
73             );
74              
75             has 'stop_timeout' => (
76             is => 'rw',
77             isa => Int,
78             default => 120,
79             documentation => 'The time to wait (in secs) for stop to finish',
80             );
81              
82             has 'background' => (
83             is => 'ro',
84             isa => Bool,
85             default => 1,
86             documentation => 'Background the process',
87             );
88              
89             no Moose;
90             __PACKAGE__->meta->make_immutable;
91              
92             sub run {
93             my ($self) = @_;
94              
95             my ($command) = @{$self->extra_argv};
96             defined $command or die "No command specified\n";
97              
98             if ( $command =~ m{^
99             (start
100             |stop
101             |restart
102             |status)
103             $}x ) {
104             $self->$command();
105             } else {
106             die "Unsupported command: $command\n";
107             }
108              
109             return;
110             }
111              
112             sub shutdown {
113             my ($self) = @_;
114              
115             exit 0;
116             }
117              
118             sub setup_signals {
119             my ($self) = @_;
120              
121             $SIG{INT} = $SIG{TERM} = sub { $self->shutdown };
122              
123             return;
124             }
125              
126             sub start {
127             my ($self) = @_;
128              
129             if ( $self->pidfile->does_file_exist ) {
130             if ( $self->pidfile->is_running ) {
131             my $pid = $self->pidfile->pid;
132             die "daemon process ($pid) already running\n";
133             } else {
134             $self->pidfile->remove;
135             }
136             }
137              
138             $self->setup_signals;
139              
140             my $workdir = $self->workdir;
141             chdir $workdir or die "Could not chdir to '$workdir': [$OS_ERROR]\n";
142              
143             my $umask = $self->umask;
144             umask $umask or die "Could not set umask to '$umask: [$OS_ERROR]\n";
145              
146             if ( $self->background ) {
147             my $process = eval { $self->daemonize() };
148             if ( !$process || $EVAL_ERROR ) {
149             # errors...
150             $self->pidfile->remove;
151             die "Failed to daemonize: $@\n";
152             }
153             elsif ( $process eq 'parent' ) {
154             exit 0;
155             }
156             }
157              
158             $self->pidfile->pid($PROCESS_ID);
159             $self->pidfile->store();
160              
161             return $self->pidfile->pid;
162             }
163              
164             sub stop {
165             my ($self) = @_;
166              
167             if ( $self->pidfile->does_file_exist && $self->pidfile->is_running ) {
168             my $pid = $self->pidfile->pid;
169              
170             if ( $pid eq $PROCESS_ID ) {
171             die "$pid is us! Cannot commit suicide.\n";
172             }
173              
174             my $killed;
175             for my $signal ( SIGINT, SIGTERM, SIGKILL ) {
176             my $timeout = $self->stop_timeout;
177             kill $signal, $pid;
178              
179             while ( $timeout > 0 ) {
180             if ( ! kill 0, $pid ) {
181             $killed = $signal;
182             last;
183             }
184             $timeout--;
185             sleep 1;
186             }
187              
188             last if $killed;
189             }
190              
191             # Ensure we are tidy
192              
193             if ( $killed && $self->pidfile->does_file_exist ) {
194             $self->pidfile->clear_pid; # force retrieval from file
195             my $pid2 = $self->pidfile->pid;
196             if ( $pid == $pid2 ) { # check it is not a new process
197             $self->pidfile->remove;
198             }
199             }
200              
201             if ( !$killed ) {
202             my $progname = $self->progname;
203             die "Failed to kill $progname PID $pid\n";
204             }
205             }
206             else {
207             print "Nothing to kill\n";
208             }
209              
210             return;
211             }
212              
213             sub restart {
214             my ($self) = @_;
215              
216             $self->stop();
217              
218             $self->start();
219              
220             return;
221             }
222              
223             sub status_message {
224             my ( $self, $pid ) = @_;
225              
226             my $progname = $self->progname;
227             if ($pid) {
228             print "$progname is running with PID $pid\n";
229             } else {
230             print "$progname is not running\n";
231             }
232              
233             return;
234             }
235              
236             sub status {
237             my ($self) = @_;
238              
239             my $pid;
240             if ( $self->pidfile->does_file_exist && $self->pidfile->is_running ) {
241             $pid = $self->pidfile->pid;
242             }
243              
244             return $self->status_message($pid);
245             }
246              
247             sub daemonize {
248             my ($self) = @_;
249              
250             my $pid_c = fork(); # Parent spawns Child
251             die "Cannot fork: $!\n" if !defined $pid_c;
252             if ($pid_c)
253             {
254             # ==== Parent ====
255             waitpid($pid_c, 0); # Zombies not allowed
256             return 'parent'; # No attachment to grand-child
257             }
258              
259             # ==== Child ====
260             my $pid_gc = fork(); # Child spawns Grand-Child
261             die "Cannot fork: $!\n" if !defined $pid_gc;
262             exit (0) if $pid_gc; # Child exits immediately
263              
264             # ==== Grand-Child ====
265             # Grand-Child continues, now parented by init.
266              
267             # setpgrp MUST be BEFORE setsid
268              
269             setpgrp(0,0) or die "Cannot set process group: $!\n";
270              
271             POSIX::setsid() or die "Cannot start a new session: $!\n";
272              
273             my $chroot = $self->chroot;
274             if ( $EUID == 0 && $chroot ) {
275             chroot $chroot or die "Could not chroot to '$chroot': [$OS_ERROR]\n";
276             }
277              
278             open( STDIN, '+>', '/dev/null' )
279             or die "Could not redirect STDIN to /dev/null: [$OS_ERROR]\n";
280              
281             open( STDOUT, '+>', '/dev/null' )
282             or die "Could not redirect STDOUT to /dev/null: [$OS_ERROR]\n";
283              
284             open( STDERR, '+>', '/dev/null' )
285             or die "Could not redirect STDERR to /dev/null: [$OS_ERROR]\n";
286              
287             return 'child';
288             }
289              
290             1;
291             __END__
292