]> mj.ucw.cz Git - libucw.git/commitdiff
Daemon: Basics of daemon control (incomplete)
authorMartin Mares <mj@ucw.cz>
Tue, 17 Jul 2012 22:01:01 +0000 (00:01 +0200)
committerMartin Mares <mj@ucw.cz>
Tue, 17 Jul 2012 22:01:01 +0000 (00:01 +0200)
ucw/daemon.c
ucw/daemon.h

index 0012db4605bb111e30bcd6c65a729312c5a10956..b0ea47672d8cc0c14dbc2a74bb0400d99b174879 100644 (file)
 #include <ucw/lib.h>
 #include <ucw/daemon.h>
 #include <ucw/strtonum.h>
+#include <ucw/process.h>
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdarg.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <pwd.h>
 #include <grp.h>
 #include <errno.h>
+#include <signal.h>
 #include <sys/file.h>
+#include <sys/wait.h>
 
-static void daemon_resolve_ugid(struct daemon_params *dp)
+static void
+daemon_resolve_ugid(struct daemon_params *dp)
 {
   // Resolve user name
   const char *u = dp->run_as_user;
@@ -76,7 +81,8 @@ static void daemon_resolve_ugid(struct daemon_params *dp)
     }
 }
 
-void daemon_init(struct daemon_params *dp)
+void
+daemon_init(struct daemon_params *dp)
 {
   daemon_resolve_ugid(dp);
 
@@ -110,7 +116,8 @@ void daemon_init(struct daemon_params *dp)
     }
 }
 
-void daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp))
+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)
@@ -162,7 +169,8 @@ void daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp)
     }
 }
 
-void daemon_exit(struct daemon_params *dp)
+void
+daemon_exit(struct daemon_params *dp)
 {
   if (dp->pid_file)
     {
@@ -172,6 +180,163 @@ void daemon_exit(struct daemon_params *dp)
     }
 }
 
+static enum daemon_control_status
+daemon_control_err(struct daemon_control_params *dc, char *msg, ...)
+{
+  va_list args;
+  va_start(args, msg);
+  vsnprintf(dc->error_msg, DAEMON_ERR_LEN, msg, args);
+  va_end(args);
+  return DAEMON_STATUS_ERROR;
+}
+
+static int
+daemon_read_pid(struct daemon_control_params *dc)
+{
+  int pid_fd = open(dc->pid_file, O_RDONLY);
+  if (pid_fd < 0)
+    {
+      if (errno == ENOENT)
+       return 0;
+      daemon_control_err(dc, "Cannot open PID file `%s': %m", dc->pid_file);
+      return -1;
+    }
+
+  if (flock(pid_fd, LOCK_EX | LOCK_NB) >= 0)
+    {
+      // The lock file is stale
+      close(pid_fd);
+      return 0;
+    }
+
+  if (errno != EINTR && errno != EWOULDBLOCK)
+    {
+      daemon_control_err(dc, "Cannot lock PID file `%s': %m", dc->pid_file);
+      goto fail;
+    }
+
+  char buf[16];
+  int n = read(pid_fd, buf, sizeof(buf));
+  if (n < 0)
+    {
+      daemon_control_err(dc, "Error reading `%s': %m", dc->pid_file);
+      goto fail;
+    }
+  if (n == (int) sizeof(buf))
+    {
+      daemon_control_err(dc, "PID file `%s' is too long", dc->pid_file);
+      goto fail;
+    }
+  buf[n] = 0;
+  int pid = atoi(buf);
+  if (!pid)
+    {
+      daemon_control_err(dc, "PID file `%s' does not contain a valid PID", dc->pid_file);
+      goto fail;
+    }
+  close(pid_fd);
+  return pid;
+
+fail:
+  close(pid_fd);
+  return -1;
+}
+
+enum daemon_control_status
+daemon_control(struct daemon_control_params *dc)
+{
+  enum daemon_control_status st = DAEMON_STATUS_ERROR;
+
+  int guard_fd = open(dc->guard_file, O_RDWR | O_CREAT, 0666);
+  if (guard_fd < 0)
+    return daemon_control_err(dc, "Cannot open guard file `%s': %m", dc->guard_file);
+  if (flock(guard_fd, LOCK_EX) < 0)
+    return daemon_control_err(dc, "Cannot lock guard file `%s': %m", dc->guard_file);
+
+  // Read the PID file
+  int pid = daemon_read_pid(dc);
+  if (pid < 0)
+    goto done;
+
+  switch (dc->action)
+    {
+    case DAEMON_CONTROL_CHECK:
+      if (pid)
+       st = DAEMON_STATUS_OK;
+      else
+       st = DAEMON_STATUS_NOT_RUNNING;
+      break;
+    case DAEMON_CONTROL_START:
+      if (pid)
+       st = DAEMON_STATUS_ALREADY_DONE;
+      else
+       {
+         pid_t pp = fork();
+         if (pp < 0)
+           {
+             daemon_control_err(dc, "Cannot fork: %m");
+             goto done;
+           }
+         if (pp)
+           {
+             close(guard_fd);
+             execvp(dc->argv[0], dc->argv);
+             fprintf(stderr, "Cannot execute `%s': %m\n", dc->argv[0]);
+             exit(DAEMON_STATUS_ERROR);
+           }
+         int stat;
+         int ec = waitpid(pp, &stat, 0);
+         if (ec < 0)
+           {
+             daemon_control_err(dc, "Cannot wait: %m");
+             goto done;
+           }
+         if (WIFEXITED(stat) && WEXITSTATUS(stat) == DAEMON_STATUS_ERROR)
+           {
+             daemon_control_err(dc, "Cannot execute the daemon");
+             goto done;
+           }
+         char ecmsg[EXIT_STATUS_MSG_SIZE];
+         if (format_exit_status(ecmsg, stat))
+           {
+             daemon_control_err(dc, "Daemon %s", ecmsg);
+             goto done;
+           }
+         pid = daemon_read_pid(dc);
+         if (!pid)
+           daemon_control_err(dc, "Daemon failed to write the PID file `%s'", dc->pid_file);
+         else
+           st = DAEMON_STATUS_OK;
+       }
+      break;
+    case DAEMON_CONTROL_STOP:
+      if (!pid)
+       return DAEMON_STATUS_ALREADY_DONE;
+      int sig = dc->signal ? : SIGTERM;
+      if (kill(pid, sig) < 0)
+       {
+         daemon_control_err(dc, "Cannot send signal %d: %m", dc->signal);
+         goto done;
+       }
+      // FIXME: Wait for the daemon to exit, possibly with a timeout
+      break;
+    case DAEMON_CONTROL_SIGNAL:
+      if (!pid)
+       return DAEMON_STATUS_NOT_RUNNING;
+      if (kill(pid, dc->signal) < 0)
+       daemon_control_err(dc, "Cannot send signal %d: %m", dc->signal);
+      else
+       st = DAEMON_STATUS_OK;
+      break;
+    default:
+      ASSERT(0);
+    }
+
+done:
+  close(guard_fd);
+  return st;
+}
+
 #ifdef TEST
 
 static void body(struct daemon_params *dp)
index 0cf87086ff86d20d6c159d9428d0545476e2c452..561a401577e9bbecc5614a932be298c71475ea7b 100644 (file)
@@ -52,4 +52,48 @@ void daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp)
  **/
 void daemon_exit(struct daemon_params *dp);
 
+#define DAEMON_ERR_LEN 256
+
+/** Parameters passed to daemon_control() **/
+struct daemon_control_params {
+  const char *pid_file;                // A path to PID file
+  const char *guard_file;      // A path to guard file
+  int action;                  // Action to perform (DAEMON_CONTROL_xxx)
+  char * const *argv;          // Daemon's arguments, NULL-terminated (for DAEMON_CONTROL_START)
+  int signal;                  // Signal to send (for DAEMON_CONTROL_SIGNAL)
+  char error_msg[DAEMON_ERR_LEN];      // A detailed error message returned (for DAEMON_STATUS_ERROR)
+};
+
+enum daemon_control_action {
+  DAEMON_CONTROL_CHECK,
+  DAEMON_CONTROL_START,
+  DAEMON_CONTROL_STOP,
+  DAEMON_CONTROL_SIGNAL,
+};
+
+/**
+ * Perform an action on a daemon:
+ *
+ * * `DAEMON_CONTROL_START` to start the daemon
+ * * `DAEMON_CONTROL_STOP` to stop the daemon (send `SIGTERM` or `dc->signal` if non-zero)
+ * * `DAEMON_CONTROL_CHECK` to check that the daemon is running
+ * * `DAEMON_CONTROL_SIGNAL` to send a signal to the daemon
+ *
+ * The function returns a status code:
+ *
+ * * `DAEMON_STATUS_OK` if the action has been performed successfully
+ * * `DAEMON_STATUS_ALREADY_DONE` if the daemon is already in the requested state
+ * * `DAEMON_STATUS_NOT_RUNNING` if the action failed, because the daemon is not running
+ * * `DAEMON_STATUS_ERROR` if the action failed for some other reason (in this case,
+ *   the error_msg field contains a full error message)
+ **/
+enum daemon_control_status daemon_control(struct daemon_control_params *dc);
+
+enum daemon_control_status {
+  DAEMON_STATUS_OK = 0,
+  DAEMON_STATUS_ALREADY_DONE = 100,
+  DAEMON_STATUS_NOT_RUNNING = 101,
+  DAEMON_STATUS_ERROR = 102,
+};
+
 #endif