From: Martin Mares Date: Thu, 12 Jul 2012 16:38:12 +0000 (+0200) Subject: Daemon: An attempt at daemonization helper X-Git-Tag: v5.99~160 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=d657737a4257516391ab62f86a4aedc0cc14809c;p=libucw.git Daemon: An attempt at daemonization helper --- diff --git a/ucw/Makefile b/ucw/Makefile index af6219bf..1d6db190 100644 --- a/ucw/Makefile +++ b/ucw/Makefile @@ -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 index 00000000..0c83bdeb --- /dev/null +++ b/ucw/daemon.c @@ -0,0 +1,196 @@ +/* + * UCW Library -- Daemonization + * + * (c) 2012 Martin Mares + * + * This software may be freely distributed and used according to the terms + * of the GNU Lesser General Public License. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 index 00000000..13f8c37a --- /dev/null +++ b/ucw/daemon.h @@ -0,0 +1,39 @@ +/* + * UCW Library -- Daemonization + * + * (c) 2012 Martin Mares + * + * 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 + +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