]> mj.ucw.cz Git - libucw.git/commitdiff
Daemon: An attempt at daemonization helper
authorMartin Mares <mj@ucw.cz>
Thu, 12 Jul 2012 16:38:12 +0000 (18:38 +0200)
committerMartin Mares <mj@ucw.cz>
Thu, 12 Jul 2012 16:38:12 +0000 (18:38 +0200)
ucw/Makefile
ucw/daemon.c [new file with mode: 0644]
ucw/daemon.h [new file with mode: 0644]

index af6219bf7af434cd3a99c72b7cf5a1834902a75b..1d6db190fd8aa66fee5b5b13e52351b97587b7d9 100644 (file)
@@ -33,7 +33,8 @@ LIBUCW_MODS= \
        bbuf gary \
        getopt \
        strtonum \
-       resource trans res-fd res-mem res-subpool res-mempool res-eltpool
+       resource trans res-fd res-mem res-subpool res-mempool res-eltpool \
+       daemon
 
 LIBUCW_MAIN_INCLUDES= \
        lib.h log.h threads.h time.h \
@@ -59,7 +60,8 @@ LIBUCW_MAIN_INCLUDES= \
        kmp.h kmp-search.h binsearch.h \
        partmap.h \
        strtonum.h \
-       resource.h trans.h
+       resource.h trans.h \
+       daemon.h
 
 ifdef CONFIG_UCW_THREADS
 # Some modules require threading
diff --git a/ucw/daemon.c b/ucw/daemon.c
new file mode 100644 (file)
index 0000000..0c83bde
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ *     UCW Library -- Daemonization
+ *
+ *     (c) 2012 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#include <ucw/lib.h>
+#include <ucw/daemon.h>
+#include <ucw/strtonum.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+
+static void daemon_resolve_ugid(struct daemon_params *dp)
+{
+  // Resolve user name
+  const char *u = dp->run_as_user;
+  struct passwd *pw = NULL;
+  if (u)
+    {
+      if (u[0] == '#')
+       {
+         uns id;
+         const char *err = str_to_uns(&id, u, NULL, 10 | STN_WHOLE);
+         if (err)
+           die("Cannot parse user `%s': %s", u, err);
+         dp->run_as_uid = id;
+         dp->want_setuid = 1;
+       }
+      else
+       {
+         pw = getpwnam(u);
+         if (!pw)
+           die("No such user `%s'", u);
+         dp->run_as_uid = pw->pw_uid;
+         dp->want_setuid = 1;
+       }
+    }
+
+  // Resolve group name
+  const char *g = dp->run_as_group;
+  struct group *gr;
+  if (g)
+    {
+      if (g[0] == '#')
+       {
+         uns id;
+         const char *err = str_to_uns(&id, g, NULL, 10 | STN_WHOLE);
+         if (err)
+           die("Cannot parse group `%s': %s", g, err);
+         dp->run_as_gid = id;
+         dp->want_setgid = 1;
+       }
+      else
+       {
+         gr = getgrnam(g);
+         if (!gr)
+           die("No such group `%s'", g);
+         dp->run_as_gid = gr->gr_gid;
+         dp->want_setgid = 1;
+       }
+    }
+  else if (pw)
+    {
+      dp->run_as_gid = pw->pw_gid;
+      dp->want_setgid = 2;
+    }
+}
+
+void daemon_init(struct daemon_params *dp)
+{
+  daemon_resolve_ugid(dp);
+
+  if (dp->pid_file)
+    {
+      // Check that PID file path is absolute
+      if (!(dp->flags & DAEMON_FLAG_PRESERVE_CWD) && dp->pid_file[0] != '/')
+       die("Path to PID file `%s' must be absolute", dp->pid_file);
+
+      // Open PID file
+      dp->pid_fd = open(dp->pid_file, O_RDWR | O_CREAT, 0666);
+      if (dp->pid_fd < 0)
+       die("Cannot open `%s': %m", dp->pid_file);
+
+      // Try to lock it with an exclusive lock
+      struct flock fl = { .l_type = F_WRLCK, .l_whence = SEEK_SET };
+      if (fcntl(dp->pid_fd, F_SETLK, &fl) < 0)
+       {
+         if (errno == EAGAIN || errno == EACCES)
+           die("Daemon is already running (`%s' locked)", dp->pid_file);
+         else
+           die("Cannot lock `%s': %m", dp->pid_file);
+       }
+
+      // Make a note that the daemon is starting
+      if (write(dp->pid_fd, "(starting)\n", 11) != 11 ||
+         ftruncate(dp->pid_fd, 11) < 0)
+       die("Error writing `%s': %m", dp->pid_file);
+    }
+}
+
+void daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp))
+{
+  // Switch GID and UID
+  if (dp->want_setgid && setresgid(dp->run_as_gid, dp->run_as_gid, dp->run_as_gid) < 0)
+    die("Cannot set GID to %d: %m", (int) dp->run_as_gid);
+  if (dp->want_setgid > 1 && initgroups(dp->run_as_user, dp->run_as_gid) < 0)
+    die("Cannot initialize groups: %m");
+  if (dp->want_setuid && setresuid(dp->run_as_uid, dp->run_as_uid, dp->run_as_uid) < 0)
+    die("Cannot set UID to %d: %m", (int) dp->run_as_uid);
+
+  // Create a new session and close stdio
+  setsid();
+  close(0);
+  if (open("/dev/null", O_RDWR, 0) < 0 ||
+      dup2(0, 1) < 0)
+    die("Cannot redirect stdio to `/dev/null': %m");
+
+  // Set umask to a reasonable value
+  umask(022);
+
+  // Do not hold the current working directory
+  if (!(dp->flags & DAEMON_FLAG_PRESERVE_CWD))
+    {
+      if (chdir("/") < 0)
+       die("Cannot chdir to root: %m");
+    }
+
+  // Fork
+  pid_t pid = fork();
+  if (pid < 0)
+    die("Cannot fork: %m");
+  if (!pid)
+    {
+      // The PID file is left open, so that it is kept locked
+      // FIXME: Downgrade the lock to shared
+      body(dp);
+      exit(0);
+    }
+  
+  // Write PID and release the PID file
+  if (dp->pid_file)
+    {
+      char buf[32];
+      int c = snprintf(buf, sizeof(buf), "%d\n", (int) pid);
+      ASSERT(c <= (int) sizeof(buf));
+      if (lseek(dp->pid_fd, 0, SEEK_SET) < 0 ||
+         write(dp->pid_fd, buf, c) != c ||
+         ftruncate(dp->pid_fd, c) < 0 ||
+         close(dp->pid_fd) < 0)
+       die("Cannot write PID to `%s': %m", dp->pid_file);
+    }
+}
+
+void daemon_exit(struct daemon_params *dp)
+{
+  if (dp->pid_file)
+    {
+      if (unlink(dp->pid_file) < 0)
+       msg(L_ERROR, "Cannot unlink PID file `%s': %m", dp->pid_file);
+      close(dp->pid_fd);
+    }
+}
+
+#ifdef TEST
+
+static void body(struct daemon_params *dp)
+{
+  log_fork();
+  msg(L_INFO, "Daemon is running");
+  sleep(60);
+  msg(L_INFO, "Daemon is shutting down");
+  daemon_exit(dp);
+}
+
+int main(void)
+{
+  struct daemon_params dp = {
+    .pid_file = "/tmp/123",
+  };
+
+  daemon_init(&dp);
+  daemon_run(&dp, body);
+  msg(L_INFO, "Main program has ended");
+  return 0;
+}
+
+#endif
diff --git a/ucw/daemon.h b/ucw/daemon.h
new file mode 100644 (file)
index 0000000..13f8c37
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ *     UCW Library -- Daemonization
+ *
+ *     (c) 2012 Martin Mares <mj@ucw.cz>
+ *
+ *     This software may be freely distributed and used according to the terms
+ *     of the GNU Lesser General Public License.
+ */
+
+#ifndef _UCW_DAEMON_H
+#define _UCW_DAEMON_H
+
+#include <sys/types.h>
+
+struct daemon_params {
+  uns flags;                           // DAEMON_FLAG_xxx
+  const char *pid_file;                        // A path to PID file (optional)
+  const char *run_as_user;             // User name or "#uid" (optional)
+  const char *run_as_group;            // Group name or "#gid" (optional)
+
+  // Internal
+  uid_t run_as_uid;
+  uid_t run_as_gid;
+  int want_setuid;
+  int want_setgid;
+  int pid_fd;
+};
+
+enum daemon_flags {
+  DAEMON_FLAG_PRESERVE_CWD = 1,                // Skip chdir("/")
+};
+
+void daemon_init(struct daemon_params *dp);
+
+void daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp));
+
+void daemon_exit(struct daemon_params *dp);
+
+#endif