Crear un Demonio en Linux

En esta entrada vamos a explicar cómo proceder para escribir un demonio bajo GNU/Linux. Lo primero es definir que se entiende por un demonio, y citamos a la wikipedia:

Un demonio, daemon o dæmon (de sus siglas en inglés Disk And Execution MONitor), es un tipo especial de proceso informático no interactivo, es decir, que se ejecuta en segundo plano en vez de ser controlado directamente por el usuario. Este tipo de programas se ejecutan de forma continua (infinita), vale decir, que aunque se intente cerrar o matar el proceso, este continuará en ejecución o se reiniciará automáticamente. Todo esto sin intervención de terceros y sin dependencia de consola alguna.

Un ejemplo de demonio es Apache, permanece en ejecución en segundo plano esperando peticiones de páginas web para ser servidas. O cron que es un demonio temporal, es decir, realiza acciones basadas en el tiempo.

El primer paso que debemos tener en cuenta es qué va a realizar el demonio, Linux basa su programación en que cada programa resuelve un único problema pero lo hace bien. Para ilustrar esta entrada construiremos un demonio que supervise si el demonio Apache está funcionando.

Requisitos

Necesitamos un compilador de C, normalmente GCC, y las cabezeras de C para linux, esto en Debian se consigue con:

# apt-get install build-essential

Necesitaremos también un editor para escribir el código.

Estructura básica de un demonio

Durante la ejecución del demonio debemos realizar algunas tareas básicas que se resumen en:

  • Hacer fork del proceso padre.
  • Cambiar la máscara de fichero.
  • Abrir los archivos de registros necesarios.
  • Crear un identificador de sesión único.
  • Cambiar el directorio de trabajo hacia un sitio más seguro.
  • Cerrar los descriptores de ficheros estandard.
  • Escribir el código del demonio propiamente.

El código

He comentado el código con lo más importante y está bastante autoexplicativo:


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>

int main(void) {
  
  pid_t pid, sid;
  int apachePIDfile, apachePIDread, apachePID, daemonLogFileDesc;
  char buf[5], filename[255];        
          
  /* Forkeamos el proceso padre */
  pid = fork();
  if (pid < 0) {
    exit(EXIT_FAILURE);
  }
  /* Cuando tenemos un PID correcto podemos cerrar
   * el proceso padre.
   * Atención al control de errores, es una buena
   * técnica de programación comprobar todas las
   * situaciones donde se pueden dar errores. */
  if (pid > 0) {
    exit(EXIT_SUCCESS);
  }

  /* Cambiamos el modo de la mascara de ficheros */
  /* Hacemos esto para que los fichero generados por el
   * demonio sean accesibles por todo el mundo */
  umask(0);
          
  /* Abrimos los ficheros de logs del demonio */        
  /* Esto es opcional pero como vamos a cerrar los descriptores 
   * hacemos esto para que exista algo de comunicación con el demonio */
  daemonLogFileDesc = open ("log", O_WRONLY | O_CREAT, 0600);
  if (daemonLogFileDesc == -1) {
    perror ("Error en la apertura del log");
    exit (EXIT_FAILURE);
  }
  
  /* Creamos un nuevo SID */
  /* Esto se hace porque al haber matado al padre el hijo puede quedarse 
   * en el sistema como un proceso zombie, generando un nuevo SID hacemos
   * que el sistema se haga cargo del proceso huérfano otorgándole un nuevo SID */
  sid = setsid();
  if (sid < 0) {
    perror("new SID failed");
    exit(EXIT_FAILURE);
  }
    
  /* Por seguridad, cambiamos el directorio de trabajo */
  if ((chdir("/")) < 0) {
    perror("Change the current work directory failed");
    exit(EXIT_FAILURE);
  }
    
  /* Cerramos los descriptores standard */
  /* El demonio no puede usar la terminal, por lo que estos
   * descriptores son inútiles y un posible riesgo de seguridad.*/
  close(STDIN_FILENO);
  close(STDOUT_FILENO);
  close(STDERR_FILENO);
    
  /* El código del demonio */
  /* Obtenemos el PID con el que está corriendo el proceso apache */
  apachePIDfile = open("/var/run/apache2.pid", O_RDONLY, 0600);
  if (apachePIDfile == -1) {
    perror("Error en la apertura del fichero");
    exit(EXIT_FAILURE);
  }
  apachePIDread = read (apachePIDfile, buf, sizeof(buf));
    
  /* El gran bucle! */
  /* El demonio ejecutara este bucle toda su vida,
   * abrirá un archivo del pseudo sistema de ficheros /proc
   * y comprobará que existe, si existe Apache está corriendo y lo escribe
   * en el log, en caso contrario sale. */
  while (1) {
    apachePID = atoi (buf);
    snprintf(filename, sizeof(filename), "/proc/%d/cmdline", apachePID);
  
    if ((open (filename, O_RDONLY, 0600)) == -1) {
      perror ("No puedo abrir el fichero en proc");
      exit(EXIT_FAILURE);
    } else {
      write (daemonLogFileDesc, "Apache running\n", 15);
      sleep(30); /* espera 30 segundos */
    }
  }
  exit(EXIT_SUCCESS);
}

Compilamos

Para compilar hacemos lo siguiente:

# cc daemon1.c -o daemon1

A falta de un nombre más original, he llamado al demonio daemon1.

Ejecución

Para ejecutarlo hacemos:

# ./daemon1

Podemos ver que está corriendo tanto en el lista de procesos con ps como mirando el fichero log que hemos creado.

tailf log
...
Apache running
Apache running
Apache running
...

Referencias

Es una traducción/adaptación de la guía Linux Daemon Writing HOWTO, que es más teórica y aquí he querido dar una aplicación práctica, aunque es obvio que existe demonios de monitorización más avanzado quería marcar bien los conceptos de la construcción de un demonio aplicados a un caso particular.

Comments are closed.