Vete al infierno. Todo sobre los demonios
GNU/LINUX
Vete al infierno. Todo sobre los demonios
2020-04-14
Por
Richi C. Poweri

Los demonios (o daemons) son programas que se ejecutan en segundo plano y se encargan de muchas de las maravillas que nuestras máquina GNU/Linux son capaces de hacer. En este artículo vamos a contaros como escribir vuestros propios demonios y todos sus secretos.

Como os acabamos de adelantar, en este artículo vamos a desvelar todos los secretos de los demonios o daemons y, como no, lo haremos escribiendo nuestro propio demonio. En el proceso, os iremos contando cada paso que es necesario realizar para crear un demonio como dios manda :).

El programa base

Comenzaremos escribiendo el programa base que queremos convertir en un demonio, y lo iremos ampliando poco a poca hasta convertir en todo un demonio hecho y derecho.

La mayoría de los demonios, siguiendo la filosofía UNIX, son programas que realizar una sola cosa, pero la hacen muy bien. En general, esa cosa que hacen se ejecuta en un bucle infinito. El demonio ejecutará una y otra vez los mismos comandos hasta que muera. Si, es una existencia un tanto monótana... pero después de todo no son más que programas.

Para nuestro ejemplo, vamos a comenzar con un programa que realmente no hace nada, pero que sigue esta idea. Aquí tenéis el código:

#include <unistd.h>

int
main (int argc, char *argv[])
{

  while (1) {
    // Aquí haremos alguna cosa
    usleep (10000);
  }
}

Si. Exacto. Como os habíamos anticipado... un bucle infinito. Si compilamos y ejecutamos este programa, nuestra línea de comandos quedará bloqueada, esperando a que el programa termine, lo cual nunca sucederá ya que nuestro programa es un bucle infinito. Así que para recobrar el control de nuestra línea de comandos debemos matar el proceso utilizando la combinación de teclas CTRL+C

occams@razor$ ./daemon01
^C
occams@razor$$

Pasando a segundo plano

Parece obvio que lo primero que debemos hacer es conseguir que nuestro demonio pase a segundo plano nada más arrancar. Podemos hacer esto lanzando el programa seguido del caracter &, pero eso tiene algunos inconvenientes y no tiene mucho sentido ya que nuestro demonio siempre debe ejecutarse en segundo plano.... sino, no sería un demonio.

La forma de pasar un proceso a segundo plano es crear un proceso hijo utilizando la llamada al sistema fork e, inmediatamente después, terminar el proceso padre.

Al crear un nuevo proceso y terminar el proceso padre, lo que estamos haciendo es crear un proceso huérfano. Los procesos huérfanos se asignan a un padre de acogida :) ... que para el caso de un sistema UNIX suele ser el proceso init con PID 1. En caso de estar en un sistema con entorno gráfico esto puede ser diferente y dependerá de como el sistema maneje las sesiones. Por ejemplo, en Ubuntu es systemd el proceso que pasará a ser padre de acogida.

Veamos los cambios que debemos hacer al código y volvamos sobre esto un poco más tarde.

#include <stdlib.h>  // For EXIT_*
#include <unistd.h>

int
main (int argc, char *argv[])
{
  pid_t pid;

  /* Poner el proceso en background */
  if ((pid = fork ()) < 0) {
    exit (EXIT_FAILURE);
  }
  else if (pid > 0) // El padre debe morir
    exit (EXIT_SUCCESS);

  while (1) {
    // Do something
    usleep (10000);
  }
}

Como podéis ver, lo único que estamos haciendo es ejecutar la llamada al sistema fork para crear un nuevo proceso y luego destruir el proceso padre (aquel que recibe el PID del hijo).

Si compilamos y ejecutamos este programa (al que hemos llamado daemon02) veremos como el programa parece no hacer nada y se nos devuelve el control de la línea de comandos inmediatamente.

$ make daemon02
$ ./daemon02
$ 

Si miramos atentamente... podremos ver que nuestro programa se está ejecutando ahora en segundo plano.

$ ps -xj | grep "daemon02"
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
 3872   362   361 17809 pts/22    1952 S     1000   0:01 ./daemon02
$ ps -xj | grep "systemd"
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1  3872  3872  3872 ?           -1 Ss    1000   0:00 /lib/systemd/systemd --user

Nota:La línea con las etiquetas de cada columna no aparecerá en la salida de los comandos de arriba, pero las hemos añadido por conveniencia.

Como podemos ver, nuestro programa daemon02 está ejecutándose en segundo plano y su proceso padre (PPID Parent PID) es el proceso 3872 (en vuestro sistema este número será probablemente diferente), el cual es, como os adelantamos systemd.

En caso de que ejecutéis el programa directamente desde un terminal (podéis cambiar a un terminal pulsando CTRL+ALT+F2) La salida del primer comando sería:

$ ps -xj | grep "daemon02"
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
    1  2754  2753 21400 tty2      2699 S     1000   0:00 ./daemon02

Como ya os habíamos adelantado, en este caso, es el proceso init (PID 1) el que se hace cargo del proceso huérfano. Ahora que nuestro proceso está en segundo plano, tenemos que conseguir eliminar todas sus dependencias de otros procesos...

Sesiones y Grupos de Procesos

Para entender los siguientes pasos, debemos hablar de como el sistema operativo organiza los procesos. Los sistemas que siguen el estándar POSIX, organizan los procesos en grupos, lo cual les permite, entre otras cosas, distribuir adecuadamente las señales, las cuales se distribuyen a todos los procesos del grupo.

De la misma forma, una sesión determina uno o más grupos de procesos y aplica ciertas restricciones sobre estos grupos de procesos. Así, un proceso en una sesión no puede crear un grupo de procesos que pertenezca a otra sesión, o unirse a un grupo de procesos de otra sesión.

Bueno, y que significa todo esto en términos prácticos?. Cuando utilizamos la consola, las sesiones se utilizan para crear lo que se conoce como "session login". Dicho de otra forma, cuando hacemos login en la máquina se crea una sesión, a la que se asociarán todos los procesos que ejecutemos tras ese login. Dentro de esa sesión, podremos tener distintos grupos de procesos.... Por ejemplo, cada secuencia de programas conectados por pipes creará diferentes procesos todos ellos pertenecientes al mismo grupo de procesos.

Veamos un ejemplo. En un terminal ejecutaremos:

$ yes | base64 > /dev/null

En otro:

$ ps xj
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
10009 17809 17809 17809 pts/22   18778 Ss    1000   0:00 /bin/bash
17809 18778 18778 17809 pts/22   18778 S+    1000   0:00 yes
17809 18779 18778 17809 pts/22   18778 R+    1000   0:02 base64

Como podéis ver, los procesos asociados a los programas yes y base64 son ambos hijos del proceso shell y pertenecen a la misma sesión que éste (eso es lo que nos dice la columna SID, 1709 en este caso). yes y base64 además forman parte del mismo grupo de procesos (18778). El proceso shell es además el líder de la sesión (Session Leader Process), puesto que su PID coincide con el valor del SID. Hay mucho más en lo que se refiere a grupos de procesos y sesiones, pero digamos que esta es la idea general.

Y que significa todo esto?... Bueno, todo esto se traduce fundamentalmente en como se distribuyen las señales. En nuestro ejemplo anterior, si matamos el proceso yes o el proceso base64, cualquiera de ellos, todos los procesos de ese grupo de procesos se terminará automáticamente. De la misma forma si matamos el líder de la sesión (/bin/bash) todos los procesos de esa sesión terminarán, lo que implica destruir todos los grupos de procesos que hay en esa sesión. En nuestro caso se trata solo de uno.

Líder de Sesión

Después de este rollo estamos en condiciones de pasar al segundo paso para convertir nuestro programa en un demonio. Para ello debemos hacer que nuestro proceso se convierta en el líder de su sesión. Más concretamente, debemos desvincular nuestro proceso de su sesión actual y asociarlo a una nueva, al ser una sesión nueva, el proceso pasa automáticamente a ser el líder de la sesión. De esta forma, el proceso no dependerá de ningún otro proceso y será independiente de la forma en la que se haya lanzado.... Ya sea a través de la línea de comandos o los scripts de arranque del sistema.

Para convertirnos en el líder de nuestra propia sesión utilizaremos la llamada al sistema setsid. Para ello debemos añadir las siguientes líneas a nuestro programa:

  if (setsid () < 0)
    exit (EXIT_FAILURE);

Veamos que ocurre ahora cuando ejecutamos nuestra tercera versión:

$ make daemon03
$ ./daemon03
$ ps
  PID TTY          TIME CMD
17809 pts/22   00:00:00 bash
20880 pts/22   00:00:00 ps
$  ps -x -o pid,ppid,pgrp,session,tty,comm | grep daemon03
22004  3872 22004 22004 ?        daemon03

Como podemos ver, ahora el proceso ya no está asociado a nuestra sesión actual... Un simple ps no lo lista. Cuando listamos todos los procesos del sistema, encontramos nuestro daemon03 y como podemos ver, ahora el proceso es el líder de su sesión (PID == SID) y además es el único proceso en su grupo de procesos que es el único grupo de procesos (PID == PGID) en la sesión.

Nota:El proceso 3872 es systemd en mi sistema. Si ejecutáis esto desde una consola, el proceso padre de nuestro demonio será init el proceso con PID 1,

Además, vemos que nuestro demonio ya no está asociado a ningún terminal tty, lo que significa que le proceso ya no podrá recibir señales del driver tty, o lo que es lo mismo, del teclado (CTRL+C o CTRL+Z no llegarán a este proceso).

Desvinculando el demonio completamente

Llegados a este punto, y dependiendo de la implementación, algunos demonios consideran que en este punto ya son unos buenos demonios, y terminan el proceso. Otros, realizan un paso adicional, que no es otra cosa que un segundo fork. El resultado de esta operación es que el proceso deja de ser el líder de sesión para su propia sesión o dicho de otra forma, el proceso pertenece a una sesión que no tiene líder de sesión.

Vamos a poner el código de inicialización todo junto por conveniencia y, en seguida, veremos el resultado del mismo:

  /* Poner el proceso en background */
  if ((pid = fork ()) < 0) {
    exit (EXIT_FAILURE);
  }
  else if (pid > 0) // El padre debe morir
    exit (EXIT_SUCCESS);

  // Nos hacemos lider de nueva session
  if (setsid () < 0)
    exit (EXIT_FAILURE);

  // Dejamos de ser lideres de sesión
  pid = fork ();
  if (pid > 0) exit (EXIT_SUCCESS);

Si compilamos nuestro cuarto intento y lo ejecutamos junto con el anterior, esto es lo que obtenemos:

$ ./daemon03
$ ./daemon04
$ ps -x -o pid,ppid,pgrp,tpgid,session,tty,comm | grep "daemon0[3|4]"
25075  3872 25075    -1 25075 ?        daemon03
25085  3872 25084    -1 25084 ?        daemon04

Como podemos ver, para el caso de daemon03 los valores de SID, PGRP y PID coinciden. El proceso es el líder de su propia sesión. Para el caso de daemon04, vemos como SID y PID son diferentes, es decir, el proceso no es el líder de la sesión a la que pertenece.... y es el único proceso en esa sesión:

$ ps -x |grep  25084
25415 pts/22   S+     0:00 grep 25084

El efecto práctico de esto, según distintas informaciones a las que hemos podido tener acceso es que, este proceso ya no podrá volver a asociarse a un terminal de ninguna forma. Si bien esto suena bien, tengo que admitir que no se cual es la ventaja de esto y que no tengo ni idea de como volver a asociar a daemon03 a un terminal.... Si alguien sabe como hacer esto que nos lo haga saber plis. Suena muy interesante.

Ficheros

El paso anterior ha convertido a nuestro proceso en un demonio en segundo plano. Y está perfecto para nuestro sencillo programa de ejemplo que básicamente no hace nada. Sin embargo, los demonios deben hacer cosas, acceder a ficheros, procesar datos, escribir logs y otras cosas. Así que,el siguiente paso que debemos hacer es inicializar el acceso al sistema de ficheros, para que nuestro demonio inicie su ejecución en un estado conocido.

Pensad que el demonio puede ser inicializado manualmente desde la línea de comandos, o desde el sistema de arranque, o desde un proceso cron. En todas esas circunstancias el directorio desde el que se lanza el programa, asi como las variables de entorno pueden tener valores muy distintos y tenemos que asegurarnos que nuestro demonio va a funcionar bien en cualquier caso.

Afortunadamente para nosotros, esto es más fácil de lo que parece y hay una serie de pasos estándar que todos los demonios siguen para alcanzar ese estado conocido.

Lo primero que debemos hacer es modificar la máscara de permisos de ficheros. No queremos que un valor preestablecido impida que podamos leer o escribir cierto fichero que nos interesa. El siguiente paso es cambiar nuestro directorio de trabajo al directorio raíz, de esta forma, sabremos siempre cual es nuestro directorio de trabajo o base, independientemente del directorio desde el que se haya iniciado la ejecución del demonio.

Para poder hacer esto, necesitamos añadir un par de includes, específicamente para poder utilizar la llamada al sistema umode.

#include <sys/types.h> 
#include <sys/stat.h>

Y ahora ya podemos añadir el código:

umask (0);
chdir ("/");

Cerrando Ficheros Innecesarios

El último paso para que nuestro demonio quede niquelado, consiste en cerrar todos los ficheros abiertos. Realmente este paso es parte de la inicialización del sistema de ficheros que describimos en la sección anterior, pero lo hemos puesto en su propia sección para que no nos quedara super larga la anterior.

Como decíamos, nuestro demonio tiene que iniciarse en un estado conocido y seguro y parte de esta inicialización es la de cerrar todos los ficheros abiertos. Pensad que el demonio es un proceso que es iniciado por otro proceso, su padre, y por lo tanto... de primeras va a heredar todos los descriptores de ficheros abiertos de su padre. Por otra parte, los demonios se deben ejecutar en segundo plano por definición así que los descriptores de ficheros 0,1 y 2 (stdin, stdout y stderr) son inútiles y solamente ocupan un descriptor de fichero que, dependiendo de la naturaleza de nuestro demonio, puede ser un recurso valioso. Pensad en un servidor web.... serían tres conexiones menos que podríamos manejar....

Así que, antes de comenzar con nuestra actividad demoníaca, vamos a cerrar todos los descriptores del ficheros del proceso... Solo para estar seguros que no se nos olvida ninguno.

int fd;

for (fd = sysconf(_SC_OPEN_MAX); fd>= 0; fd--) close (fd);

Perfecto. Llegados a este punto podemos decir que nuestro demonio está listo....

Pero aún hay un par de cosas que se consideran buenas prácticas y que no está de más hacer.

Cuaderno de Bitácora...

Fecha estelar.... Naaa. Vamos a hablar del log. Puesto que los demonios se ejecutan en segundo plano y, como acabamos de ver, no están asociados a ninguna consola y además stdout, stderr y stdin han sido cerrados, necesitamos una forma de poder escribir cosas importantes para que el usuario (el administrador del sistema normalmente) pueda saber si todo va bien o, en caso de producirse un error, cual ha sido la causa y solucionarlo.

Así que, lo que los demonios hacen es escribir un cuaderno de bitácora, un log...En lugar de escribir cosas en la pantalla, lo escriben en un fichero. Tanto el contenido del log como su formato es libre, si bien, hay una serie de características que son deseables para que el mencionado log sea de utilidad. Si se trata de un log especifico para nuestro demonio, al menos deberíamos añadir una marca temporal a cada entrada, de forma que podamos identificar cuando ha ocurrido alguna cosa. Es también recomendable configurar un cliente NTP para mantener la hora del sistema. Esta recomendación toma especial interés en el análisis de incidentes (de seguridad por ejemplo) en los que es necesario cotejar logs de distintas máquinas y identificar acciones que han ocurrido secuencialmente.

En caso de que el log sea compartido, como ocurre con el log del sistema, también es conveniente indicar el nombre del demonio que está añadiendo la entrada al log. Finalmente, en el caso de que el log sea distribuido, y almacene entradas de demonios corriendo en distintas máquinas en la red... en ese caso también nos va a interesar incluir el nombre de la máquina que originó el mensaje.

Bueno, todo esto parece muy complicado, pero afortunadamente, el sistema nos ofrece herramientas para que generar un log sea algo muy sencillo. Para nuestro ejemplo vamos a utilizar syslog, el cual nos ofrece todas las funciones de más arriba y algunas más. Veamos los cambios que debemos hacer a nuestro programa para poder utilizar estas funciones:

#include <syslog.h>

(...)
  openlog ("daemon07", LOG_PID, LOG_DAEMON);
  syslog (LOG_NOTICE, "Nuestro demonio arranca!!!");
  int cnt = 0;
  while (1) {
    // Do something
    usleep (10000);
    cnt++;
    if (cnt > 1000) {
      syslog (LOG_INFO, "Han pasado %d segundos", cnt / 10);
      cnt = 0;
      syslog (LOG_DEBUG, "Reseteando contador interno");
    }
  }
(...)

Como podéis ver, la función openlog, nos permite conectar a nuestro demonio con syslog. Como parámetros le pasaremos una cadena con la que identificarnos, una serie de opciones (en este caso indicamos que queremos que el PID del proceso se incluya en el log y, finalmente, lo que el sistema llama una facility. Este parámetro nos permite indicar el tipo de programa que somos de forma que, dependiendo de como haya sido configurado syslog nuestros mensajes se manejen de una determinada forma, por ejemplo, almacenándolos en un fichero especial. En nuestro caso estamos diciendo que somos un demonio (LOG_DAEMON). Para obtener una lista exhaustiva de los posibles valores consultad la página del manual (man syslog).

Una vez que el log esté configurado, solo tenemos que utilizar la función syslog para añadir mensajes al log. Podemos indicar distintos niveles de prioridades. Al igual que sucede con el parámetro facility este valor es utilizado por el demonio de log del sistema para determinar como manejar el mensaje.

Por ejemplo, en mi Ubuntu 18.04, el demonio de log es rsyslog. Si miramos el fichero de configuración /etc/rsyslog.d/50-default.conf, encontraremos líneas como:

mail.*                          -/var/log/mail.log
mail.err                        /var/log/mail.err

Esto indica al demonio que cualquier petición de un proceso usando la facility LOG_MAIL se almacenará en /var/log/mail.log, pero si el mensaje tiene una prioridad LOG_ERR o mayor, entonces lo almacenamos en /var/log/mail.err. El - en la primera entrada indica que no es necesario sincronizar el mensaje en el disco cada vez que se reciba uno. Como podemos ver, para los errores, queremos que los mensajes se almacenen inmediatamente en el disco... pero para los mensajes normales eso nos da igual y, probablemente, impondría una penalización en el rendimiento del proceso.

Ejecutando solo una instancia

Normalmente, un demonio hace una sola cosa y no es habitual lanzar varios de estos demonios simultáneamente. En general, una vez configurado un demonio, nos interesa que si se vuelve a lanzar por error, la segunda instancia simplemente se de cuenta que todo está funcionando y se muera. Hay muchas otras circunstancias en las que nos puede interesar lanzar más de un demonio, pero en esos casos lo normal es preparar el código del demonio para manejar esas situaciones.

Por ejemplo, si nuestro demonio es un servidor web, nos puede interesar disponer de dos instancias del servidor a la vez con diferentes configuración para depurar nuestra aplicación. En un caso como ese, lo normal es tener dos ficheros de configuración diferentes. El demonio debería de ser capaz de detectar que ya hay una instancia funcionando con esa configuración y no ejecutarse (muchas veces podemos indicar el fichero de bloqueo que queremos usar en la configuración... ahora veremos que es esto).

En cualquier caso, la forma más común de conseguir esto es creando un fichero en el directorio /var/run y nombrarlo con el nombre del demonio seguido de la extensión .pid, aunque esto último es a gusto del consumidor. En ese fichero almacenamos el pid del proceso que se está ejecutando.

Cuando el demonio arranca, comprueba si ese fichero existe. Si existe termina su ejecución ya que todo está ya funcionando. Sino, arranca normalmente y crea el fichero para que si otra instancia se intenta ejecutar pueda detectar que el demonio ya está funcionando.

Vamos a añadir este código justo antes de la parte del programa en la que cerramos stdout y stderr, de forma que podamos mostrar un error en pantalla si ya hay una instancia ejecutándose.

(...)
#define LOCK_FILE "/var/run/daemon08.pid"
(...)
  chdir ("/");

  FILE *f;
  if ((f = fopen (LOCK_FILE, "rt")) == NULL)
    {
      // El fichero no existe... Lo creamos
      if ((f = fopen (LOCK_FILE, "wt")) == NULL)
      {
        syslog (LOG_ERR, "No puedo crear fichero de lock");
        syslog (LOG_CRIT, "Terminando...");
        exit (EXIT_FAILURE);
      }
      fprintf (f, "%ld\n", (long) getpid());
      fclose (f);
    }
  else
    {
      fprintf (stderr, "No puedo iniciar demonio. "
           "Fallo al crear fichero de lock\n");
      exit (EXIT_FAILURE);
    }
(...)

Nada especial aquí. Intentamos abrir el fichero para leer. Si falla significa que el fichero no existe y lo creamos. Sino falla significa que ya hay una instancia ejecutándose y terminamos el programa... Fácil.

NOTA:Para poder escribir en /var/run debéis ser root. Podéis cambiar el define durante las pruebas o ejecutar el programa con sudo.

Señales

Si habéis probado el código anterior probablemente habréis encontrado algún problema para volver a lanzar el demonio una vez que lo habéis matado. Exacto, no tenemos una forma ordenada de terminar nuestro demonio así que cuando lo matamos a las bravas (con un killall daemon08)... pues el fichero de bloqueo no se borra. La próxima vez que lancemos nuestro demonio, se encontrará el fichero y pensará que ya hay una instancia ejecutándose.

NOTA: Podríamos complicar el código y leer el PID almacenado en el fichero para comprobar si el proceso existe en /proc/PID. Enviadnos vuestras soluciones para esta alternativa!!!!!

Así que lo que vamos a hacer es añadir un manejador de señales para detectar cuando nos quieren matar y dejarlo todo limpio antes de morir. Como en la peli.... Tu mata que el manejador limpia la sangre. Para ello capturaremos la señal SIGTERM que los comandos kill o killall envían a los procesos. El código se modificaría de la siguiente forma:

#include <signal.h>

#define LOCK_FILE "/var/run/daemon09.pid"

void 
die_handler (int s)
{
  syslog (LOG_NOTICE, "Terminando ejecución. Ciao");
  closelog ();
  unlink (LOCK_FILE);
  exit (EXIT_SUCCESS);
}
(...)
  syslog (LOG_NOTICE, "Nuestro demonio arranca!!!");

  signal (SIGTERM, die_handler);
  (...)

Nada especial. Nos enganchamos en la señal SIGTERM y cuando la recibimos, logeamos un mensajito, borramos el fichero de bloqueo, cerramos el fichero de log (lo que equivale a flushear todos los mensajes pendientes) y terminamos.... Más fácil no se puede.

Fieles a las tradiciones

Para terminar con nuestro demonio, vamos a ser fieles a las tradiciones UNIX más clásicas, y dejar nuestro demonio niquelao.

Tradicionalmente, los demonios UNIX utilizaban (y utilizan) las señales SIGUSR1 and SIGUSR2 para interactuar con el usuario. En esta tradición clásica, SIGUSR1 se utiliza para forzar al demonio a volver a leer el fichero de configuración y procesarlo. De esta forma, el administrador del sistema puede hacer pequeños cambios en la configuración e iniciar el demonio enviando una simple señal.

SIGUSR2 se utiliza tradicionalmente para re-iniciar el demonio, no solo volver a procesar el fichero de configuración sino un reinicio completo. Muchos de los demonios que están ahora mismo funcionando en vuestro ordenador todavía responden a estas señales con este significado... Añadir esta capacidad en nuestro demonio es super sencillo. Solo necesitamos dos manejadores y usar signal para asociarlos a estas señales.

Aquí tenéis el código completo de nuestro demonio con esta última modificación:

#include <stdio.h>
#include <stdlib.h>    // Para exit
#include <unistd.h>

#include <sys/types.h> // Parar umask
#include <sys/stat.h>

#include <syslog.h> 
#include <signal.h>

#define LOCK_FILE "/var/run/daemon09.pid"

void 
die_handler (int s)
{
  syslog (LOG_NOTICE, "Terminando ejecución. Ciao");
  closelog();
  unlink (LOCK_FILE);
  exit (EXIT_SUCCESS);
}

void 
usr1_handler (int s)
{
  syslog (LOG_NOTICE, "Reconfigurando demonio");
}

void 
usr2_handler (int s)
{
  syslog (LOG_NOTICE, "Reiniciando demonio");
}

int
main (int argc, char *argv[])
{
  pid_t pid;
  int   fd;

  /* Poner el proceso en background */
  if ((pid = fork ()) < 0) exit (EXIT_FAILURE);
  else if (pid > 0) exit (EXIT_SUCCESS); // El padre debe morir

  /* Creamos nueva sesión */
  if (setsid () < 0)
    exit (EXIT_FAILURE);

  /* Evitamos ser Lideres de sesión */
  pid = fork ();
  if (pid > 0) exit (EXIT_SUCCESS);

  /* Nos preparamos para interacturar con el sistema de ficheros*/
  /* Cambiar la máscara de modo de fichero y directorio de trabajo*/
  umask (0);
  chdir ("/");

  /* Generamos fichero de bloqueo */
  FILE *f;
  if ((f = fopen (LOCK_FILE, "rt")) == NULL)
    {
      /* El fichero no existe... Lo creamos */
      if ((f = fopen (LOCK_FILE, "wt")) == NULL)
      {
        syslog (LOG_ERR, "No puedo crear fichero de lock");
        syslog (LOG_CRIT, "Terminando...");
        exit (EXIT_FAILURE);
      }
      fprintf (f, "%ld\n", (long) getpid());
      fclose (f);
    }
  else
    {
      fprintf (stderr, "No puedo iniciar demonio. "
           "Fallo al crear fichero de lock\n");
      exit (EXIT_FAILURE);
    }
  
  /* Cerramos todos los ficheros abiertos */
  for (fd = sysconf(_SC_OPEN_MAX); fd>= 0; fd--) close (fd);

  /* Abrimos fichero de Log */
  openlog ("daemon07", LOG_PID, LOG_DAEMON);
  syslog (LOG_NOTICE, "Nuestro demonio arranca!!!");

  /* Añadimos manejadores de señales*/
  signal (SIGTERM, die_handler);
  signal (SIGUSR1, usr1_handler);
  signal (SIGUSR2, usr2_handler);
  
  int cnt = 0;
  while (1) {
    /* Aquí haríamos lo que sea que haga el demonio */
    usleep (10000);
    /* Un poco de código para ver actividad en el log */
    cnt++;
    if (cnt > 1000) {
      syslog (LOG_INFO, "Han pasado %d segundos", cnt / 10);
      cnt = 0;
      syslog (LOG_DEBUG, "Reseteando contador interno");
    }
  }
}

Conclusiones

Y esto es todo lo que os podemos contar sobre demonios. Esperamos que os haya resultado interesante y que ya no tengáis ninguna duda sobre como funcionan estos pequeños y simpáticos programitas... Pero si la tenéis... ya sabéis donde encontrarnos. Hasta la próxima.

Header Image Credits: James Lee

SOBRE Richi C. Poweri
Cuando se trata de programación, Richi es tu chica. Tiene un don especial para los lenguajes de programación, y habla con el sistema operativo de tu a tu. En su tienpo libre Richi ayuda a su familia, en los temas familiares