From ae79d159182044ad450f6c189c231f7ddbbdc918 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Mon, 28 Dec 2015 15:07:11 +0100 Subject: daemonise: support keeping arbitrary fd:s open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- include/unistd.h | 23 ++++++++++- src/unistd/daemonise.c | 103 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 118 insertions(+), 8 deletions(-) diff --git a/include/unistd.h b/include/unistd.h index f4d699c..29a65c6 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -1075,6 +1075,12 @@ int daemon(int, int) */ #define DAEMONISE_KEEP_STDOUT 512 +/** + * Enables you to select additional + * file descritors to keep open. + */ +#define DAEMONISE_KEEP_FDS 1024 + /** * Daemonise the process. This means to: * @@ -1156,25 +1162,40 @@ int daemon(int, int) * - `DAEMONISE_CLOSE_STDERR` * - `DAEMONISE_KEEP_STDIN` * - `DAEMONISE_KEEP_STDOUT` + * - `DAEMONISE_KEEP_FDS` + * @parma ... Enabled if `DAEMONISE_KEEP_FDS` is used, + * do not add anything if `DAEMONISE_KEEP_FDS` + * is unused. This is a `-1`-terminated list + * of file descritors to keep open. 0, 1, and 2 + * are implied by `DAEMONISE_KEEP_STDIN`, + * `DAEMONISE_KEEP_STDOUT`, and `DAEMONISE_KEEP_STDERR`, + * respectively. All arguments are of type `int`. * @return Zero on success, -1 on error. * * @throws EEXIST The PID file already exists on the system. * Unless your daemon supervisor removs old * PID files, this could mean that the daemon * has exited without removing the PID file. + * @throws EINVAL `flags` contains an unsupported bit, both + * `DAEMONISE_KEEP_STDERR` and `DAEMONISE_CLOSE_STDERR` + * are set, or both `DAEMONISE_CLOSE_STDERR` and + * `DAEMONISE_KEEP_FDS` are set whilst `2` is + * in the list of file descriptor not to close. * @throws Any error specified for signal(3). * @throws Any error specified for sigemptyset(3). * @throws Any error specified for sigprocmask(3). * @throws Any error specified for chdir(3). * @throws Any error specified for pipe(3). + * @throws Any error specified for dup(3). * @throws Any error specified for dup2(3). * @throws Any error specified for fork(3). * @throws Any error specified for setsid(3). * @throws Any error specified for open(3). + * @throws Any error specified for malloc(3). * * @since Always. */ -int daemonise(const char*, int) +int daemonise(const char*, int, ...) __GCC_ONLY(__attribute__((__nonnull__, __warn_unused_result__))); /** diff --git a/src/unistd/daemonise.c b/src/unistd/daemonise.c index 7b0bc56..e61b72f 100644 --- a/src/unistd/daemonise.c +++ b/src/unistd/daemonise.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include @@ -41,6 +43,41 @@ static char* __pidfile = NULL; +/** + * Wrapper for `dup` create a new file descriptor + * with a number not less than 3. + * + * @param old The file descriptor to duplicate. + * @return The new file descriptor, -1 on error. + * + * @throws Any error specified for dup(3). + */ +static int dup_at_least_3(int old) +{ + int intermediary[] = { -1, -1, -1 }; + int i, saved_errno; + + while (old < 3) + { + if (old = dup(old), old == -1) + goto fail; + assert(i < 3); + if (i >= 3) + abort(); + intermediary[i++] = old; + } + + fail: + saved_errno = errno; + if (intermediary[0] >= 0) close(intermediary[0]); + if (intermediary[1] >= 0) close(intermediary[1]); + if (intermediary[2] >= 0) close(intermediary[2]); + errno = saved_errno; + return old; +} + + + /** * Daemonise the process. This means to: * @@ -122,25 +159,40 @@ static char* __pidfile = NULL; * - `DAEMONISE_CLOSE_STDERR` * - `DAEMONISE_KEEP_STDIN` * - `DAEMONISE_KEEP_STDOUT` + * - `DAEMONISE_KEEP_FDS` + * @parma ... Enabled if `DAEMONISE_KEEP_FDS` is used, + * do not add anything if `DAEMONISE_KEEP_FDS` + * is unused. This is a `-1`-terminated list + * of file descritors to keep open. 0, 1, and 2 + * are implied by `DAEMONISE_KEEP_STDIN`, + * `DAEMONISE_KEEP_STDOUT`, and `DAEMONISE_KEEP_STDERR`, + * respectively. All arguments are of type `int`. * @return Zero on success, -1 on error. * * @throws EEXIST The PID file already exists on the system. * Unless your daemon supervisor removs old * PID files, this could mean that the daemon * has exited without removing the PID file. + * @throws EINVAL `flags` contains an unsupported bit, both + * `DAEMONISE_KEEP_STDERR` and `DAEMONISE_CLOSE_STDERR` + * are set, or both `DAEMONISE_CLOSE_STDERR` and + * `DAEMONISE_KEEP_FDS` are set whilst `2` is + * in the list of file descriptor not to close. * @throws Any error specified for signal(3). * @throws Any error specified for sigemptyset(3). * @throws Any error specified for sigprocmask(3). * @throws Any error specified for chdir(3). * @throws Any error specified for pipe(3). + * @throws Any error specified for dup(3). * @throws Any error specified for dup2(3). * @throws Any error specified for fork(3). * @throws Any error specified for setsid(3). * @throws Any error specified for open(3). + * @throws Any error specified for malloc(3). * * @since Always. */ -int daemonise(const char* name, int flags) +int daemonise(const char* name, int flags, ...) { #define t(...) do { if (__VA_ARGS__) goto fail; } while (0) @@ -151,18 +203,52 @@ int daemonise(const char* name, int flags) char** w; char* run; int i, closeerr, fd = -1; + char* keep = NULL; + int keepmax = 0; pid_t pid; + va_list args; int saved_errno; /* Validate flags. */ - if (flags & ~1023) + if (flags & ~2047) return errno = EINVAL, -1; if (flags & DAEMONISE_KEEP_STDERR) if (flags & DAEMONISE_CLOSE_STDERR) return errno = EINVAL, -1; + /* Find out which file descriptors not too close. */ + if ((flags & DAEMONISE_KEEP_FDS) == 0) + { + va_start(args, flags); + while (va_arg(args, int) >= 0) + if ((fd > 2) && (keepmax < fd)) + keepmax = fd; + va_end(args); + keep = calloc(keepmax + 1, sizeof(char)); + t (keep == NULL); + va_start(args, flags); + while ((fd = va_arg(args, int)) >= 0) + switch (fd) + { + case 0: flags |= DAEMONISE_KEEP_STDIN; break; + case 1: flags |= DAEMONISE_KEEP_STDOUT; break; + case 2: flags |= DAEMONISE_KEEP_STDERR; + if (flags & DAEMONISE_CLOSE_STDERR) + return free(keep), errno = EINVAL, -1; + break; + default: + keep[fd] = 1; + break; + } + va_end(args); + fd = -1; + } + /* We assume that the maximum file descriptor is not extremely large. + * We also assume the number of file descriptors too keep is very small, + * but this does not affect us. */ + /* Close all files except stdin, stdout, and stderr. */ if ((flags & DAEMONISE_NO_CLOSE) == 0) { @@ -171,8 +257,10 @@ int daemonise(const char* name, int flags) for (i = 3; (rlim_t)i < rlimit.rlim_cur; i++) /* File descriptors with numbers above and including * `rlimit.rlim_cur` cannot be created. They cause EBADF. */ - close(i); + if ((keep == NULL) || (keep[i] == 0)) + close(i); } + free(keep), keep = NULL; /* Reset all signal handlers. */ if ((flags & DAEMONISE_NO_SIG_DFL) == 0) @@ -205,12 +293,12 @@ int daemonise(const char* name, int flags) /* Create a channel for letting the original process know when to exit. */ if (pipe(pipe_rw)) t ((pipe_rw[0] = pipe_rw[1] = -1)); - t (dup2(pipe_rw[0], 10) == -1); + t (fd = dup_at_least_3(pipe_rw[0]), fd == -1); close(pipe_rw[0]); - pipe_rw[0] = 10; - t (dup2(pipe_rw[1], 11) == -1); + pipe_rw[0] = fd; + t (fd = dup_at_least_3(pipe_rw[1]), fd == -1); close(pipe_rw[1]); - pipe_rw[1] = 11; + pipe_rw[1] = fd; /* Become a background process. */ t (pid = fork(), pid == -1); @@ -289,6 +377,7 @@ int daemonise(const char* name, int flags) if (pipe_rw[0] >= 0) close(pipe_rw[0]); if (pipe_rw[1] >= 0) close(pipe_rw[1]); if (fd >= 0) close(fd); + free(keep); errno = saved_errno; return -1; } -- cgit v1.2.3-70-g09d2