This is Part I in a series. Check out the rest of the series:

Thanks!

Last week was my very first post. I received a lot of positive feedback from the good people over at the proggit subreddit… and a lot of good, constructive criticism. Thanks everyone!

First, tl;dr:

  • fork; setsid; reset file mask; cd; reopen standard files

(special thanks to tinou for the idea).

Introduction

This is the first part in a series of posts dedicated to writing a *NIX-based daemon that does… something interesting. Part I is primarily dedicated to setting up the environment and establishing the daemon framework.

As with all of the source code on this blog, I’ve pushed everything up to github under the repository. You can retrieve the latest like so:

git clone git://github.com/jgshort/wordptr.git

The example project that I’ll be building through the course of this series (conveniently called the Example Service Daemon [exsvcd]) will fork itself and run perpetually, waiting for specific events and responding as necessary:

  1. ctor@asphyxia:~/git/nm/exsvcd$ sudo src/exsvcd 
  2. ctor@asphyxia:~/git/nm/exsvcd$ ps aux | grep exsvcd
  3. daemon   13068  0.0  0.0  12424   368 ?        S    16:55   0:00 src/exsvcd
  4. ctor     13070  0.0  0.0   7624   920 pts/0    S+   16:55   0:00 grep --color=auto exsvcd
  5. ctor@asphyxia:~/git/nm/exsvcd$ sudo kill -TERM 13068
  6. ctor@asphyxia:~/git/nm/exsvcd$ ps aux | grep exsvcd
  7. ctor     13073  0.0  0.0   7624   924 pts/0    S+   16:56   0:00 grep --color=auto exsvcd
  8. ctor@asphyxia:~/git/nm/exsvcd$

Other example daemons include mysqld, memcached and httpd.

In Part I, we’ll look at the basic building blocks of a daemon in C.


Example

By the end of this article, we’ll have the essential pieces put together to kick off a daemon service. The following code demonstrates the basic object interface:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <exsvcd.h>
  4. #include <nmc_daemonizer.h>
  5.  
  6. int main(int argc, char* argv[]) {
  7.   /* TODO: Utilize arguments. */
  8.   nmc_status_t status = NMC_FAILURE;
  9.   nmc_daemonizer_t *daemon = NULL;
  10.  
  11.   if((status = nmc_daemonizer_initialize(&daemon)) == NMC_SUCCESS) {;
  12.     /* Daemonize ourselves:
  13.      * fork; setsid; reset file mask; cd; reopen standard files
  14.      */
  15.     if((status = daemon->daemonize(daemon)) == NMC_SUCCESS) {
  16.       /* Assuming we successfully daemonize, here we start. */
  17.       status = daemon->start(daemon);
  18.     }
  19.   }
  20.  
  21.   return status;
  22. }

Details

The public interface for the daemonizer is presented below:

  1. #ifndef NMC_DAEMONIZER__H
  2. #define NMC_DAEMONIZER__H
  3.  
  4. #include <nmc_common.h>
  5.  
  6. /* Keep the private impementation... private. */
  7. struct __nmc_daemonizer_private_t;
  8. typedef struct __nmc_daemonizer_private_t *nmc_daemonizer_private_t;
  9.  
  10. /* Here's the public interface! */
  11. typedef struct nmc_daemonizer {
  12.   /* The daemonize method which will fork, etc., our process */
  13.   nmc_status_t (*daemonize)(const struct nmc_daemonizer *self);
  14.   /* Start the "main loop." */
  15.   nmc_status_t (*start)(const struct nmc_daemonizer *self);
  16.  
  17.   /* Return an instance of the daemon singleton. */
  18.   struct nmc_daemonizer* (*getInstance)();
  19.  
  20.   /* Handle signals from the OS. */
  21.   void (*signal_handler)(int sig);
  22.   void (*install_signal_handlers)();
  23.  
  24.   /* Shutdown the daemon. */
  25.   void (*shutdown)();
  26.  
  27.   /* Our private implementation details. */
  28.   nmc_daemonizer_private_t data;
  29. } nmc_daemonizer_t;
  30.  
  31. /* Utility method to create a new instance of the daemonizer */
  32. nmc_status_t nmc_daemonizer_initialize(nmc_daemonizer_t **self);
  33.  
  34. #endif

We have a basic framework from which we can build up a much more complete daemon in the upcoming weeks. For now, this just gets us started. In order to encapsulate everything into a single class and keep the details as straight forward as possible, I implemented the daemonizer class as a singleton. We can either initialize or retrieve the instance with getInstance or nmc_daemonizer_initialize.

In the future, we will provide initialization parameters to the initialize method. For now, we’ll simply start up with some defaults.

The real work is contained within the nmc_daemonizer_daemonize method. Though I’ve done my best to keep this blog and the source on github in sync, I defer you to the actual repository for specifics. The following method, nmc_daemonizer_daemonize is contained within the nmc_daemonizer.c implementation file, located here: nmc_daemonizer.c.

  1. static nmc_status_t nmc_daemonizer_daemonize(const nmc_daemonizer_t *self) {
  2.   nmc_status_t res = NMC_FAILURE;
  3.   pid_t pid, sid;
  4.   struct sigaction sa;
  5.  
  6.   if((res = nmc_set_uid(self)) == NMC_SUCCESS) {
  7.     /* Forking. Opening syslog for exsvcd. */
  8.     if((pid = fork()) < 0) {
  9.       /* fork error */
  10.       /* FATAL: fork: %m */
  11.       exit(EXIT_FAILURE);
  12.     } else if(pid != 0) {
  13.       exit(EXIT_SUCCESS);
  14.     }
  15.  
  16.     umask(027);
  17.  
  18.     sid = setsid(); /* get a new process group. */
  19.     if(sid < 0) {
  20.       /* FATAL: setsid: %m */
  21.       exit(EXIT_FAILURE);
  22.     }
  23.  
  24.     /* log to syslog prefixed with exsvcd */
  25.     openlog("exsvcd", LOG_PID, LOG_USER);
  26.  
  27.     sa.sa_handler = SIG_IGN;
  28.     sigemptyset(&sa.sa_mask);
  29.     sa.sa_flags = 0;
  30.     if(sigaction(SIGHUP, &sa, NULL) < 0) {
  31.       /* FATAL: sigaction: %m */
  32.       exit(EXIT_FAILURE);
  33.     }
  34.  
  35.     if(chdir(self->data->run_path) == 0) {
  36.       set_pid_lock(self);
  37.       null_file_descriptors(self);
  38.       return NMC_SUCCESS;
  39.     } else { /* FATAL: chdir. */ }
  40.   } else {
  41.     /* couldn't set UID. */
  42.   }
  43.   exit(EXIT_FAILURE);
  44. }

Many things are happening in this very short snippet of code:

  • Line 6 calls the method nmc_set_uid which attempts to see if we’re already running as a daemon and sets the UID to user “daemon.” This assumes we were started as root.
  • Assuming we’re successful, we are now running as user “daemon,” we create a new process by calling fork on line 8.
  • We open up a PID file and save our PID to the file. This is one method of ensuring the daemon is running only once.
  • Line 16 sets default file system permissions.
  • We open a syslog for the daemon on line 25. This will become essential in coming posts: We cannot output diagnostic information to the screen as a service, since we set all of the standard file descriptors to /dev/null. The simplest solution is syslogd.
  • Before we complete the deamonization process, we chdir to a “safe” folder path, in this case /tmp.

By the conclusion of this method, the UID has changed, process has forked, we’ve created a PID file and chdir to a neutral location. Our daemon is running and ready to receive events.

Eventually, the deamon will sit around listening for interesting events. Right now, we don’t listen for anything… We just run in a loop forever.

  1. static nmc_status_t nmc_daemonizer_start(const nmc_daemonizer_t *self) {
  2.   sigset_t mask, oldmask;
  3.   sigemptyset(&mask);
  4.   sigaddset(&mask, SIGUSR1);
  5.   sigprocmask(SIG_BLOCK, &mask, &oldmask);
  6.   while(true) {
  7.     sigsuspend(&oldmask);
  8.   }
  9.   sigprocmask(SIG_UNBLOCK, &mask, NULL);
  10.  
  11.   return NMC_SUCCESS;
  12. }

Conclusion

We now have a simple class that sets up a process as a daemon service. Many things need to be fleshed out, but the framework is present. We still need to:

  • Establish a logging framework. This is the highest priority as – once the daemon is running – we’re blind to anything going on.
  • Listen for events. Right now our daemon starts up… but then it just runs and doesn’t do anything at all. What a worthless service!

In the next several posts, we’ll look more into the implementation details of the daemon, and we’ll set up a framework from which we can listen to and handle events, whether that be on a socket or the file system or whatever.

This is a comparatively short post but I sincerely hope you find it useful. As with anything I write, I welcome criticisms, suggestions and bug fixes. I’m open to ideas for the service, too.

Thanks!

References

Update (2011-01-31)

Wow! Lots of great info up on the Reddit proggit entry for this post and in the comments below.

  • kurin asks, “Don’t you double fork, to prevent zombies?”
    The answer is yes, you do. See here, here and here. The original implementation of my deamon did perform the double fork. I removed it thinking A) It was a mistake on my part, and B) I was under the impression that if the parent exists immediately after the fork, the double fork is not necessary. I’ve reimplemented the double fork pattern in the source. See the github repository for the latest.
  • There’s always int daemon(int nochdir, int noclose) in unistd.h. Personally, I don’t care for this implementation of daemon. Eventually, my implementation will have a bit more configuration-driven options and overrides. I like that. Again, just my personal preference.
  • As kev009 states, “It’s also tradition to close stdin, stdout, stdrr and reopen them as null to prevent misuse of those FDs.”
    This is correct and I failed to mention it in the post above, but it is implemented in code:
    1. static void null_file_descriptors(const nmc_daemonizer_t *self) {
    2.   FILE *f;
    3.   f = freopen("/dev/null", "r", stdin);
    4.   if(f == NULL) {
    5.     /* FATAL: freopen: %m */
    6.     exit(EXIT_FAILURE);
    7.   }
    8.   f = freopen("/dev/null", "w", stdout);
    9.   if(f == NULL) {
    10.     /* FATAL: freopen: %m */
    11.     exit(EXIT_FAILURE);
    12.   }
    13.   f = freopen("/dev/null", "w", stderr);
    14.   if(f == NULL) {
    15.     /* FATAL: freopen: %m */
    16.     exit(EXIT_FAILURE);
    17.   }
    18. }
  • Stefan M. (see below) comments that this is really a tutorial on the interface for my daemon code and not necessarily a POSIX daemon tutorial. He’s absolutely right, this post does not attempt to teach the fundamentals of POSIX programming. For that, I highly recommend the seminal works of the late W. Richard Stevens. His writings helped shape me into the developer I am today and I personally feel they are invaluable to any UNIX system developer.

Thanks for all of the great comments, everyone!

Tagged with: