Los cambios principales son los siguientes:
launch_child)
y de manejo de la señal de CHLD
para llevar en las claves del hash %CHILDREN los PIDs y en los valores
los callbacks. Esto es manejado por la función reap_child:
90 sub reap_child {
91 while ( (my $child = waitpid(-1,WNOHANG)) > 0) {
92 $CHILDREN{$child}->($child) if ref $CHILDREN{$child} eq 'CODE';
93 delete $CHILDREN{$child};
94 }
95 }
kill_children que termina todas las conexiones activas
97 sub kill_children {
98 kill TERM => keys %CHILDREN;
99 # wait until all the children die
100 sleep while %CHILDREN;
101 }
do_realunch que relanza el servidor después de la recepción de una señal
HUP:
103 sub do_relaunch {
104 $> = $<; # regain privileges
105 chdir $1 if $CWD =~ m!([./a-zA-z0-9_-]+)!;
106 croak "bad program name" unless $0 =~ m!([./a-zA-z0-9_-]+)!;
107 my $program = $1;
108 my $port = $1 if $ARGV[0] =~ /(\d+)/;
109 unlink $pidfile;
110 exec 'perl','-T',$program,$port or croak "Couldn't exec: $!";
111 }
Los procesos hijo por defecto heredan los manejadores de señales establecidos por el proceso
padre. En consecuencia heredan el manejador de HUP el cual rearranca el servidor.
No queremos que cada hijo ejecute este manejadorpues ello daría lugar a múltiples intentos opr los hijos de
relanzar el servidor.
Para ello bastaría con establecer el manejador de señal de HUP para el hijo
a DEFAULT. Existe no obstante el riesgo de que una señal de HUP
llegue en el periodo que va desde la creación del hijo hasta la asignación
73 $SIG{HUP} = $SIG{INT} = $SIG{CHLD} = $SIG{TERM} = 'DEFAULT';
Para ello se deben bloquear las señales antes de hacer el forking para desbloquearlas después tanto en el padre como en el hijo:
64 sub launch_child {
65 my $callback = shift;
66 my $home = shift;
67 my $signals = POSIX::SigSet->new(SIGINT,SIGCHLD,SIGTERM,SIGHUP);
68 sigprocmask(SIG_BLOCK,$signals); # block inconvenient signals
69 log_die("Can't fork: $!") unless defined (my $child = fork());
70 if ($child) {
71 $CHILDREN{$child} = $callback || 1;
72 } else {
73 $SIG{HUP} = $SIG{INT} = $SIG{CHLD} = $SIG{TERM} = 'DEFAULT';
74 prepare_child($home);
75 }
76 sigprocmask(SIG_UNBLOCK,$signals); # unblock signals
77 return $child;
78 }
La función sigprocmask del módulo POSIX lo hace posible. Se llama de la forma:
$result = sigprocmask($operation, $newsigset, [$oldsigset])
sigprocmask permite la manipulación de la máscara de señales del proceso:
una máscatra de bits que controla que señales serán o no recibidas.
Por defecto un proceso recibe todas las señales del sistema oeprativo
pero podemos bloquear algunas modificando la máscara.
Las señales no se descartan sino que se guardan
esperando hasta el momento en el
que el proceso desbloquee la señal.
Las operaciones pueden ser:
SIG_BLOCK Las señales indicadas se añaden a la máscara para bloqueo
SIG_UNBLOCK Las señales indicadas se retiran de la máscara, desbloqueando las señales
SIG_SETMASK La máscara de señales es borrada y reemplazada por el conjunto
de señales inicado en $newsigset
sigprocmask retorna verdadero si tuvo éxito.
Las señales pueden ser creadas y examinadas utilizando la clase POSIX::SigSet .
El módulo importa las funciones POSIX
correspondientes a la etiqueta :signal_h.
1 package Daemon; 2 use strict; 3 our (@EXPORT, @ISA, @EXPORT_OK, $VERSION); 4 5 use POSIX qw(:signal_h setsid WNOHANG); 6 use Carp 'croak','cluck'; 7 use Carp::Heavy; 8 use File::Basename; 9 use IO::File; 10 use Cwd; 11 use Sys::Syslog qw(:DEFAULT setlogsock); 12 require Exporter; 13 14 @EXPORT_OK = qw(init_server prepare_child kill_children 15 launch_child do_relaunch 16 log_debug log_notice log_warn 17 log_die %CHILDREN); 18 @EXPORT = @EXPORT_OK; 19 @ISA = qw(Exporter); 20 $VERSION = '1.00';
En la función become_daemon recordamos en la variable
$CWD el directorio de arranque.
El resto de la rutina no tiene
cambios sustanciales:
40 sub become_daemon {
41 croak "Can't fork" unless defined (my $child = fork);
42 exit 0 if $child; # parent dies;
43 POSIX::setsid(); # become session leader
44 open(STDIN,"</dev/null");
45 open(STDOUT,">/dev/null");
46 open(STDERR,">&STDOUT");
47 $CWD = getcwd; # remember working directory
48 chdir '/'; # change working directory
49 umask(0); # forget file mode creation mask
50 $ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin';
51 delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
52 $SIG{CHLD} = \&reap_child;
53 }
La variable $CWD será utilizada durante
el rearranque del servidor.
103 sub do_relaunch {
104 $> = $<; # regain privileges
105 chdir $1 if $CWD =~ m!([./a-zA-z0-9_-]+)!;
106 croak "bad program name" unless $0 =~ m!([./a-zA-z0-9_-]+)!;
107 my $program = $1;
108 my $port = $1 if $ARGV[0] =~ /(\d+)/;
109 unlink $pidfile;
110 exec 'perl','-T',$program,$port or croak "Couldn't exec: $!";
111 }
root@beowulf:/home/casiano/src/perl/serverwithhup# cat -n Daemon.pm
1 package Daemon;
2 use strict;
3 our (@EXPORT, @ISA, @EXPORT_OK, $VERSION);
4
5 use POSIX qw(:signal_h setsid WNOHANG);
6 use Carp 'croak','cluck';
7 use Carp::Heavy;
8 use File::Basename;
9 use IO::File;
10 use Cwd;
11 use Sys::Syslog qw(:DEFAULT setlogsock);
12 require Exporter;
13
14 @EXPORT_OK = qw(init_server prepare_child kill_children
15 launch_child do_relaunch
16 log_debug log_notice log_warn
17 log_die %CHILDREN);
18 @EXPORT = @EXPORT_OK;
19 @ISA = qw(Exporter);
20 $VERSION = '1.00';
21
22 use constant PIDPATH => '/var/run';
23 use constant FACILITY => 'local0';
24 our %CHILDREN;
25 my ($pid,$pidfile,$saved_dir,$CWD);
26
27 sub init_server {
28 my ($user,$group);
29 ($pidfile,$user,$group) = @_;
30 $pidfile ||= getpidfilename();
31 my $fh = open_pid_file($pidfile);
32 become_daemon();
33 print $fh $$;
34 close $fh;
35 init_log();
36 change_privileges($user,$group) if defined $user && defined $group;
37 return $pid = $$;
38 }
39
40 sub become_daemon {
41 croak "Can't fork" unless defined (my $child = fork);
42 exit 0 if $child; # parent dies;
43 POSIX::setsid(); # become session leader
44 open(STDIN,"</dev/null");
45 open(STDOUT,">/dev/null");
46 open(STDERR,">&STDOUT");
47 $CWD = getcwd; # remember working directory
48 chdir '/'; # change working directory
49 umask(0); # forget file mode creation mask
50 $ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin';
51 delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
52 $SIG{CHLD} = \&reap_child;
53 }
54
55 sub change_privileges {
56 my ($user,$group) = @_;
57 my $uid = getpwnam($user) or die "Can't get uid for $user\n";
58 my $gid = getgrnam($group) or die "Can't get gid for $group\n";
59 $) = "$gid $gid";
60 $( = $gid;
61 $> = $uid; # change the effective UID (but not the real UID)
62 }
63
64 sub launch_child {
65 my $callback = shift;
66 my $home = shift;
67 my $signals = POSIX::SigSet->new(SIGINT,SIGCHLD,SIGTERM,SIGHUP);
68 sigprocmask(SIG_BLOCK,$signals); # block inconvenient signals
69 log_die("Can't fork: $!") unless defined (my $child = fork());
70 if ($child) {
71 $CHILDREN{$child} = $callback || 1;
72 } else {
73 $SIG{HUP} = $SIG{INT} = $SIG{CHLD} = $SIG{TERM} = 'DEFAULT';
74 prepare_child($home);
75 }
76 sigprocmask(SIG_UNBLOCK,$signals); # unblock signals
77 return $child;
78 }
79
80 sub prepare_child {
81 my $home = shift;
82 if ($home) {
83 local($>,$<) = ($<,$>); # become root again (briefly)
84 chdir $home || croak "chdir(): $!";
85 chroot $home || croak "chroot(): $!";
86 }
87 $< = $>; # set real UID to effective UID
88 }
89
90 sub reap_child {
91 while ( (my $child = waitpid(-1,WNOHANG)) > 0) {
92 $CHILDREN{$child}->($child) if ref $CHILDREN{$child} eq 'CODE';
93 delete $CHILDREN{$child};
94 }
95 }
96
97 sub kill_children {
98 kill TERM => keys %CHILDREN;
99 # wait until all the children die
100 sleep while %CHILDREN;
101 }
102
103 sub do_relaunch {
104 $> = $<; # regain privileges
105 chdir $1 if $CWD =~ m!([./a-zA-z0-9_-]+)!;
106 croak "bad program name" unless $0 =~ m!([./a-zA-z0-9_-]+)!;
107 my $program = $1;
108 my $port = $1 if $ARGV[0] =~ /(\d+)/;
109 unlink $pidfile;
110 exec 'perl','-T',$program,$port or croak "Couldn't exec: $!";
111 }
112
113 sub init_log {
114 setlogsock('unix');
115 my $basename = basename($0);
116 openlog($basename,'pid',FACILITY);
117 $SIG{__WARN__} = \&log_warn;
118 $SIG{__DIE__} = \&log_die;
119 }
120
121 sub log_debug { syslog('debug',_msg(@_)) }
122 sub log_notice { syslog('notice',_msg(@_)) }
123 sub log_warn { syslog('warning',_msg(@_)) }
124 sub log_die {
125 syslog('crit',_msg(@_)) unless $^S;
126 die @_;
127 }
128 sub _msg {
129 my $msg = join('',@_) || "Something's wrong";
130 my ($pack,$filename,$line) = caller(1);
131 $msg .= " at $filename line $line\n" unless $msg =~ /\n$/;
132 $msg;
133 }
134
135 sub getpidfilename {
136 my $basename = basename($0,'.pl');
137 return PIDPATH . "/$basename.pid";
138 }
139
140 sub open_pid_file {
141 my $file = shift;
142 if (-e $file) { # oops. pid file already exists
143 my $fh = IO::File->new($file) || return;
144 my $pid = <$fh>;
145 croak "Invalid PID file" unless $pid =~ /^(\d+)$/;
146 croak "Server already running with PID $1" if kill 0 => $1;
147 cluck "Removing PID file for defunct server process $pid.\n";
148 croak"Can't unlink PID file $file" unless -w $file && unlink $file;
149 }
150 return IO::File->new($file,O_WRONLY|O_CREAT|O_EXCL,0644)
151 or die "Can't create $file: $!\n";
152 }
153
154 END {
155 $> = $<; # regain privileges
156 unlink $pidfile if defined $pid and $$ == $pid
157 }
158
159 1;
160 __END__
root@beowulf:/home/casiano/src/perl/serverwithhup# ps -fA | grep eliza nobody 1404 1 0 18:54 ? 00:00:00 /usr/bin/perl -w -T ./eliza_hup.pl root 1406 16664 0 18:54 pts/0 00:00:00 grep eliza root@beowulf:/home/casiano/src/perl/serverwithhup# telnet localhost 1200 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Eliza: Please tell me what's been bothering you. you: bye Eliza: I think you should talk to a REAL analyst. Ciao! Connection closed by foreign host.
