File Coverage

blib/lib/Net/Server/Daemonize.pm
Criterion Covered Total %
statement 12 110 10.9
branch 0 70 0.0
condition 0 17 0.0
subroutine 4 17 23.5
pod 10 12 83.3
total 26 226 11.5


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::Daemonize - Daemonization utilities.
4             #
5             # Copyright (C) 2001-2022
6             #
7             # Jeremy Howard
8             # j+daemonize@howard.fm
9             #
10             # Paul Seamons
11             #
12             # This package may be distributed under the terms of either the
13             # GNU General Public License
14             # or the
15             # Perl Artistic License
16             #
17             # All rights reserved.
18             #
19             ################################################################
20              
21             package Net::Server::Daemonize;
22              
23 54     54   415 use strict;
  54         140  
  54         3109  
24 54     54   342 use base qw(Exporter);
  54         114  
  54         13743  
25 54     54   428 use Carp qw(croak);
  54         149  
  54         3695  
26 54     54   354 use POSIX qw(SIGINT SIG_BLOCK SIG_UNBLOCK);
  54         107  
  54         589  
27              
28             our $VERSION = "0.06";
29              
30             our @EXPORT_OK = qw(check_pid_file create_pid_file unlink_pid_file
31             is_root_user get_uid get_gid set_uid set_gid
32             set_user safe_fork daemonize);
33              
34             ###----------------------------------------------------------------###
35              
36             ### check for existence of pid_file
37             ### if the file exists, check for a running process
38             sub check_pid_file ($) {
39 0     0 1   my $pid_file = shift;
40 0 0 0       return 1 if ! -e $pid_file or ! -s $pid_file && -M _ > 0.01;
      0        
41              
42 0 0         open my $fh, '<', $pid_file or croak "$pid_file: Couldn't open existent pid_file [$!]";
43 0   0       my $current_pid = <$fh> || "";
44 0           close $fh;
45 0 0         $current_pid = ($current_pid =~ /^(\d{1,10})/) ? $1 : croak "$pid_file: Couldn't find pid in existent pid_file";
46              
47 0           my $exists;
48 0 0         if ($$ == $current_pid) {
    0          
    0          
49 0           warn "Pid_file created by this same process. Doing nothing.\n";
50 0           return 1;
51             } elsif (-d "/proc/$$") { # try a proc file system
52 0           $exists = -e "/proc/$current_pid";
53             } elsif (kill 0, $current_pid) {
54 0           $exists = 1;
55             }
56 0 0         croak "Pid_file already exists for running process ($current_pid)... aborting"
57             if $exists;
58              
59             # remove the pid_file
60 0           warn "Pid_file \"$pid_file\" already exists. Overwriting!\n";
61 0   0       unlink $pid_file || croak "Couldn't remove pid_file \"$pid_file\" [$!]";
62 0           return 1;
63             }
64              
65             ### actually create the pid_file, calls check_pid_file
66             ### before proceeding
67             sub create_pid_file ($) {
68 0     0 0   my $pid_file = shift;
69              
70 0           check_pid_file($pid_file);
71              
72 0 0         open my $fh, '>', $pid_file or croak "Couldn't open pid file \"$pid_file\" [$!]";
73 0           print $fh "$$\n";
74 0           close $fh;
75              
76 0 0         die "Pid_file \"$pid_file\" not created.\n" if ! -e $pid_file;
77 0           return 1;
78             }
79              
80             ### Allow for safe removal of the pid_file.
81             ### Make sure this process owns it.
82             sub unlink_pid_file ($) {
83 0     0 1   my $pid_file = shift;
84 0 0         return 1 if ! -e $pid_file; # no pid_file = return success
85              
86 0 0         open my $fh, '<', $pid_file or croak "$pid_file: Couldn't open existent pid_file [$!]"; # slight race
87 0           my $current_pid = <$fh>;
88 0           close $fh;
89 0           chomp $current_pid;
90              
91 0 0         croak "Process $$ doesn't own pid_file \"$pid_file\" so can't remove it"
92             if $current_pid ne $$;
93              
94 0 0         unlink($pid_file) || die "$pid_file: Couldn't unlink pid_file [$!]\n";
95 0           return 1;
96             }
97              
98             ###----------------------------------------------------------------###
99              
100             sub is_root_user () {
101 0     0 1   my $id = get_uid('root');
102 0   0       return ! defined($id) || $< == $id || $> == $id;
103             }
104              
105             ### get the uid for the passed user
106             sub get_uid ($) {
107 0     0 1   my $user = shift;
108 0 0         my $uid = ($user =~ /^(\d+)$/) ? $1 : getpwnam($user);
109 0 0         croak "No such user \"$user\"" unless defined $uid;
110 0           return $uid;
111             }
112              
113             ### get all of the gids that this group is (space delimited)
114             sub get_gid {
115 0     0 1   my @gid;
116              
117 0           foreach my $group ( split( /[, ]+/, join(" ",@_) ) ){
118 0 0         if( $group =~ /^\d+$/ ){
119 0           push @gid, $group;
120             }else{
121 0           my $id = getgrnam($group);
122 0 0         croak "No such group \"$group\"" unless defined $id;
123 0           push @gid, $id;
124             }
125             }
126              
127 0 0         croak "No group found in arguments" unless @gid;
128 0           return join(" ", $gid[0], @gid);
129             }
130              
131             ### change the process to run as this uid
132             sub set_uid {
133 0     0 1   my $uid = get_uid(shift());
134              
135 0           POSIX::setuid($uid);
136 0 0 0       if ($< != $uid || $> != $uid) { # check $> also (rt #21262)
137 0           $< = $> = $uid; # try again - needed by some 5.8.0 linux systems (rt #13450)
138 0 0         if ($< != $uid) {
139 0           die "Couldn't become uid \"$uid\": $!\n";
140             }
141             }
142              
143 0           return 1;
144             }
145              
146             ### change the process to run as this gid(s)
147             ### multiple groups must be space or comma delimited
148             sub set_gid {
149 0     0 1   my $gids = get_gid(@_);
150 0           my $gid = (split /\s+/, $gids)[0];
151 0           eval { $) = $gids }; # store all the gids - this is really sort of optional
  0            
152              
153 0           POSIX::setgid($gid);
154 0 0         if (! grep {$gid == $_} split /\s+/, $() { # look for any valid id in the list
  0            
155 0           die "Couldn't become gid \"$gid\": $!\n";
156             }
157              
158 0           return 1;
159             }
160              
161             ### backward compatibility sub
162             sub set_user {
163 0     0 1   my ($user, @group) = @_;
164 0 0         set_gid(@group) || return undef;
165 0 0         set_uid($user) || return undef;
166 0           return 1;
167             }
168              
169             ###----------------------------------------------------------------###
170              
171             ### routine to protect process during fork
172             sub safe_fork () {
173              
174             # block signal for fork
175 0     0 1   my $sigset = POSIX::SigSet->new(SIGINT);
176 0 0         POSIX::sigprocmask(SIG_BLOCK, $sigset) or croak "Can't block SIGINT for fork: [$!]";
177              
178 0           my $pid = fork;
179 0 0         die "Couldn't fork: [$!]" if ! defined $pid;
180              
181 0           $SIG{'INT'} = 'DEFAULT'; # make SIGINT kill us as it did before
182              
183 0 0         POSIX::sigprocmask(SIG_UNBLOCK, $sigset) or croak "Can't unblock SIGINT for fork: [$!]";
184              
185 0           return $pid;
186             }
187              
188             ###----------------------------------------------------------------###
189              
190             ### routine to completely dissociate from terminal process.
191             sub daemonize ($$$) {
192 0     0 1   my ($user, $group, $pid_file) = @_;
193              
194 0 0         check_pid_file($pid_file) if defined $pid_file;
195              
196 0           my $uid = get_uid($user);
197 0           my $gid = get_gid($group); # returns list of groups
198 0           $gid = (split /[\s,]+/, $gid)[0];
199              
200 0           my $pid = safe_fork();
201              
202 0 0         exit(0) if $pid; # exit parent
203              
204             # child
205 0 0         create_pid_file($pid_file) if defined $pid_file;
206 0 0         chown($uid, $gid, $pid_file) if defined $pid_file;
207              
208 0           set_user($uid, $gid);
209              
210 0 0         open STDIN, '<', '/dev/null' or die "Can't open STDIN from /dev/null: [$!]\n";
211 0 0         open STDOUT, '>', '/dev/null' or die "Can't open STDOUT to /dev/null: [$!]\n";
212 0 0         open STDERR, '>&STDOUT' or die "Can't open STDERR to STDOUT: [$!]\n";
213              
214             ### does this mean to be chroot ?
215 0 0         chdir '/' or croak "Can't chdir to \"/\": [$!]";
216              
217 0           POSIX::setsid(); # Turn process into session leader, and ensure no controlling terminal
218              
219             ### install a signal handler to make sure SIGINT's remove our pid_file
220 0 0   0     $SIG{'INT'} = sub { HUNTSMAN($pid_file) } if defined $pid_file;
  0            
221 0           return 1;
222             }
223              
224             ### SIGINT routine that will remove the pid_file
225             sub HUNTSMAN {
226 0     0 0   my $path = shift;
227 0           unlink $path;
228              
229 0           eval {
230 0           require Unix::Syslog;
231 0           Unix::Syslog::syslog(Unix::Syslog::LOG_ERR(), "Exiting on INT signal.");
232             };
233              
234 0           exit;
235             }
236              
237              
238             1;
239              
240             __END__