Hostinfo day 2: Implement the daemon.
authorRichard W.M. Jones <rjones@redhat.com>
Fri, 7 Aug 2009 12:07:54 +0000 (13:07 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Fri, 7 Aug 2009 18:15:36 +0000 (19:15 +0100)
.gitignore
configure.ac
hostinfod/Makefile.am
hostinfod/configuration.c
hostinfod/error.c [new file with mode: 0644]
hostinfod/hostinfo.pod
hostinfod/hostinfod.h
hostinfod/main.c
hostinfod/monitor_sockets.c [new file with mode: 0644]

index cc6cc7d..eb47ddd 100644 (file)
@@ -23,6 +23,8 @@ hostinfo-status/hostinfo-status
 hostinfo-test/hostinfo-test.8
 hostinfo-test/hostinfo-test
 localconfigure
+local.conf
+local.guests.conf
 install-sh
 missing
 stamp-h1
index cbb64c7..125eb28 100644 (file)
@@ -33,6 +33,9 @@ AM_PROG_CC_C_O
 dnl Check support for 64 bit file offsets.
 AC_SYS_LARGEFILE
 
+dnl C functions which may be missing on older systems.
+AC_CHECK_FUNCS([inotify_init1])
+
 dnl Check for required packages using pkg-config.
 PKG_CHECK_MODULES([HOSTINFOD],[apr-1 >= 1.3])
 
index 3f21ebe..f512655 100644 (file)
@@ -21,13 +21,16 @@ sbin_PROGRAMS = hostinfod
 
 hostinfod_SOURCES = \
        configuration.c \
+       error.c \
        hostinfod.h \
-       main.c
+       main.c \
+       monitor_sockets.c
 
 hostinfod_CFLAGS = \
        -Wall \
        $(HOSTINFOD_CFLAGS) \
        -DDEFAULT_CONF_FILE=\"$(sysconfdir)/hostinfo/hostinfo.conf\" \
+       -DDEFAULT_GUESTS_FILE=\"$(sysconfdir)/hostinfo/guests.conf\" \
        -DDEFAULT_SOCKET_DIR=\"$(localstatedir)/lib/hostinfo\"
 hostinfod_LDADD = $(HOSTINFOD_LIBS)
 
index 14a2b80..0d71908 100644 (file)
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <unistd.h>
 
 #include <apr_general.h>
+#include <apr_strings.h>
 
 #include "hostinfod.h"
 
-typedef int (*process_line_fn) (const char *key, const char *value);
-static void process_conf_file (const char *path, int exit_if_not_exist, process_line_fn process_line);
+typedef int (*process_line_fn) (const char *path, int lineno,
+                               const char *key, const char *value);
+typedef int (*process_section_fn) (const char *path, int lineno,
+                                  const char *section);
+static void process_conf_file (const char *path, int exit_if_not_exist,
+                              process_line_fn process_line,
+                              process_section_fn process_section);
+static int get_bool (const char *str);
+
+/* Read the main configuration file. */
+static int main_process_line (const char *path, int lineno, const char *key, const char *value);
 
-/* Read the main configuration file.  This file is NOT reread if it
- * changes, but the auxiliary guests.conf file IS reread (and so is
- * the socket directory of course).
- */
 void
 read_main_conf_file (void)
 {
-  process_conf_file (conf_file, 0, NULL);
+  process_conf_file (conf_file, 0, main_process_line, NULL);
+}
+
+static int
+main_process_line (const char *path, int lineno,
+                  const char *key, const char *value)
+{
+  int bool;
+
+  if (strcasecmp (key, "guests") == 0) {
+    if (!value) {
+      error ("%s:%d: directive is empty: %s", path, lineno, key);
+      return -1;
+    }
+    guests_file = apr_pstrdup (pool, value);
+  } else if (strcasecmp (key, "sockets") == 0) {
+    if (!value) {
+      error ("%s:%d: directive is empty: %s", path, lineno, key);
+      return -1;
+    }
+    socket_dir = apr_pstrdup (pool, value);
+  } else if (strcasecmp (key, "verbose") == 0) {
+    bool = get_bool (value);
+    if (bool == -1) {
+      error ("%s:%d: %s: not a valid boolean - use 1 or 0", path, lineno, key);
+      return -1;
+    }
+    if (!verbose_set_on_cmdline)
+      verbose = bool;
+  } else if (strcasecmp (key, "foreground") == 0) {
+    bool = get_bool (value);
+    if (bool == -1) {
+      error ("%s:%d: %s: not a valid boolean - use 1 or 0", path, lineno, key);
+      return -1;
+    }
+    if (!foreground_set_on_cmdline)
+      foreground = bool;
+  } else {
+    error ("%s:%d: unknown directive in configuration file: %s",
+          path, lineno, key);
+    return -1;
+  }
+  return 0;
 }
 
+/* Configuration file parser. */
 static void
 process_conf_file (const char *path, int exit_if_not_exist,
-                  process_line_fn process_line)
+                  process_line_fn process_line,
+                  process_section_fn process_section)
 {
   static const char *whitespace = " \t\n\v";
   FILE *fp;
   char *line = NULL;
+  int lineno = 0;
   size_t len = 0;
   size_t real_len, key_len;
   ssize_t r;
   const char *key, *value;
 
+  debug ("begin processing configuration file %s", path);
+
   fp = fopen (path, "r");
   if (!fp) {
     if (exit_if_not_exist) {
-      perror (path);
+      perrorf ("%s", path);
       exit (1);
-    }
+    } else
+      pwarningf ("%s", path);
     return;
   }
 
   while ((r = getline (&line, &len, fp)) != -1) {
+    lineno++;
+
     /* Remove trailing \n */
     real_len = len;
     if (real_len > 0 && line[real_len-1] == '\n')
@@ -74,29 +131,69 @@ process_conf_file (const char *path, int exit_if_not_exist,
     if (line[0] == '#' || strspn (line, whitespace) == real_len)
       continue;
 
-    /* Split into key value. */
-    key_len = strcspn (line, whitespace);
-    line[key_len] = '\0';
-    key = line;
-    value = key_len < real_len ? &line[key_len+1] : NULL;
-    if (value) {
-      value += strspn (line, whitespace);
-      if (value[0] == '\0')
-       value = NULL;
+    if (line[0] == '[') {      /* Section. */
+      if (line[real_len-1] == ']')
+       line[--real_len] = '\0';
+      else {
+       error ("%s:%d: in section header, ']' not found (is there trailing whitespace or a comment?), near: %s",
+              path, lineno, line);
+       exit (1);
+      }
+
+      debug ("configuration file: section [%s]", line);
+
+      if (process_section && process_section (path, lineno, line) == -1)
+       exit (1);
     }
+    else {                     /* Key value */
+      key_len = strcspn (line, whitespace);
+      line[key_len] = '\0';
+      key = line;
+      value = key_len < real_len ? &line[key_len+1] : NULL;
+      if (value) {
+       value += strspn (line, whitespace);
+       if (value[0] == '\0')
+         value = NULL;
+      }
 
-    if (process_line && process_line (key, value) == -1)
-      exit (1);
+      debug ("configuration file: key '%s', value '%s'", key, value);
+
+      if (process_line && process_line (path, lineno, key, value) == -1)
+       exit (1);
+    }
   }
 
   free (line);
 
   if (ferror (fp)) {
-    fprintf (stderr, "error reading configuration file: %s\n", path);
+    error ("%s: error reading configuration file", path);
     exit (1);
   }
   if (fclose (fp) == EOF) {
-    perror (path);
+    perrorf ("%s", path);
     exit (1);
   }
+
+  debug ("finished processing configuration file successfully");
+}
+
+static int
+get_bool (const char *str)
+{
+  if (!str)
+    return -1;
+
+  if (strcasecmp (str, "on") == 0)
+    return 1;
+  if (strcasecmp (str, "off") == 0)
+    return 0;
+
+  switch (str[0]) {
+  case '1': case 'y': case 'Y': case 't': case 'T': case 'e': case 'E':
+    return 1;
+  case '0': case 'n': case 'N': case 'f': case 'F': case 'd': case 'D':
+    return 0;
+  }
+
+  return -1;
 }
diff --git a/hostinfod/error.c b/hostinfod/error.c
new file mode 100644 (file)
index 0000000..6fc6298
--- /dev/null
@@ -0,0 +1,202 @@
+/* virt-hostinfo
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include <apr_general.h>
+#include <apr_errno.h>
+
+#include "hostinfod.h"
+
+void
+init_syslog (void)
+{
+  openlog ("hostinfo", 0, LOG_DAEMON);
+}
+
+static void
+print (int level, const char *msg)
+{
+  const char *errwarn;
+  switch (level) {
+  case LOG_WARNING: errwarn = "warning"; break;
+  case LOG_ERR:     errwarn = "error"; break;
+  default:          errwarn = "info";
+  }
+
+  if (messages_to_stderr)
+    fprintf (stderr, "hostinfo: %s: %s\n", errwarn, msg);
+  syslog (LOG_DAEMON|level, "%s: %s", errwarn, msg);
+}
+
+void
+debug (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+
+  if (verbose) {
+    va_start (args, fs);
+    int err = vasprintf (&msg, fs, args);
+    va_end (args);
+
+    if (err < 0) return;
+
+    print (LOG_INFO, msg);
+    free (msg);
+  }
+}
+
+void
+message (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+
+  va_start (args, fs);
+  int err = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (err < 0) return;
+
+  print (LOG_INFO, msg);
+  free (msg);
+}
+
+void
+error (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+
+  va_start (args, fs);
+  int err = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (err < 0) return;
+
+  print (LOG_ERR, msg);
+  free (msg);
+}
+
+void
+warning (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+
+  va_start (args, fs);
+  int err = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (err < 0) return;
+
+  print (LOG_WARNING, msg);
+  free (msg);
+}
+
+void
+perrorf (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int errnum = errno;
+
+  va_start (args, fs);
+  int err = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (err < 0) return;
+
+#ifndef _GNU_SOURCE
+  char buf[256];
+  strerror_r (errnum, buf, sizeof buf);
+#else
+  char _buf[256];
+  char *buf;
+  buf = strerror_r (errnum, _buf, sizeof _buf);
+#endif
+
+  msg = realloc (msg, strlen (msg) + 2 + strlen (buf) + 1);
+  strcat (msg, ": ");
+  strcat (msg, buf);
+
+  print (LOG_ERR, msg);
+  free (msg);
+}
+
+void
+pwarningf (const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int errnum = errno;
+
+  va_start (args, fs);
+  int err = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (err < 0) return;
+
+#ifndef _GNU_SOURCE
+  char buf[256];
+  strerror_r (errnum, buf, sizeof buf);
+#else
+  char _buf[256];
+  char *buf;
+  buf = strerror_r (errnum, _buf, sizeof _buf);
+#endif
+
+  msg = realloc (msg, strlen (msg) + 2 + strlen (buf) + 1);
+  strcat (msg, ": ");
+  strcat (msg, buf);
+
+  print (LOG_WARNING, msg);
+  free (msg);
+}
+
+void
+paprerror (apr_status_t r, const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  char buf[256];
+
+  va_start (args, fs);
+  int err = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (err < 0) return;
+
+  apr_strerror (r, buf, sizeof buf);
+
+  msg = realloc (msg, strlen (msg) + 2 + strlen (buf) + 1);
+  strcat (msg, ": ");
+  strcat (msg, buf);
+
+  print (LOG_ERR, msg);
+  free (msg);
+}
index 0f700eb..be9c3f6 100644 (file)
@@ -31,19 +31,21 @@ The system administrator (of the host) must:
 
 =item *
 
-explicitly enable the hostinfo feature,
+explicitly enable the hostinfo feature (see below),
 
 =item *
 
-select which virtual machines can see any data,
+select which virtual machines can see any data (see L<hostinfo-set(8)>),
 
 =item *
 
-select which data they wish to disclose to virtual machines, and
+select which data they wish to disclose to virtual machines
+(C<guests.conf> below), and
 
 =item *
 
-select the frequency that virtual machines can ask for data.
+select the frequency that virtual machines can ask for data
+(C<guests.conf>).
 
 =back
 
@@ -205,7 +207,7 @@ controlling the security of the hostinfo system.
 Edits to this file take effect as soon as the next request is received
 from any guest.  You do not need to restart the daemon.
 
-B<However if this file contains any mistakes, then the daemon will
+However B<if this file contains any mistakes, then the daemon will
 exit> (ie. the default becomes no access).  The reason for the error
 will be sent to a system log file.  So after editing this file, you
 should check that hostinfo is available end-to-end (ie. from a guest),
@@ -386,14 +388,10 @@ Use the L<hostinfo-test(8)> program in the guest to test this.
 
 =item /etc/hostinfo/hostinfo.conf
 
-
 =item /etc/hostinfo/guests.conf
 
-
 =item /var/lib/hostinfo/
 
-
-
 =back
 
 =head1 SEE ALSO
index 99a1efb..f1da3e4 100644 (file)
 #define HOSTINFOD_H
 
 #include <apr_general.h>
+#include <apr_errno.h>
 
 /* main.c */
 extern const char *conf_file;
-extern const char *socket_dir;
+extern char *socket_dir;
 extern char *guests_file;
-extern int socket_dir_set_on_cmdline;
-extern int debug;
-extern int debug_set_on_cmdline;
 extern int verbose;
 extern int verbose_set_on_cmdline;
 extern int foreground;
 extern int foreground_set_on_cmdline;
+extern int messages_to_stderr;
 extern apr_pool_t *pool;       /* pool for global memory allocation */
 
+/* error.c */
+extern void init_syslog (void);
+extern void error (const char *fs, ...)
+  __attribute__((format (printf,1,2)));
+extern void perrorf (const char *fs, ...)
+  __attribute__((format (printf,1,2)));
+extern void warning (const char *fs, ...)
+  __attribute__((format (printf,1,2)));
+extern void pwarningf (const char *fs, ...)
+  __attribute__((format (printf,1,2)));
+extern void debug (const char *fs, ...)
+  __attribute__((format (printf,1,2)));
+extern void message (const char *fs, ...)
+  __attribute__((format (printf,1,2)));
+extern void paprerror (apr_status_t r, const char *fs, ...)
+  __attribute__((format (printf,2,3)));
+
 /* configuration.c */
 extern void read_main_conf_file (void);
 
+/* monitor_sockets.c */
+extern int sockets_inotify_fd;
+extern void monitor_socket_dir (void);
+
 #endif /* HOSTINFOD_H */
index ad5b326..e272210 100644 (file)
 #include <apr_general.h>
 #include <apr_network_io.h>
 #include <apr_getopt.h>
+#include <apr_strings.h>
+#include <apr_thread_proc.h>
+#include <apr_poll.h>
+#include <apr_portable.h>
 
 #include "hostinfod.h"
 
+static void main_loop (void);
+static void set_reread_socket_dir (const apr_pollfd_t *);
+static void do_reread_socket_dir (void);
+
+typedef void (*poll_callback) (const apr_pollfd_t *);
+
 const char *conf_file = DEFAULT_CONF_FILE;
-const char *socket_dir = DEFAULT_SOCKET_DIR;
+char *socket_dir = NULL;
 char *guests_file = NULL;
-int socket_dir_set_on_cmdline = 0;
-int debug = 0;
-int debug_set_on_cmdline = 0;
+
 int verbose = 0;
 int verbose_set_on_cmdline = 0;
 int foreground = 0;
 int foreground_set_on_cmdline = 0;
+
+int messages_to_stderr = 1;
+
+static int reread_socket_dir = 1;
+static int quit = 0;
+
 apr_pool_t *pool = NULL;
+static apr_pollset_t *set;
 
 static void
 usage (void)
@@ -55,14 +70,11 @@ usage (void)
          "  --help           Display full usage\n"
          "  -c file | --config file\n"
          "                   Configuration file (default: %s)\n"
-         "  -d | --debug     Enable debug messages to stderr (implies -v)\n"
          "  -f | --foreground\n"
          "                   Run in the foreground (don't fork)\n"
-         "  -s dir | --socketdir dir\n"
-         "                   Socket directory (default: %s)\n"
          "  -v               Enable verbose messages (sent to syslog, and to\n"
          "                     stderr if -d option is given)\n",
-         DEFAULT_CONF_FILE, DEFAULT_SOCKET_DIR);
+         DEFAULT_CONF_FILE);
 }
 
 int
@@ -70,9 +82,7 @@ main (int argc, char *argv[])
 {
   static const apr_getopt_option_t options[] = {
     { "config", 'c', TRUE, "configuration file" },
-    { "debug", 'd', FALSE, "enable debug messages to stderr" },
     { "foreground", 'f', FALSE, "run in foreground (don't fork)" },
-    { "socketdir", 's', TRUE, "socket directory" },
     { "verbose", 'v', FALSE, "enable verbose messages" },
     { "help", '?', FALSE, "display help" },
     { NULL, 0, 0, NULL },
@@ -87,6 +97,12 @@ main (int argc, char *argv[])
 
   apr_getopt_init (&opt, pool, argc, argv);
 
+  init_syslog ();
+
+  socket_dir = apr_pstrdup (pool, DEFAULT_SOCKET_DIR);
+  guests_file = apr_pstrdup (pool, DEFAULT_GUESTS_FILE);
+
+  /* Command line. */
   while ((r = apr_getopt_long (opt, options, &c, &optarg)) == APR_SUCCESS) {
     switch (c) {
     case 'c':
@@ -95,22 +111,14 @@ main (int argc, char *argv[])
        * it should exist.  They may have typo'd the name.
        */
       if (access (conf_file, R_OK) == -1) {
-       perror (conf_file);
+       perrorf ("%s", conf_file);
        exit (1);
       }
       break;
-    case 'd':
-      debug = verbose = 1;
-      debug_set_on_cmdline = verbose_set_on_cmdline = 1;
-      break;
     case 'f':
       foreground = 1;
       foreground_set_on_cmdline = 1;
       break;
-    case 's':
-      socket_dir = optarg;
-      socket_dir_set_on_cmdline = 1;
-      break;
     case 'v':
       verbose = 1;
       verbose_set_on_cmdline = 1;
@@ -127,22 +135,114 @@ main (int argc, char *argv[])
     exit (1);
   }
 
+  /* Read the config file. */
   read_main_conf_file ();
 
+  /* Monitor the socket directory. */
+  monitor_socket_dir ();
 
-
-
-
-
-
+  /* Create the initial pollset, just containing inotify socket. */
+  r = apr_pollset_create (&set, 1024 /* ? */, pool, 0);
+  if (r != APR_SUCCESS) {
+    paprerror (r, "apr_pollset_create");
+    exit (1);
+  }
+  apr_socket_t *tsock;
+  r = apr_os_sock_put (&tsock, &sockets_inotify_fd, pool);
+  if (r != APR_SUCCESS) {
+    paprerror (r, "apr_os_sock_put");
+    exit (1);
+  }
+  apr_pollfd_t *tpollfd = apr_palloc (pool, sizeof *tpollfd);
+  tpollfd->p = pool;
+  tpollfd->desc_type = APR_POLL_SOCKET;
+  tpollfd->reqevents = APR_POLLIN;
+  tpollfd->rtnevents = 0;
+  tpollfd->desc.s = tsock;
+  tpollfd->client_data = set_reread_socket_dir;
+  r = apr_pollset_add (set, tpollfd);
+  if (r != APR_SUCCESS) {
+    paprerror (r, "apr_pollset_add");
+    exit (1);
+  }
 
   /* Daemonize. */
   chdir ("/");
-  if (!foreground)
+  if (!foreground) {
     apr_proc_detach (1);
 
+    /* After we detach from the terminal, all further messages
+     * should just go to syslog.
+     */
+    messages_to_stderr = 0;
+  }
 
+  message (PACKAGE_STRING);
+  main_loop ();
 
   apr_terminate ();
   return 0;
 }
+
+static void
+main_loop (void)
+{
+  apr_status_t r;
+  apr_int32_t numdescs;
+  const apr_pollfd_t *descs;
+  int i;
+
+  while (!quit) {
+    /* A socket has appeared or disappeared from the socket directory. */
+    if (reread_socket_dir) {
+      do_reread_socket_dir ();
+      reread_socket_dir = 0;
+    }
+
+    /* Poll. */
+    r = apr_pollset_poll (set, 0, &numdescs, &descs);
+    if (r != APR_SUCCESS) {
+      paprerror (r, "apr_pollset_poll");
+      exit (1);
+    }
+
+    /* Perform the callbacks. */
+    for (i = 0; i < numdescs; ++i) {
+      poll_callback callback;
+
+      callback = descs[i].client_data;
+      callback (&descs[i]);
+    }
+  }
+}
+
+static void
+set_reread_socket_dir (const apr_pollfd_t *_)
+{
+  reread_socket_dir = 1;
+}
+
+static void
+do_reread_socket_dir (void)
+{
+  char buf[256];
+
+  debug ("reading socket directory");
+
+  /* Discard anything which appears on the inotify socket.  We will
+   * reread the whole directory each time.
+   */
+  while (read (sockets_inotify_fd, buf, sizeof buf) > 0)
+    ;
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/hostinfod/monitor_sockets.c b/hostinfod/monitor_sockets.c
new file mode 100644 (file)
index 0000000..547a535
--- /dev/null
@@ -0,0 +1,87 @@
+/* virt-hostinfo
+ * Copyright (C) 2009 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+
+#include <apr_general.h>
+
+#include "hostinfod.h"
+
+int sockets_inotify_fd = -1;
+
+void
+monitor_socket_dir (void)
+{
+  struct stat statbuf;
+
+  if (!socket_dir) {
+    error ("no socket directory was configured - see hostinfo(8)");
+    exit (1);
+  }
+
+  if (stat (socket_dir, &statbuf) == -1) {
+    perrorf ("%s (socket directory): does not exist or is not accessible",
+            socket_dir);
+    exit (1);
+  }
+  if (!S_ISDIR (statbuf.st_mode)) {
+    error ("%s (socket directory): not a directory", socket_dir);
+    exit (1);
+  }
+
+#ifdef HAVE_INOTIFY_INIT1
+  sockets_inotify_fd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC);
+  if (sockets_inotify_fd == -1) {
+    perrorf ("inotify_init");
+    exit (1);
+  }
+#else
+  sockets_inotify_fd = inotify_init ();
+  if (sockets_inotify_fd == -1) {
+    perrorf ("inotify_init");
+    exit (1);
+  }
+  if (fcntl (sockets_inotify_fd, F_SETFL, O_NONBLOCK) == -1) {
+    perrorf ("fcntl: O_NONBLOCK");
+    exit (1);
+  }
+  if (fcntl (sockets_inotify_fd, F_SETFD, FD_CLOEXEC) == -1) {
+    perrorf ("fcntl: FD_CLOEXEC");
+    exit (1);
+  }
+#endif
+
+  if (inotify_add_watch (sockets_inotify_fd, socket_dir,
+                        IN_CREATE|IN_DELETE|IN_MOVE) == -1) {
+    perrorf ("inotify_add_watch: %s", socket_dir);
+    exit (1);
+  }
+
+  debug ("inotify of %s successful, fd = %d",
+        socket_dir, sockets_inotify_fd);
+}