summaryrefslogtreecommitdiff
path: root/libdaemon/dfork.c
diff options
context:
space:
mode:
Diffstat (limited to 'libdaemon/dfork.c')
-rw-r--r--libdaemon/dfork.c730
1 files changed, 730 insertions, 0 deletions
diff --git a/libdaemon/dfork.c b/libdaemon/dfork.c
new file mode 100644
index 0000000..783033f
--- /dev/null
+++ b/libdaemon/dfork.c
@@ -0,0 +1,730 @@
+/***
+ This file is part of libdaemon.
+
+ Copyright 2003-2008 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <assert.h>
+#include <sys/ioctl.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <dirent.h>
+
+#include "dfork.h"
+#include "dnonblock.h"
+#include "dlog.h"
+
+#if defined(_NSIG) /* On glibc NSIG does not count RT signals */
+# define SIGNAL_UPPER_BOUND _NSIG
+#elif defined(NSIG) /* Solaris defines just this */
+# define SIGNAL_UPPER_BOUND NSIG
+#else
+# error "Unknown upper bound for signals"
+#endif
+
+static int _daemon_retval_pipe[2] = { -1, -1 };
+
+static int _null_open(int f, int fd) {
+ int fd2;
+
+ if ((fd2 = open("/dev/null", f)) < 0)
+ return -1;
+
+ if (fd2 == fd)
+ return fd;
+
+ if (dup2(fd2, fd) < 0)
+ return -1;
+
+ close(fd2);
+ return fd;
+}
+
+static ssize_t atomic_read(int fd, void *d, size_t l) {
+ ssize_t t = 0;
+
+ while (l > 0) {
+ ssize_t r;
+
+ if ((r = read(fd, d, l)) <= 0) {
+
+ if (r < 0)
+ return t > 0 ? t : -1;
+ else
+ return t;
+ }
+
+ t += r;
+ d = (char*) d + r;
+ l -= r;
+ }
+
+ return t;
+}
+
+static ssize_t atomic_write(int fd, const void *d, size_t l) {
+ ssize_t t = 0;
+
+ while (l > 0) {
+ ssize_t r;
+
+ if ((r = write(fd, d, l)) <= 0) {
+
+ if (r < 0)
+ return t > 0 ? t : -1;
+ else
+ return t;
+ }
+
+ t += r;
+ d = (const char*) d + r;
+ l -= r;
+ }
+
+ return t;
+}
+
+static int move_fd_up(int *fd) {
+ assert(fd);
+
+ while (*fd <= 2) {
+ if ((*fd = dup(*fd)) < 0) {
+ daemon_log(LOG_ERR, "dup(): %s", strerror(errno));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void sigchld(int s) {
+}
+
+pid_t daemon_fork(void) {
+ pid_t pid;
+ int pipe_fds[2] = {-1, -1};
+ struct sigaction sa_old, sa_new;
+ sigset_t ss_old, ss_new;
+ int saved_errno;
+
+ memset(&sa_new, 0, sizeof(sa_new));
+ sa_new.sa_handler = sigchld;
+ sa_new.sa_flags = SA_RESTART;
+
+ if (sigemptyset(&ss_new) < 0) {
+ daemon_log(LOG_ERR, "sigemptyset() failed: %s", strerror(errno));
+ return (pid_t) -1;
+ }
+
+ if (sigaddset(&ss_new, SIGCHLD) < 0) {
+ daemon_log(LOG_ERR, "sigaddset() failed: %s", strerror(errno));
+ return (pid_t) -1;
+ }
+
+ if (sigaction(SIGCHLD, &sa_new, &sa_old) < 0) {
+ daemon_log(LOG_ERR, "sigaction() failed: %s", strerror(errno));
+ return (pid_t) -1;
+ }
+
+ if (sigprocmask(SIG_UNBLOCK, &ss_new, &ss_old) < 0) {
+ daemon_log(LOG_ERR, "sigprocmask() failed: %s", strerror(errno));
+
+ saved_errno = errno;
+ sigaction(SIGCHLD, &sa_old, NULL);
+ errno = saved_errno;
+
+ return (pid_t) -1;
+ }
+
+ if (pipe(pipe_fds) < 0) {
+ daemon_log(LOG_ERR, "pipe() failed: %s", strerror(errno));
+
+ saved_errno = errno;
+ sigaction(SIGCHLD, &sa_old, NULL);
+ sigprocmask(SIG_SETMASK, &ss_old, NULL);
+ errno = saved_errno;
+
+ return (pid_t) -1;
+ }
+
+ if ((pid = fork()) < 0) { /* First fork */
+ daemon_log(LOG_ERR, "First fork() failed: %s", strerror(errno));
+
+ saved_errno = errno;
+ close(pipe_fds[0]);
+ close(pipe_fds[1]);
+ sigaction(SIGCHLD, &sa_old, NULL);
+ sigprocmask(SIG_SETMASK, &ss_old, NULL);
+ errno = saved_errno;
+
+ return (pid_t) -1;
+
+ } else if (pid == 0) {
+ pid_t dpid;
+
+ /* First child. Now we are sure not to be a session leader or
+ * process group leader anymore, i.e. we know that setsid()
+ * will succeed. */
+
+ if (daemon_log_use & DAEMON_LOG_AUTO)
+ daemon_log_use = DAEMON_LOG_SYSLOG;
+
+ if (close(pipe_fds[0]) < 0) {
+ daemon_log(LOG_ERR, "close() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ /* Move file descriptors up*/
+ if (move_fd_up(&pipe_fds[1]) < 0)
+ goto fail;
+
+ if (_daemon_retval_pipe[0] >= 0 && move_fd_up(&_daemon_retval_pipe[0]) < 0)
+ goto fail;
+ if (_daemon_retval_pipe[1] >= 0 && move_fd_up(&_daemon_retval_pipe[1]) < 0)
+ goto fail;
+
+ if (_null_open(O_RDONLY, 0) < 0) {
+ daemon_log(LOG_ERR, "Failed to open /dev/null for STDIN: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (_null_open(O_WRONLY, 1) < 0) {
+ daemon_log(LOG_ERR, "Failed to open /dev/null for STDOUT: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (_null_open(O_WRONLY, 2) < 0) {
+ daemon_log(LOG_ERR, "Failed to open /dev/null for STDERR: %s", strerror(errno));
+ goto fail;
+ }
+
+ /* Create a new session. This will create a new session and a
+ * new process group for us and we will be the ledaer of
+ * both. This should always succeed because we cannot be the
+ * process group leader because we just forked. */
+ if (setsid() < 0) {
+ daemon_log(LOG_ERR, "setsid() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ umask(0077);
+
+ if (chdir("/") < 0) {
+ daemon_log(LOG_ERR, "chdir() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if ((pid = fork()) < 0) { /* Second fork */
+ daemon_log(LOG_ERR, "Second fork() failed: %s", strerror(errno));
+ goto fail;
+
+ } else if (pid == 0) {
+ /* Second child. Our father will exit right-away. That way
+ * we can be sure that we are a child of init now, even if
+ * the process which spawned us stays around for a longer
+ * time. Also, since we are no session leader anymore we
+ * can be sure that we will never acquire a controlling
+ * TTY. */
+
+ if (sigaction(SIGCHLD, &sa_old, NULL) < 0) {
+ daemon_log(LOG_ERR, "close() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (sigprocmask(SIG_SETMASK, &ss_old, NULL) < 0) {
+ daemon_log(LOG_ERR, "sigprocmask() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (signal(SIGTTOU, SIG_IGN) == SIG_ERR) {
+ daemon_log(LOG_ERR, "signal(SIGTTOU, SIG_IGN) failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (signal(SIGTTIN, SIG_IGN) == SIG_ERR) {
+ daemon_log(LOG_ERR, "signal(SIGTTIN, SIG_IGN) failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (signal(SIGTSTP, SIG_IGN) == SIG_ERR) {
+ daemon_log(LOG_ERR, "signal(SIGTSTP, SIG_IGN) failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ dpid = getpid();
+ if (atomic_write(pipe_fds[1], &dpid, sizeof(dpid)) != sizeof(dpid)) {
+ daemon_log(LOG_ERR, "write() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (close(pipe_fds[1]) < 0) {
+ daemon_log(LOG_ERR, "close() failed: %s", strerror(errno));
+ goto fail;
+ }
+
+ return 0;
+
+ } else {
+ /* Second father */
+ close(pipe_fds[1]);
+ _exit(0);
+ }
+
+ fail:
+ dpid = (pid_t) -1;
+
+ if (atomic_write(pipe_fds[1], &dpid, sizeof(dpid)) != sizeof(dpid))
+ daemon_log(LOG_ERR, "Failed to write error PID: %s", strerror(errno));
+
+ close(pipe_fds[1]);
+ _exit(0);
+
+ } else {
+ /* First father */
+ pid_t dpid;
+
+ close(pipe_fds[1]);
+
+ if (waitpid(pid, NULL, WUNTRACED) < 0) {
+ saved_errno = errno;
+ close(pipe_fds[0]);
+ sigaction(SIGCHLD, &sa_old, NULL);
+ sigprocmask(SIG_SETMASK, &ss_old, NULL);
+ errno = saved_errno;
+ return -1;
+ }
+
+ sigprocmask(SIG_SETMASK, &ss_old, NULL);
+ sigaction(SIGCHLD, &sa_old, NULL);
+
+ if (atomic_read(pipe_fds[0], &dpid, sizeof(dpid)) != sizeof(dpid)) {
+ daemon_log(LOG_ERR, "Failed to read daemon PID.");
+ dpid = (pid_t) -1;
+ errno = EINVAL;
+ } else if (dpid == (pid_t) -1)
+ errno = EIO;
+
+ saved_errno = errno;
+ close(pipe_fds[0]);
+ errno = saved_errno;
+
+ return dpid;
+ }
+}
+
+int daemon_retval_init(void) {
+
+ if (_daemon_retval_pipe[0] < 0 || _daemon_retval_pipe[1] < 0) {
+
+ if (pipe(_daemon_retval_pipe) < 0) {
+ daemon_log(LOG_ERR, "pipe(): %s", strerror(errno));
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+void daemon_retval_done(void) {
+ int saved_errno = errno;
+
+ if (_daemon_retval_pipe[0] >= 0)
+ close(_daemon_retval_pipe[0]);
+
+ if (_daemon_retval_pipe[1] >= 0)
+ close(_daemon_retval_pipe[1]);
+
+ _daemon_retval_pipe[0] = _daemon_retval_pipe[1] = -1;
+
+ errno = saved_errno;
+}
+
+int daemon_retval_send(int i) {
+ ssize_t r;
+
+ if (_daemon_retval_pipe[1] < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ r = atomic_write(_daemon_retval_pipe[1], &i, sizeof(i));
+
+ daemon_retval_done();
+
+ if (r != sizeof(i)) {
+
+ if (r < 0)
+ daemon_log(LOG_ERR, "write() failed while writing return value to pipe: %s", strerror(errno));
+ else {
+ daemon_log(LOG_ERR, "write() too short while writing return value from pipe");
+ errno = EINVAL;
+ }
+
+ return -1;
+ }
+
+ return 0;
+}
+
+int daemon_retval_wait(int timeout) {
+ ssize_t r;
+ int i;
+
+ if (timeout > 0) {
+ struct timeval tv;
+ int s;
+ fd_set fds;
+
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ FD_ZERO(&fds);
+ FD_SET(_daemon_retval_pipe[0], &fds);
+
+ if ((s = select(FD_SETSIZE, &fds, 0, 0, &tv)) != 1) {
+
+ if (s < 0)
+ daemon_log(LOG_ERR, "select() failed while waiting for return value: %s", strerror(errno));
+ else {
+ errno = ETIMEDOUT;
+ daemon_log(LOG_ERR, "Timeout reached while wating for return value");
+ }
+
+ return -1;
+ }
+ }
+
+ if ((r = atomic_read(_daemon_retval_pipe[0], &i, sizeof(i))) != sizeof(i)) {
+
+ if (r < 0)
+ daemon_log(LOG_ERR, "read() failed while reading return value from pipe: %s", strerror(errno));
+ else if (r == 0) {
+ daemon_log(LOG_ERR, "read() failed with EOF while reading return value from pipe.");
+ errno = EINVAL;
+ } else if (r > 0) {
+ daemon_log(LOG_ERR, "read() too short while reading return value from pipe.");
+ errno = EINVAL;
+ }
+
+ return -1;
+ }
+
+ daemon_retval_done();
+
+ return i;
+}
+
+int daemon_close_all(int except_fd, ...) {
+ va_list ap;
+ int n = 0, i, r;
+ int *p;
+ int saved_errno;
+
+ va_start(ap, except_fd);
+
+ if (except_fd >= 0)
+ for (n = 1; va_arg(ap, int) >= 0; n++)
+ ;
+
+ va_end(ap);
+
+ if (!(p = malloc(sizeof(int) * (n+1))))
+ return -1;
+
+ va_start(ap, except_fd);
+
+ i = 0;
+ if (except_fd >= 0) {
+ int fd;
+ p[i++] = except_fd;
+
+ while ((fd = va_arg(ap, int)) >= 0)
+ p[i++] = fd;
+ }
+ p[i] = -1;
+
+ va_end(ap);
+
+ r = daemon_close_allv(p);
+
+ saved_errno = errno;
+ free(p);
+ errno = saved_errno;
+
+ return r;
+}
+
+/** Same as daemon_close_all but takes an array of fds, terminated by -1 */
+int daemon_close_allv(const int except_fds[]) {
+ struct rlimit rl;
+ int fd, maxfd;
+
+#ifdef __linux__
+ int saved_errno;
+
+ DIR *d;
+
+ if ((d = opendir("/proc/self/fd"))) {
+
+ struct dirent *de;
+
+ while ((de = readdir(d))) {
+ int found;
+ long l;
+ char *e = NULL;
+ int i;
+
+ if (de->d_name[0] == '.')
+ continue;
+
+ errno = 0;
+ l = strtol(de->d_name, &e, 10);
+ if (errno != 0 || !e || *e) {
+ closedir(d);
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd = (int) l;
+
+ if ((long) fd != l) {
+ closedir(d);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (fd < 3)
+ continue;
+
+ if (fd == dirfd(d))
+ continue;
+
+ if (fd == _daemon_retval_pipe[1])
+ continue;
+
+ found = 0;
+ for (i = 0; except_fds[i] >= 0; i++)
+ if (except_fds[i] == fd) {
+ found = 1;
+ break;
+ }
+
+ if (found)
+ continue;
+
+ if (close(fd) < 0) {
+ saved_errno = errno;
+ closedir(d);
+ errno = saved_errno;
+
+ return -1;
+ }
+
+ if (fd == _daemon_retval_pipe[0])
+ _daemon_retval_pipe[0] = -1; /* mark as closed */
+ }
+
+ closedir(d);
+ return 0;
+ }
+
+#endif
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) > 0)
+ maxfd = (int) rl.rlim_max;
+ else
+ maxfd = sysconf(_SC_OPEN_MAX);
+
+ for (fd = 3; fd < maxfd; fd++) {
+ int i, found;
+
+ if (fd == _daemon_retval_pipe[1])
+ continue;
+
+ found = 0;
+ for (i = 0; except_fds[i] >= 0; i++)
+ if (except_fds[i] == fd) {
+ found = 1;
+ break;
+ }
+
+ if (found)
+ continue;
+
+ if (close(fd) < 0 && errno != EBADF)
+ return -1;
+
+ if (fd == _daemon_retval_pipe[0])
+ _daemon_retval_pipe[0] = -1; /* mark as closed */
+ }
+
+ return 0;
+}
+
+int daemon_unblock_sigs(int except, ...) {
+ va_list ap;
+ int n = 0, i, r;
+ int *p;
+ int saved_errno;
+
+ va_start(ap, except);
+
+ if (except >= 1)
+ for (n = 1; va_arg(ap, int) >= 0; n++)
+ ;
+
+ va_end(ap);
+
+ if (!(p = malloc(sizeof(int) * (n+1))))
+ return -1;
+
+ va_start(ap, except);
+
+ i = 0;
+ if (except >= 1) {
+ int sig;
+ p[i++] = except;
+
+ while ((sig = va_arg(ap, int)) >= 0)
+ p[i++] = sig;
+ }
+ p[i] = -1;
+
+ va_end(ap);
+
+ r = daemon_unblock_sigsv(p);
+
+ saved_errno = errno;
+ free(p);
+ errno = saved_errno;
+
+ return r;
+}
+
+int daemon_unblock_sigsv(const int except[]) {
+ int i;
+ sigset_t ss;
+
+ if (sigemptyset(&ss) < 0)
+ return -1;
+
+ for (i = 0; except[i] > 0; i++)
+ if (sigaddset(&ss, except[i]) < 0)
+ return -1;
+
+ return sigprocmask(SIG_SETMASK, &ss, NULL);
+}
+
+int daemon_reset_sigs(int except, ...) {
+ va_list ap;
+ int n = 0, i, r;
+ int *p;
+ int saved_errno;
+
+ va_start(ap, except);
+
+ if (except >= 1)
+ for (n = 1; va_arg(ap, int) >= 0; n++)
+ ;
+
+ va_end(ap);
+
+ if (!(p = malloc(sizeof(int) * (n+1))))
+ return -1;
+
+ va_start(ap, except);
+
+ i = 0;
+ if (except >= 1) {
+ int sig;
+ p[i++] = except;
+
+ while ((sig = va_arg(ap, int)) >= 0)
+ p[i++] = sig;
+ }
+ p[i] = -1;
+
+ va_end(ap);
+
+ r = daemon_reset_sigsv(p);
+
+ saved_errno = errno;
+ free(p);
+ errno = saved_errno;
+
+ return r;
+}
+
+int daemon_reset_sigsv(const int except[]) {
+ int sig;
+
+ for (sig = 1; sig < SIGNAL_UPPER_BOUND; sig++) {
+ int reset = 1;
+
+ switch (sig) {
+ case SIGKILL:
+ case SIGSTOP:
+ reset = 0;
+ break;
+
+ default: {
+ int i;
+
+ for (i = 0; except[i] > 0; i++) {
+ if (sig == except[i]) {
+ reset = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ if (reset) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+
+ /* On Linux the first two RT signals are reserved by
+ * glibc, and sigaction() will return EINVAL for them. */
+ if ((sigaction(sig, &sa, NULL) < 0))
+ if (errno != EINVAL)
+ return -1;
+ }
+ }
+
+ return 0;
+}