Daemonize - a Tiny C Library for Programming the UNIX Daemons
Whatever they say, writing System-V style UNIX daemons is hard. One has to follow many rules to make a daemon process behave correctly on diverse UNIX flavours. Moreover, debugging such a code might be somewhat tricky. On the other hand, the process of daemon initialisation is rigid and well defined so the corresponding code has to be written and debugged once and later can be reused countless number of times.
Developers of BSD UNIX were very aware of this, as there a C library function daemon()
was available starting from version 4.4. The function, although non-standard, is present on many UNIXes. Unfortunately, it does not follow all the required steps to reliably run a process in the background on systems which follow System-V semantics (e.g. Linux). The details are available at the corresponding Linux man page. The main problem here, as I understand it, is that daemon()
does not use the double-forking technique to avoid the situation when zombie processes appear.
Whenever I encounter a problem like this one, I know it is time to write a tiny C library which solves it. This is exactly how 'daemonize' was born (GitHub mirror). The library consists of only two files which are meant to be integrated into the source tree of your project. Recently I have updated the library and realised that it would be good to describe how to use it on this site.
If for some reason you want to make a Windows service, I have a battle tested template code for you as well.
System-V Daemon Initialisation Procedure
To make discussion clear we shall quote the steps which have to be performed during a daemon initialisation (according to daemon(7) manual page on Linux). I do it to demonstrate that this task is more tricky than one might expect.
So, here we go:
- Close all open file descriptors except standard input, output, and error (i.e. the first three file descriptors 0, 1, 2). This ensures that no accidentally passed file descriptor stays around in the daemon process. On Linux, this is best implemented by iterating through
/proc/self/fd
, with a fallback of iterating from file descriptor 3 to the value returned bygetrlimit()
forRLIMIT_NOFILE
. - Reset all signal handlers to their default. This is best done by iterating through the available signals up to the limit of
_NSIG
and resetting them toSIG_DFL
. - Reset the signal mask using
sigprocmask()
. - Sanitize the environment block, removing or resetting environment variables that might negatively impact daemon runtime.
- Call
fork()
, to create a background process. - In the child, call
setsid()
to detach from any terminal and create an independent session. - In the child, call
fork()
again, to ensure that the daemon can never re-acquire a terminal again. - Call
exit()
in the first child, so that only the second child (the actual daemon process) stays around. This ensures that the daemon process is re-parented to init/PID 1, as all daemons should be. - In the daemon process, connect
/dev/null
to standard input, output, and error. - In the daemon process, reset the
umask
to 0, so that the file modes passed toopen()
,mkdir()
and suchlike directly control the access mode of the created files and directories. - In the daemon process, change the current directory to the root directory (
/
), in order to avoid that the daemon involuntarily blocks mount points from being unmounted. - In the daemon process, write the daemon PID (as returned by
getpid()
) to a PID file, for example/run/foobar.pid
(for a hypothetical daemon "foobar") to ensure that the daemon cannot be started more than once. This must be implemented in race-free fashion so that the PID file is only updated when it is verified at the same time that the PID previously stored in the PID file no longer exists or belongs to a foreign process. - In the daemon process, drop privileges, if possible and applicable.
- From the daemon process, notify the original process started that initialization is complete. This can be implemented via an unnamed pipe or similar communication channel that is created before the first
fork()
and hence available in both the original and the daemon process. - Call
exit()
in the original process. The process that invoked the daemon must be able to rely on that thisexit()
happens after initialization is complete and all external communication channels are established and accessible.
The discussed library does most of the above-mentioned initialisation steps as it becomes immediately evident that implementation details for some of them heavily dependent on the internal logic of an application itself, so it is not possible to implement them in a universal library. I believe it is not a flaw, though, as the missed parts are safe to implement in an application code.
The Library's Application Programming Interface
The generic programming interface was loosely modelled after above-mentioned BSD's daemon() function. The library provides two user available functions (one is, in fact, implemented on top of the other) as well as a set of flags to control a daemon creation behaviour.
The first one is:
pid_t daemonize(int flags);
The function might be considered the portable substitution to the BSD's daemon()
function.
Its only argument is the bit-mask flags
which control different aspects of the daemon creation.
DMN_DEFAULT
- Create daemon with default daemonization parameters (close existing file descriptors, reset signal handlers, change the current directory to /, set umask to 0).DMN_NO_CLOSE
- Do not close existing file descriptors and do not redirect standard file descriptors to /dev/null.DMN_KEEP_SIGNAL_HANDLERS
- Do not reset signal handlers to their defaults.DMN_NO_CHDIR
- Do not change the current directory of the daemon to /.DMN_NO_UMASK
- Do not set umask to 0.
One can use bit-or operator |
to pass multiple flags at once, as it is often the case in C programming language.
This function, generally, follows fork()
semantics. By default, the function returns PID of the daemon process to the parent process (which starts a daemon) or 0 to the daemon process. On error it returns -1 - this value might be returned to both parent and daemon process. In this case, the errno value is set accordingly. In the case, if during the daemonization an error has occurred, the errno value is transparently transferred to the parent process.
The second function is:
pid_t rundaemon(int flags, int (*daemon_func)(void *udata), void *udata, int *exit_code, const char *pid_file_path);
This function is a mere wrapper on top of daemonize()
which might check and create PID-file if desired. It is recommended to use this function instead of daemonize()
whenever applicable.
As you can see, this function has more arguments than the previous one. These arguments are:
int flags
- daemon creation flags, see above;int (*daemon_func)(void *udata)
- pointer to the function to be called after successful daemonization (the actual daemon body);void *udata
- pointer to be passed as the value in a call todaemon_func
;int exit_code
- pointer to a variable which receives value returned bydaemon_func
;const char *pid_file_path
- full pathname to the PID-file. It is used for checking if the daemon is not already running (by checking if the file exists and locked) and is created if it does not exist. When daemon exits, this file is removed under normal conditions. This value might beNULL
, in this case no checks performed and no file is created.
rundaemon()
shares return values with daemonize()
with one notable exception: it returns -2 if daemon is running.
A Complete Example
I believe that the discussion would be incomplete without a full example. It shows how to write a basic daemon using the above discussed library. The following example might be used as a template for your own daemon.
The daemon performs the following actions:
- Runs in background and creates PID-file;
- Sets up logging via syslog facility;
- Handles
SIGTERM
andSIGHUP
signals (usingsignalfd()
andselect()
); - Exits on
SIGTERM
signal.
It is implemented on top of the above-discussed rundaemon()
in an event-driven manner and distributed alongside the library as example.c
file.
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <errno.h> #include <sys/types.h> #include <sys/select.h> #include <sys/time.h> #include <sys/stat.h> #include <sys/signalfd.h> #include <fcntl.h> #include <syslog.h> #include "daemonize.h" /* The daemon process body */ static int example_daemon(void *udata) { int exit = 0; int exit_code = EXIT_SUCCESS; int sfd = -1; sigset_t mask; struct signalfd_siginfo si; /* open the system log */ openlog("EXAMPLE", LOG_NDELAY, LOG_DAEMON); /* greeting */ syslog(LOG_INFO, "EXAMPLE daemon started. PID: %ld", (long)getpid()); /* create a file descriptor for signal handling */ sigemptyset(&mask); /* handle the following signals */ sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGHUP); /* Block the signals so that they aren't handled according to their default dispositions */ if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) { closelog(); return EXIT_FAILURE; } sfd = signalfd(-1, &mask, 0); if (sfd == -1) { perror("signalfd failed"); sigprocmask(SIG_UNBLOCK, &mask, NULL); closelog(); return EXIT_FAILURE; } /* the daemon loop */ while (!exit) { int result; fd_set readset; /* add the signal file descriptor to set */ FD_ZERO(&readset); FD_SET(sfd, &readset); /* One could add more file descriptors here and handle them accordingly if one wants to build a server using event-driven approach. */ /* wait for the data in the signal file descriptor */ result = select(FD_SETSIZE, &readset, NULL, NULL, NULL); if (result == -1) { syslog(LOG_ERR, "Fatal error during select() call."); /* a low level error */ exit_code = EXIT_FAILURE; break; } /* read the data from the signal handler file descriptor */ if (FD_ISSET(sfd, &readset) && read(sfd, &si, sizeof(si)) > 0) { /* handle the signals */ switch (si.ssi_signo) { case SIGTERM: /* stop the daemon */ syslog(LOG_INFO, "Got SIGTERM signal. Stopping daemon..."); exit = 1; break; case SIGHUP: /* reload the configuration */ syslog(LOG_INFO, "Got SIGHUP signal."); break; default: syslog(LOG_WARNING, "Got unexpected signal (number: %d).", si.ssi_signo); break; } } } /* close the signal file descriptor */ close(sfd); /* remove the signal handlers */ sigprocmask(SIG_UNBLOCK, &mask, NULL); /* write an exit code to the system log */ syslog(LOG_INFO, "Daemon stopped with status code %d.", exit_code); /* close the system log */ closelog(); return exit_code; } int main(int argc, char **argv) { int exit_code = 0; pid_t pid = rundaemon(0, /* Daemon creation flags. */ example_daemon, NULL, /* Daemon body function and its argument. */ &exit_code, /* Pointer to a variable to receive daemon exit code */ "/tmp/example.pid"); /* Full path to the PID-file (lock). */ switch (pid) { case -1: /* Low level error. See errno for details. */ { perror("Cannot start daemon."); return EXIT_FAILURE; } break; case -2: /* Daemon is already running */ { fprintf(stderr,"Daemon already running.\n"); } break; case 0: /* Daemon process. */ { return exit_code; /* Return daemon exit code. */ } default: /* Parent process */ { printf("Parent: %ld, Daemon: %ld\n", (long)getpid(), (long)pid); } break; } return EXIT_SUCCESS; }
Conclusion
The objective of the library is to hide all the trickery of programming a daemon so you could concentrate on the more creative parts of your application. I hope it does this well.
If you are not only interested in writing a daemon, but also want to make yourself familiar with the techniques which are used to accomplish that, the source code is available. Moreover, I would advise anyone, who starts developing for a UNIX environment to do that, as it shows many intricacies of programming for these platforms.
Categories: C/C++ UNIX/Linux Programming