WORD PTR

Pointed Development

Let's Write a Linux Daemon – Part II

| Comments

Thanks II

The response to my last post was really overwhelming, all things considered. Thanks once again to the Reddit community for all of the great suggestions, corrections and constructive criticisms leveled at the code I’ve written. Several brave souls even left comments on the post itself. For completeness, I suggest you review the previous post, the previous posts’ comments, and the reddit comments concerning this series.

First, tl;dr:

  • misc. bug fixes; add configuration options

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:

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

Introduction

This is the second in a series of posts concerning the construction of a Linux daemon from the ground up. This is a fledgling effort and there’s still quite a bit to flesh out:

  • Improve the daemon interface
  • Support configuration-driven options
  • Make the daemon do something… fun. Nobody likes boring daemons.

In this post, I’ll discuss some of the design decisions concerning the interface and add support for command line and configuration file options. I’ll touch briefly on logging as well.

This post isn’t as exciting as the previous post (or posts to come), but it lays some important ground work.

Example

Initialization code has changed dramatically. There’s now a callback to handle reconfiguration by the wp_reconfigure_method_fn function pointer. Examine the following initialization routine, which creates the daemon instance and handles client-side reconfiguration.

Daemon Initialization (wp_daemonizer_initialize.c) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
 * Initialize the singleton instance of the daemon.
 * @param self_out The reference to the singleton.
 * @return WP_SUCCESS if everything is great, otherwise NULL.
 */
wp_status_t wp_daemonizer_initialize(wp_daemonizer_t **out, wp_reconfigure_method_fn on_reconfigure) {
  wp_status_t ret = WP_FAILURE;
  wp_configuration_pt config = NULL;
  wp_daemonizer_pt self = NULL;

  if(instance) {
    ret = WP_SUCCESS;
  } else {

    if((ret = wp_configuration_new(&config)) != WP_SUCCESS) {
      /* TODO: Print out some help. */
      wp_configuration_delete(config);
    } else {
      config->populate_from_file(config, NULL);
      /* config->populate_from_args(config, argc, argv); 
      if(config->get_print_arguments(config) || config->get_print_config_options(config)) {
        config->configuration_print(config);
      }
      */

      if((self = malloc(sizeof(*self)))) {
        if((self->data = malloc(sizeof(*(self->data))))) {
          /* TODO: Load from the command line or config file. */
          self->data->config = config;
          self->data->created_pid_lock_file = 0;
          self->data->reconfigure_method = on_reconfigure;

          /* Setup some static and instance methods... */
          self->daemonize = &wp_daemonizer_daemonize;
          self->signal_handler = &wp_daemonizer_signal_handler;
          self->shutdown = &wp_daemonizer_shutdown;
          self->install_signal_handlers = &wp_daemonizer_install_signal_handlers;
          self->get_instance = &wp_daemonizer_get_instance;
          self->start = &wp_daemonizer_on_start;

          /* Let's try to reconfigure ourselves.*/
          on_reconfigure(self, config);

          /* By default, install the signal handlers. Will probably change. */
          self->install_signal_handlers();
          instance = self;
          ret = WP_SUCCESS;
        } else {
          free(self);
          self = NULL;
        }
      }
    }
  }

  *out = instance;
  return ret;
}

Note the call to populate_from_file. The library can read options a configuration file. Update: I’ve removed command-line argument handling. Code using this library should use their own command line argument processing technique, handling arguments with the callback handler described above.

Configuration files have the following syntax:

1
2
3
4
5
6
7
8
9
10
11
# Example configuration file for wpd - An example Linux daemon.
# (formerly the exsvcd daemon)
# Comments start with a # and are ignored.
# Configuration options are delimited by = and ;
# Example:
#    arg=val;
verbose_logging_enabled=true;
daemon_enabled=false;
config_file_path=/etc/wpd.conf;
run_folder_path=/tmp;
lock_file_path=/tmp/wpd.lock;

Why Not libdaemon

It was suggested on the Reddit post that new deamon applications should simply utilize libdaemon. This isn’t a bad idea and I highly recommend that you examine both sources, but ultimately the code described in this series will become a full-fledged application and not simply a library for writing other daemons.

Changes to the Daemon Interface

If you’ve been following along, you may have noticed a couple of minor interface changes. The getInstance method name was renamed to get_instance. This keeps the daemon interface naming convention consistent across classes and methods. Second, a new type has been created, wp_configuration_t which is passed to the wp_daemon_t initializer. The purpose of this class is to encapsulate the various configuration options in a single source location.

Configuration

Part of a nice application interface includes configuration settings. The wp_configuration_t class describes the interface for (primarily) the daemon (and may be extended later). Examine the new/delete interface below, as described in wp_configuration.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    /**
    * Create a new wp_configuration_t instance.
    * @param config will point to the newly created configuration instance, or NULL
    *        on failure.
    * @param argc argc from the command line
    * @param argv argv from the command line
    * @return returns WP_SUCCESS on success, otherwise WP_FAILURE.
    */
    wp_status_t wp_configuration_new(wp_configuration_pt *config);

    /**
    * Deletes an wp_configuration_t instance.
    * @param config The configuration instance to delete. Will contain NULL upon
    *        success.
    * @return returns WP_SUCCESS.
    */
    void wp_configuration_delete(wp_configuration_pt config);

And the configuration interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef struct wp_configuration {
  wp_status_t (*populate_from_file)(struct wp_configuration *self, const char *file_path);

  /* TODO: revise: wp_status_t (*reload)(const struct wp_configuration *self); */

  bool (*get_enable_pid_lock)(const struct wp_configuration *self);
  void (*set_enable_pid_lock)(const struct wp_configuration *self, bool value);

  bool (*get_enable_daemon)(const struct wp_configuration *self);
  void (*set_enable_daemon)(const struct wp_configuration *self, bool value);
  bool (*get_enable_verbose_logging)(const struct wp_configuration *self);
  void (*set_enable_verbose_logging)(const struct wp_configuration *self, bool value);
  bool (*get_print_arguments)(const struct wp_configuration *self);
  void (*set_print_arguments)(const struct wp_configuration *self, bool value);
  bool (*get_print_config_options)(const struct wp_configuration *self);
  void (*set_print_config_options)(const struct wp_configuration *self, bool value);

  void (*configuration_print)(const struct wp_configuration *self);

  char *(*get_config_file_path)(const struct wp_configuration *self);
  void (*set_config_file_path)(const struct wp_configuration *self, const char *value);
  char *(*get_run_folder_path)(const struct wp_configuration *self);
  char *(*get_lock_file_path)(const struct wp_configuration *self);
  void (*set_lock_file_path)(const struct wp_configuration *self, const char *value);
  char *(*get_uid)(const struct wp_configuration *self);

  /**
   * Get the current wp_daemon_start_method_fn function pointer reference called
   * on daemon start.
   * @param self self must be an instance of the wp_configuration_pt.
   */
  wp_daemon_on_start_method_fn (*get_daemon_on_start_method)(const struct wp_configuration *self);
  void (*set_daemon_on_start_method)(const struct wp_configuration *self, wp_daemon_on_start_method_fn fn);

  wp_configuration_private_t data;
} wp_configuration_t, *wp_configuration_pt;

Hopefully the names are all self-explanatory. We have a load method (populate_from_file) and a collection of get and set properties. Note that populate_from_file populates the configuration object from a configuration file (/etc/wpd.conf for example).

Details

For simplicity, the main loop of the method to populate from a configuration file utilizes fgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while(fgets(line, WP_MAX_LINE, file) != NULL) {
      /* TODO: Ignore spaces. */
      if(line[0] == '#') {
        continue;
      }
      pch = strtok(line, tok);
      while(pch != NULL) {
        w = pch[0];
        if(((pch = strtok(NULL, tok)) == NULL) || pch[0] == 'n') {
          break;
        }
        /* helper function to populate the config. */
        fn(config, pch, w);
      }
    } /* while(); */

A function pointer to a helper method is provided to properly populate the configuration object from the parsed token:

1
2
3
4
5
switch(w) {
    case 'v':
      config->set_enable_verbose_logging(config, tolower(pch[0]) == 't');
      break;
  /*Addtional items removed for brevity*/

Logging is implemented through the ever-so-useful syslogd interface in syslog.h. For the purpose of this project, I abused the C preprocessor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifdef NDEBUG
  #define wp_log(fileptr, priority, ...) ((void)0)
#else
  #define wp_log(fileptr, config, priority, ...) \
    do { \
      if(config) { \
        if(config->get_enable_verbose_logging(config)) { \
          if(config->get_enable_daemon(config)) {\
            syslog((priority), __VA_ARGS__); \
          } \
          if(!config->get_enable_daemon(config)) { \
            fprintf((fileptr), __VA_ARGS__ ); \
            fprintf((fileptr), "\n"); \
          } \
        } \
      } \
    } while(0)
#endif

With this in place, we now have a very simple logging mechanism in place:

1
wp_log(stderr, instance->data->config, LOG_ERR, "Removed lock file: %s: %m", lock_file_name);

For a daemon, this is absolutely essential. Once running, we’re blind to anything the daemon is doing since we’re not connected to a terminal. Our only visibility to anything is that which is logged.

With the daemonizer, configuration and logging utilities now in place, we have a very basic framework stitched together from which a much larger system can be built. Before we can get to that level, however, we still need to explore some additional ideas, including threading. As it stands, the configuration class isn’t thread safe, though we’ve made no guarantees in our interface that it’s threaded at all.

TODO

There’s a lot left to flesh out. Mostly, we need to make the daemon do something besides sit around taking up cycles. In the upcoming posts, I’ll look at:

  • Improving the logging interface and configuration file parsing
  • Rationalizing some of the configuration options
  • Fix some outstanding TODOs scattered throughout the code
  • Tackle the all-important purpose of the daemon
  • Fix a memory leak when not started as a daemon
  • Explore threading

References

Conclusion

In this post, we looked at configuration options and logging. Code like this isn’t the most exciting or difficult to write, but it’s absolutely foundational for things to come.

Do you find these posts interesting? Useful? I’d appreciate whatever feedback you care to provide.

Thanks for reading!

Comments