diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2014-01-02 16:57:44 +0100 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2014-01-02 22:50:20 +0100 |
commit | 42a1a3dbee96c209f5ccb97207d11f536253516c (patch) | |
tree | dc3b9827929f8a671583a3d50a67a43fd4e2a43f | |
parent | e3f0453c482a386012a7f6e6f3942bb9635a0c19 (diff) |
Properly daemonise nslcd and only exit when ready
This introduces a new daemonize module that provides functions for
closing all file descriptors, redirecting stdin/stdout/stderr to
/dev/null and a function for backgrounding an application while only
exiting the original process after the daemon process has indicated
readiness.
This is used to exit the original process only after the listening
socket has been set up and the worker threads have been started.
-rw-r--r-- | nslcd/Makefile.am | 5 | ||||
-rw-r--r-- | nslcd/daemonize.c | 191 | ||||
-rw-r--r-- | nslcd/daemonize.h | 67 | ||||
-rw-r--r-- | nslcd/nslcd.c | 41 |
4 files changed, 288 insertions, 16 deletions
diff --git a/nslcd/Makefile.am b/nslcd/Makefile.am index 4346f21..a17b4ce 100644 --- a/nslcd/Makefile.am +++ b/nslcd/Makefile.am @@ -1,7 +1,7 @@ # Makefile.am - use automake to generate Makefile.in # -# Copyright (C) 2006, 2007 West Consulting -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Arthur de Jong +# Copyright (C) 2006-2007 West Consulting +# Copyright (C) 2006-2014 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 @@ -26,6 +26,7 @@ AM_CFLAGS = $(PTHREAD_CFLAGS) nslcd_SOURCES = nslcd.c ../nslcd.h ../common/nslcd-prot.h \ ../compat/attrs.h \ log.c log.h \ + daemonize.c daemonize.h \ common.c common.h \ myldap.c myldap.h \ cfg.c cfg.h \ diff --git a/nslcd/daemonize.c b/nslcd/daemonize.c new file mode 100644 index 0000000..c9fb5e4 --- /dev/null +++ b/nslcd/daemonize.c @@ -0,0 +1,191 @@ +/* + daemoninze.c - functions for properly daemonising an application + + Copyright (C) 2014 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 <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "daemonize.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) +{ + int i; + /* close all file descriptors (except stdin/out/err) */ + 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 > 3; i--) + close(i); +} + +void daemonize_redirect_stdio(void) +{ + /* close stdin, stdout and stderr */ + close(0); /* stdin */ + close(1); /* stdout */ + close(2); /* stderr */ + /* reconnect to /dev/null */ + open("/dev/null", O_RDWR); /* stdin, fd=0 */ + dup(0); /* stdout, fd=1 */ + 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 + return -1; + } + return r; +} + +/* ihe 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 */ + i = read_response(fd, (void *)&rc, sizeof(int)); + if (i != sizeof(int)) + return -1; + /* read string length */ + i = read_response(fd, (void *)&l, sizeof(int)); + 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[sizeof(buffer) - 1] = '\0'; + if (i == l) + fprintf(stderr, "%s", buffer); + _exit(rc); +} + +int daemonize_daemon(void) +{ + int pipefds[2]; + int i; + /* set up a pipe for communication */ + if (pipe(pipefds) < 0) + 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) + { + close(pipefds[0]); + close(pipefds[1]); + return -1; + } + if (fcntl(pipefds[1], F_SETFL, i | O_NONBLOCK) < 0) + { + 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 */ + 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) + { + 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 */ + close(pipefds[1]); + _exit(EXIT_FAILURE); + default: + /* we are the parent and we're done */ + close(pipefds[1]); + _exit(EXIT_SUCCESS); + } + daemonizefd = pipefds[1]; + return 0; +} + +void daemonize_ready(int status, const char *message) +{ + if (daemonizefd >= 0) + { + /* we ignore any errors writing */ + write(daemonizefd, &status, sizeof(int)); + if ((message == NULL) || (message[0] == '\0')) + { + status = 0; + write(daemonizefd, &status, sizeof(int)); + } + else + { + status = strlen(message); + write(daemonizefd, &status, sizeof(int)); + write(daemonizefd, message, status); + } + close(daemonizefd); + daemonizefd = -1; + } +} diff --git a/nslcd/daemonize.h b/nslcd/daemonize.h new file mode 100644 index 0000000..8eb9885 --- /dev/null +++ b/nslcd/daemonize.h @@ -0,0 +1,67 @@ +/* + daemonize.h - definition of functions for daemonising an application + + Copyright (C) 2014 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 +*/ + +#ifndef NSLCD__DAEMONINZE_H +#define NSLCD__DAEMONINZE_H 1 + +/* + To properly run as a daemon an application should: + + - close all open file descriptors (see daemonize_closefds() for that) + - (re)set proper signal handlers and signal mask + - sanitise the environment + - fork() / setsid() / fork() to detach from terminal, become process + leader and run in the background (see daemonize_demon() for that) + - reconnect stdin/stdout/stderr to /dev/null (see + daemonize_redirect_stdio() for that) + - set the umask to a reasonable value + - chdir(/) to avoid locking any mounts + - drop privileges as appropriate + - chroot() if appropriate + - create and lock a pidfile + - exit the starting process if initialisation is complete (see + daemonize_ready() for that) +*/ + +/* This closes all open file descriptors, except stdin, stdout and stderr. */ +void daemonize_closefds(void); + +/* Redirect stdio, stdin and stderr to /dev/null. */ +void daemonize_redirect_stdio(void); + +/* Detach from the controlling terminal and run in the background. This + function does: + - double fork and exit first child + - in the first child call setsid() to detach from any terminal and + create an independent session + - keep the parent process waiting until a call to daemonize_ready() is + done by the deamon process + This function returns either an error which indicates that the + daemonizing failed for some reason (usually sets errno), or returns + without error indicating that the process has been daemonized. */ +int daemonize_daemon(void); + +/* Signal that the original parent may exit because the service has been + initialised. The status indicates the exit code of the original process and + message, if not NULL or an empty string, is printed to stderr. */ +void daemonize_ready(int status, const char *message); + +#endif /* not NSLCD__DAEMONINZE_H */ diff --git a/nslcd/nslcd.c b/nslcd/nslcd.c index b5881c4..bbe6547 100644 --- a/nslcd/nslcd.c +++ b/nslcd/nslcd.c @@ -2,7 +2,7 @@ nslcd.c - ldap local connection daemon Copyright (C) 2006 West Consulting - Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Arthur de Jong + Copyright (C) 2006-2014 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 @@ -53,7 +53,6 @@ #ifndef HAVE_GETOPT_LONG #include "compat/getopt_long.h" #endif /* not HAVE_GETOPT_LONG */ -#include "compat/daemon.h" #include <dlfcn.h> #include <libgen.h> #include <limits.h> @@ -65,6 +64,7 @@ #include "compat/attrs.h" #include "compat/getpeercred.h" #include "compat/socket.h" +#include "daemonize.h" /* read timeout is half a second because clients should send their request quickly, write timeout is 60 seconds because clients could be taking some @@ -117,7 +117,7 @@ static void display_version(FILE *fp) { fprintf(fp, "%s\n", PACKAGE_STRING); fprintf(fp, "Written by Luke Howard and Arthur de Jong.\n\n"); - fprintf(fp, "Copyright (C) 1997-2013 Luke Howard, Arthur de Jong and West Consulting\n" + fprintf(fp, "Copyright (C) 1997-2014 Luke Howard, Arthur de Jong and West Consulting\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); } @@ -649,13 +649,7 @@ int main(int argc, char *argv[]) struct timespec ts; #endif /* HAVE_PTHREAD_TIMEDJOIN_NP */ /* close all file descriptors (except stdin/out/err) */ - 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 > 3; i--) - close(i); + daemonize_closefds(); /* parse the command line */ parse_cmdline(argc, argv); /* clean the environment */ @@ -695,22 +689,34 @@ int main(int argc, char *argv[]) exit(EXIT_FAILURE); } } + /* change directory */ + if (chdir("/") != 0) + { + log_log(LOG_ERR, "chdir failed: %s", strerror(errno)); + exit(EXIT_FAILURE); + } /* normal check for pidfile locked */ if (is_locked(NSLCD_PIDFILE)) { - log_log(LOG_ERR, "daemon may already be active, cannot acquire lock (%s): %s", + log_log(LOG_ERR, "nslcd may already be active, cannot acquire lock (%s): %s", NSLCD_PIDFILE, strerror(errno)); exit(EXIT_FAILURE); } /* daemonize */ - if ((!nslcd_debugging) && (!nslcd_nofork) && (daemon(0, 0) < 0)) + if ((!nslcd_debugging) && (!nslcd_nofork)) { - log_log(LOG_ERR, "unable to daemonize: %s", strerror(errno)); - exit(EXIT_FAILURE); + if (daemonize_daemon() != 0) + { + log_log(LOG_ERR, "unable to daemonize: %s", strerror(errno)); + exit(EXIT_FAILURE); + } } /* intilialize logging */ if (!nslcd_debugging) + { + daemonize_redirect_stdio(); log_startlogging(); + } /* write pidfile */ create_pidfile(NSLCD_PIDFILE); /* log start */ @@ -719,6 +725,7 @@ int main(int argc, char *argv[]) if (atexit(exithandler)) { log_log(LOG_ERR, "atexit() failed: %s", strerror(errno)); + daemonize_ready(EXIT_FAILURE, "atexit() failed\n"); exit(EXIT_FAILURE); } /* create socket */ @@ -760,6 +767,7 @@ int main(int argc, char *argv[]) { log_log(LOG_ERR, "cannot setgid(%d): %s", (int)nslcd_cfg->gid, strerror(errno)); + daemonize_ready(EXIT_FAILURE, "cannot setgid()\n"); exit(EXIT_FAILURE); } log_log(LOG_DEBUG, "setgid(%d) done", (int)nslcd_cfg->gid); @@ -771,6 +779,7 @@ int main(int argc, char *argv[]) { log_log(LOG_ERR, "cannot setuid(%d): %s", (int)nslcd_cfg->uid, strerror(errno)); + daemonize_ready(EXIT_FAILURE, "cannot setuid()\n"); exit(EXIT_FAILURE); } log_log(LOG_DEBUG, "setuid(%d) done", (int)nslcd_cfg->uid); @@ -792,6 +801,7 @@ int main(int argc, char *argv[]) if (nslcd_threads == NULL) { log_log(LOG_CRIT, "main(): malloc() failed to allocate memory"); + daemonize_ready(EXIT_FAILURE, "malloc() failed to allocate memory\n"); exit(EXIT_FAILURE); } for (i = 0; i < nslcd_cfg->threads; i++) @@ -800,6 +810,7 @@ int main(int argc, char *argv[]) { log_log(LOG_ERR, "unable to start worker thread %d: %s", i, strerror(errno)); + daemonize_ready(EXIT_FAILURE, "unable to start worker thread\n"); exit(EXIT_FAILURE); } } @@ -813,6 +824,8 @@ int main(int argc, char *argv[]) install_sighandler(SIGTERM, sig_handler); install_sighandler(SIGUSR1, sig_handler); install_sighandler(SIGUSR2, SIG_IGN); + /* signal the starting process to exit because we can provide services now */ + daemonize_ready(EXIT_SUCCESS, NULL); /* wait until we received a signal */ while ((nslcd_receivedsignal == 0) || (nslcd_receivedsignal == SIGUSR1)) { |