Manejo de Señales

A menudo es necesario reconfigurar un servicio que está en ejecución y es conveniente hacerlo sin detenerlo. En UNIX existe un convenio que es utilizar la señal HUP para reiniciar un servidor. Esto es, según este convenio un servidor que recibe la señal de HUP vuelve a cargar y analizar sus ficheros de configuración y se reconfigura de acuerdo a los cambios.

Se elegió la señal de HUP dado que esta no es normalmente recibida por un daemon ya que este se ha disociado de la terminal de control.

Ahora instalamos manejadores para las señales de TERM, HUP e INT:

14  # signal handler for child die events
15  $SIG{TERM} = $SIG{INT} = \&do_term;
16  $SIG{HUP}  = \&do_hup;

El puerto se lee desde línea de comandos. No se retira (no se usa shift) pues la subrutina do_relaunch puede que necesite volver a accederla mas tarde.

18  my $port = $ARGV[0] || PORT;

La inicialización del socket y el bucle de espera y manejo de las conexiones registra un cambio: en vez de llamar directamente a fork se usa la función launch_child, definida en el módulo Daemon.

19  my $listen_socket = IO::Socket::INET->new(LocalPort => $port,
20                                            Listen    => 20,
21                                            Proto     => 'tcp',
22                                            Reuse     => 1);
23  die "Can't create a listening socket: $@" unless $listen_socket;
24  my $pid = init_server(PIDFILE,USER,GROUP,$port);
25
26  log_notice "Server accepting connections on port $port\n";
27
28  while (my $connection = $listen_socket->accept) {
29    my $host = $connection->peerhost;
30    my $child = launch_child(undef,ELIZA_HOME);
31    if ($child == 0) {
32      $listen_socket->close;
33      log_notice("Accepting a connection from $host\n");
34      interact($connection);
35      log_notice("Connection from $host finished\n");
36      exit 0;
37    }
38    $connection->close;
39  }

La subrutina launch_child hace el fork, llama a chroot y renuncia a los privilegios de root (mediante la llamada a prepare_child).

  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  }
Además launch_child almacena en el hash %CHILDREN un callback que será llamado cuando el hijo termine y que puede ser definido por el programador. Las claves de este hash serán utilizadas para eliminar a los hijos cuando se reciba una señal de HUP o de terminación.
  97  sub kill_children {
  98    kill TERM => keys %CHILDREN;
  99    # wait until all the children die
 100    sleep while %CHILDREN;
 101  }

El manejador do:term se encarga de al señal TERM. Registra un mensaje al sistema de log, llama a la subrutina kill_children y finaliza el programa.

51  sub do_term {
52    log_notice("TERM signal received, terminating children...\n");
53    kill_children();
54    exit 0;
55  }

El manejador do_hup se encarga de la señal de HUP.

57  sub do_hup {
58    log_notice("HUP signal received, reinitializing...\n");
59    log_notice("Closing listen socket...\n");
60    close $listen_socket;
61    log_notice("Terminating children...\n");
62    kill_children;
63    log_notice("Trying to relaunch...\n");
64    do_relaunch();
65    log_die("Relaunch failed. Died");
66  }
La subrutina do_relaunch intentará relanzar el programa y si tiene éxito no retorna.

El programa principal contiene un bloque END que emite un mensaje de log.

82  END {
83    log_notice("Server exiting normally\n") if (defined($pid) && $$ == $pid);
84  }

El Programa Principal: Versión con Log, Privilegios, chroot, Taint y Rearranque

casiano@beowulf:~/src/perl/serverwithhup$ cat -n eliza_hup.pl
 1  #!/usr/bin/perl -w -T
 2  use strict;
 3  use lib '.';
 4  use Chatbot::Eliza;
 5  use IO::Socket;
 6  use Daemon;
 7
 8  use constant PORT      => 1002;
 9  use constant PIDFILE   => '/var/run/eliza_hup.pid';
10  use constant USER      => 'nobody';
11  use constant GROUP     => 'nogroup';
12  use constant ELIZA_HOME => '/var/www/';
13
14  # signal handler for child die events
15  $SIG{TERM} = $SIG{INT} = \&do_term;
16  $SIG{HUP}  = \&do_hup;
17
18  my $port = $ARGV[0] || PORT;
19  my $listen_socket = IO::Socket::INET->new(LocalPort => $port,
20                                            Listen    => 20,
21                                            Proto     => 'tcp',
22                                            Reuse     => 1);
23  die "Can't create a listening socket: $@" unless $listen_socket;
24  my $pid = init_server(PIDFILE,USER,GROUP,$port);
25
26  log_notice "Server accepting connections on port $port\n";
27
28  while (my $connection = $listen_socket->accept) {
29    my $host = $connection->peerhost;
30    my $child = launch_child(undef,ELIZA_HOME);
31    if ($child == 0) {
32      $listen_socket->close;
33      log_notice("Accepting a connection from $host\n");
34      interact($connection);
35      log_notice("Connection from $host finished\n");
36      exit 0;
37    }
38    $connection->close;
39  }
40
41  sub interact {
42    my $sock = shift;
43    STDIN->fdopen($sock,"r")  or die "Can't reopen STDIN: $!";
44    STDOUT->fdopen($sock,"w") or die "Can't reopen STDOUT: $!";
45    STDERR->fdopen($sock,"w") or die "Can't reopen STDERR: $!";
46    $| = 1;
47    my $bot = Chatbot::Eliza->new;
48    $bot->command_interface;
49  }
50
51  sub do_term {
52    log_notice("TERM signal received, terminating children...\n");
53    kill_children();
54    exit 0;
55  }
56
57  sub do_hup {
58    log_notice("HUP signal received, reinitializing...\n");
59    log_notice("Closing listen socket...\n");
60    close $listen_socket;
61    log_notice("Terminating children...\n");
62    kill_children;
63    log_notice("Trying to relaunch...\n");
64    do_relaunch();
65    log_die("Relaunch failed. Died");
66  }
67
68  {
69    no warnings 'redefine';
70
71    sub Chatbot::Eliza::_testquit {
72      my ($self,$string) = @_;
73      return 1 unless defined $string;  # test for EOF
74      foreach (@{$self->{quit}}) { return 1 if $string =~ /\b$_\b/i };
75    }
76
77    # prevents an annoying warning from Chatbot::Eliza module
78    sub Chatbot::Eliza::DESTROY { }
79  }
80
81
82  END {
83    log_notice("Server exiting normally\n") if (defined($pid) && $$ == $pid);
84  }



Subsecciones
Casiano Rodríguez León
2010-03-22
ink $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__

Ejecución

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.



Subsecciones
Casiano Rodríguez León
2010-05-05
BORDER="0" SRC="pbp.jpeg" ALT="pbp">google code project hostingintro a PerlModern Perlblogsgoogleetsiiullpcgull
Sig: Logs con warn y Sup: Como Convertirse en un Ant: El Módulo Perl Log::Log4Perl
Casiano Rodríguez León
2011-03-19