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
2012-02-29