/*
* UCW Library -- Daemonization
*
- * (c) 2012 Martin Mares <mj@ucw.cz>
+ * (c) 2012--2014 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 <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/types.h>
+#include <sys/stat.h>
#include <sys/file.h>
-#include <sys/wait.h>
-static void
+void
daemon_resolve_ugid(struct daemon_params *dp)
{
// Resolve user name
{
if (u[0] == '#')
{
- uns id;
- const char *err = str_to_uns(&id, u, NULL, 10 | STN_WHOLE);
+ uint id;
+ const char *err = str_to_uint(&id, u, NULL, 10 | STN_WHOLE);
if (err)
die("Cannot parse user `%s': %s", u, err);
dp->run_as_uid = id;
{
if (g[0] == '#')
{
- uns id;
- const char *err = str_to_uns(&id, g, NULL, 10 | STN_WHOLE);
+ uint id;
+ const char *err = str_to_uint(&id, g, NULL, 10 | STN_WHOLE);
if (err)
die("Cannot parse group `%s': %s", g, err);
dp->run_as_gid = id;
}
}
+void daemon_switch_ugid(struct daemon_params *dp)
+{
+ 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);
+}
+
void
daemon_init(struct daemon_params *dp)
{
daemon_resolve_ugid(dp);
+ if (dp->flags & DAEMON_FLAG_SIMULATE)
+ return;
+
if (dp->pid_file)
{
// Check that PID file path is absolute
void
daemon_run(struct daemon_params *dp, void (*body)(struct daemon_params *dp))
{
+ if (dp->flags & DAEMON_FLAG_SIMULATE)
+ {
+ body(dp);
+ return;
+ }
+
// 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);
+ daemon_switch_ugid(dp);
// Create a new session and close stdio
setsid();
void
daemon_exit(struct daemon_params *dp)
{
+ if (dp->flags & DAEMON_FLAG_SIMULATE)
+ return;
+
if (dp->pid_file)
{
if (unlink(dp->pid_file) < 0)
}
}
-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 will_wait)
-{
- 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 | (will_wait ? 0 : LOCK_NB)) >= 0)
- {
- // The lock file is stale
- close(pid_fd);
- return 0;
- }
+#ifdef TEST
- if (errno != EINTR && errno != EWOULDBLOCK)
- {
- daemon_control_err(dc, "Cannot lock PID file `%s': %m", dc->pid_file);
- goto fail;
- }
+#include <signal.h>
- 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;
+static volatile sig_atomic_t terminate;
-fail:
- close(pid_fd);
- return -1;
+static void term_handler(int sig UNUSED)
+{
+ msg(L_INFO | L_SIGHANDLER, "SIGTERM received, terminating in a while");
+ terminate = 1;
}
-enum daemon_control_status
-daemon_control(struct daemon_control_params *dc)
+static void hup_handler(int sig UNUSED)
{
- enum daemon_control_status st = DAEMON_STATUS_ERROR;
+ msg(L_INFO | L_SIGHANDLER, "SIGHUP received");
+}
- 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);
+static void body(struct daemon_params *dp)
+{
+ log_fork();
+ msg(L_INFO, "Daemon is running");
+ msg(L_INFO, "uid=%d/%d gid=%d/%d", (int) getuid(), (int) geteuid(), (int) getgid(), (int) getegid());
- // Read the PID file
- int pid = daemon_read_pid(dc, 0);
- if (pid < 0)
- goto done;
+ struct sigaction sa_term = { .sa_handler = term_handler };
+ struct sigaction sa_hup = { .sa_handler = hup_handler };
+ if (sigaction(SIGTERM, &sa_term, NULL) < 0 ||
+ sigaction(SIGHUP, &sa_hup, NULL) < 0)
+ ASSERT(0);
- switch (dc->action)
+ while (!terminate)
{
- 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, 0);
- 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)
+ if (!sleep(60))
{
- daemon_control_err(dc, "Cannot send signal %d: %m", dc->signal);
- goto done;
+ msg(L_INFO, "Timeout elapsed, terminating in a while");
+ break;
}
- pid = daemon_read_pid(dc, 1);
- ASSERT(pid <= 0);
- if (!pid)
- st = DAEMON_STATUS_OK;
- 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)
-{
- log_fork();
- msg(L_INFO, "Daemon is running");
- msg(L_INFO, "uid=%d/%d gid=%d/%d", (int) getuid(), (int) geteuid(), (int) getgid(), (int) getegid());
- sleep(60);
+ sleep(2);
msg(L_INFO, "Daemon is shutting down");
daemon_exit(dp);
}