#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;
}
}
-void daemon_init(struct daemon_params *dp)
+void
+daemon_init(struct daemon_params *dp)
{
daemon_resolve_ugid(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)
}
}
-void daemon_exit(struct daemon_params *dp)
+void
+daemon_exit(struct daemon_params *dp)
{
if (dp->pid_file)
{
}
}
+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)
**/
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