/* daemoninze.c - functions for properly daemonising an application Copyright (C) 2014-2022 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_PTHREAD_H #include #endif /* HAVE_PTHREAD_H */ #include "daemonize.h" #include "log.h" /* the write end of a pipe that is used to signal the fact that the child process has finished initialising (see daemonize_daemon() and daemonize_ready() for details) */ static int daemonizefd = -1; void daemonize_closefds(void) { /* close all file descriptors (except stdin/out/err) */ #ifdef HAVE_CLOSEFROM closefrom(3); #else int i; i = sysconf(_SC_OPEN_MAX) - 1; /* if the system does not have OPEN_MAX just close the first 32 and hope we closed enough */ if (i < 0) i = 32; for (; i > 2; i--) close(i); #endif } void daemonize_redirect_stdio(void) { /* close stdin, stdout and stderr */ (void)close(0); /* stdin */ (void)close(1); /* stdout */ (void)close(2); /* stderr */ /* reconnect to /dev/null */ (void)open("/dev/null", O_RDWR); /* stdin, fd=0 */ (void)dup(0); /* stdout, fd=1 */ (void)dup(0); /* stderr, fd=2 */ } /* try to fill the buffer until EOF or error */ static int read_response(int fd, char *buffer, size_t bufsz) { int rc; size_t r = 0; while (r < bufsz) { rc = read(fd, buffer + r, bufsz - r); if (rc == 0) break; else if (rc > 0) r += rc; else if ((errno == EINTR) || (errno == EAGAIN)) continue; /* ignore these errors and try again */ else { log_log(LOG_ERR, "read_response(): read() failed: %s", strerror(errno)); return -1; } } return r; } /* The parent process calling daemonize_daemon() will end up here on success */ static int wait_for_response(int fd) { int i, l, rc; char buffer[1024]; /* read return code */ errno = 0; i = read_response(fd, (void *)&rc, sizeof(int)); log_log(LOG_DEBUG, "DEBUG: wait_for_response(): i=%d, rc=%d", i, rc); if (i != sizeof(int)) { log_log(LOG_ERR, "wait_for_response(): read_response() returned %d (expected %d)", i, (int)sizeof(int)); if (errno == 0) #ifdef ENODATA errno = ENODATA; #else errno = ENOATTR; #endif return -1; } /* read string length */ i = read_response(fd, (void *)&l, sizeof(int)); log_log(LOG_DEBUG, "DEBUG: wait_for_response(): i=%d, l=%d", i, l); if ((i != sizeof(int)) || (l <= 0)) _exit(rc); /* read string */ if ((size_t)l > (sizeof(buffer) - 1)) l = sizeof(buffer) - 1; i = read_response(fd, buffer, l); buffer[l] = '\0'; if (i == l) fprintf(stderr, "%s", buffer); _exit(rc); } static void closefd(void) { if (daemonizefd >= 0) { close(daemonizefd); daemonizefd = -1; } } int daemonize_daemon(void) { int pipefds[2]; int i; /* set up a pipe for communication */ if (pipe(pipefds) < 0) { log_log(LOG_ERR, "pipe() failed: %s", strerror(errno)); return -1; } /* set O_NONBLOCK on the write end to ensure that a call to daemonize_ready() will not lock the application */ if ((i = fcntl(pipefds[1], F_GETFL, 0)) < 0) { log_log(LOG_ERR, "fcntl() failed: %s", strerror(errno)); close(pipefds[0]); close(pipefds[1]); return -1; } if (fcntl(pipefds[1], F_SETFL, i | O_NONBLOCK) < 0) { log_log(LOG_ERR, "fcntl() failed: %s", strerror(errno)); close(pipefds[0]); close(pipefds[1]); return -1; } /* fork() and exit() to detach from the parent process */ switch (fork()) { case 0: /* we are the child, close read end of pipe */ close(pipefds[0]); break; case -1: /* we are the parent, but have an error */ log_log(LOG_ERR, "fork() failed: %s", strerror(errno)); close(pipefds[0]); close(pipefds[1]); return -1; default: /* we are the parent, close write end and wait for information */ close(pipefds[1]); return wait_for_response(pipefds[0]); } /* become process leader */ if (setsid() < 0) { log_log(LOG_ERR, "setsid() failed: %s", strerror(errno)); close(pipefds[1]); _exit(EXIT_FAILURE); } /* fork again so we cannot allocate a pty */ switch (fork()) { case 0: /* we are the child */ break; case -1: /* we are the parent, but have an error */ log_log(LOG_ERR, "fork() failed: %s", strerror(errno)); close(pipefds[1]); _exit(EXIT_FAILURE); default: /* we are the parent and we're done */ close(pipefds[1]); _exit(EXIT_SUCCESS); } daemonizefd = pipefds[1]; /* close the file descriptor on exec (ignore errors) */ fcntl(daemonizefd, F_SETFD, FD_CLOEXEC); #ifdef HAVE_PTHREAD_ATFORK /* handle any other forks by closing daemonizefd first */ (void)pthread_atfork(NULL, NULL, closefd); #endif /* HAVE_PTHREAD_ATFORK */ return 0; } void daemonize_ready(int status, const char *message) { int l; if (daemonizefd >= 0) { /* we ignore any errors writing */ (void)write(daemonizefd, &status, sizeof(int)); if ((message == NULL) || (message[0] == '\0')) { l = 0; (void)write(daemonizefd, &l, sizeof(int)); } else { l = strlen(message); (void)write(daemonizefd, &l, sizeof(int)); (void)write(daemonizefd, message, l); } (void)close(daemonizefd); daemonizefd = -1; } }