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.
■