/***
|
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;
|
}
|