diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2014-01-02 22:53:01 +0100 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2014-01-02 22:53:01 +0100 |
commit | 2b8fbc249e8781290e5a05b338b3bc1b700d05e6 (patch) | |
tree | 648612c4b5969837bfa0c3f54c40b3ad5a64c0fc | |
parent | e3f0453c482a386012a7f6e6f3942bb9635a0c19 (diff) | |
parent | 3afedc4a3c9c90d9bdb2bc72f6aca7e5cc9380e9 (diff) |
Only exit nslcd when daemon is ready
This removes a race condition between the exit of the initial nslcd
process (as started by the init script) and nslcd services being ready.
-rw-r--r-- | compat/Makefile.am | 3 | ||||
-rw-r--r-- | compat/daemon.c | 71 | ||||
-rw-r--r-- | compat/daemon.h | 34 | ||||
-rw-r--r-- | configure.ac | 13 | ||||
-rw-r--r-- | nslcd/Makefile.am | 5 | ||||
-rw-r--r-- | nslcd/daemonize.c | 209 | ||||
-rw-r--r-- | nslcd/daemonize.h | 67 | ||||
-rw-r--r-- | nslcd/nslcd.c | 41 |
8 files changed, 310 insertions, 133 deletions
diff --git a/compat/Makefile.am b/compat/Makefile.am index 66703c5..361c9be 100644 --- a/compat/Makefile.am +++ b/compat/Makefile.am @@ -1,6 +1,6 @@ # Makefile.am - use automake to generate Makefile.in # -# Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Arthur de Jong +# Copyright (C) 2008-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 @@ -23,7 +23,6 @@ AM_CPPFLAGS=-I$(top_srcdir) AM_CFLAGS = -fPIC EXTRA_DIST = getopt_long.c getopt_long.h \ - daemon.c daemon.h \ ether.c ether.h \ shell.h \ strndup.c strndup.h \ diff --git a/compat/daemon.c b/compat/daemon.c deleted file mode 100644 index e3d5aea..0000000 --- a/compat/daemon.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - daemon.c - implementation of daemon() for systems that lack it - - Copyright (C) 2002, 2003, 2008, 2012 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 "daemon.h" - -#include <unistd.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -int daemon(int nochdir, int noclose) -{ - /* change directory */ - if (!nochdir) - if (chdir("/") != 0) - return -1; - /* fork() and exit() to detach from the parent process */ - switch (fork()) - { - case 0: /* we are the child */ - break; - case -1: /* we are the parent, but have an error */ - return -1; - default: /* we are the parent and we're done */ - _exit(0); - } - /* become process leader */ - if (setsid() < 0) - { - return -1; - } - /* 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 */ - return -1; - default: /* we are the parent and we're done */ - _exit(0); - } - /* close stdin, stdout and stderr and reconnect to /dev/null */ - if (!noclose) - { - close(0); /* stdin */ - close(1); /* stdout */ - close(2); /* stderr */ - open("/dev/null", O_RDWR); /* stdin, fd=0 */ - dup(0); /* stdout, fd=1 */ - dup(0); /* stderr, fd=2 */ - } - return 0; -} diff --git a/compat/daemon.h b/compat/daemon.h deleted file mode 100644 index 0de27cc..0000000 --- a/compat/daemon.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - daemon.h - definition of daemon() for systems that lack it - - Copyright (C) 2002, 2003, 2008, 2011, 2012 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 COMPAT__DAEMON_H -#define COMPAT__DAEMON_H 1 - -#include <unistd.h> - -#if !HAVE_DECL_DAEMON -/* we define daemon() here because on some platforms the function is - undefined: deamonize process, optionally chdir to / and optionally - close stdin, strdout and stderr and redirect them to /dev/null */ -int daemon(int nochdir, int noclose); -#endif /* not HAVE_DECL_DAEMON */ - -#endif /* not COMPAT__DAEMON_H */ diff --git a/configure.ac b/configure.ac index 27d8e56..411c9e5 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # # Copyright (C) 2006 Luke Howard # 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 @@ -23,7 +23,7 @@ AC_PREREQ(2.61) AC_COPYRIGHT( [Copyright (C) 2006 Luke Howard 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 configure script is derived from configure.ac which is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser @@ -697,13 +697,6 @@ then AC_REPLACE_FUNCS(getopt_long) AC_REPLACE_FUNCS(strndup) - # replace daemon() function if it is not on the system - AC_SEARCH_LIBS(daemon, bsd) - AC_REPLACE_FUNCS(daemon) - AC_CHECK_DECLS([daemon],,, [ - #include <unistd.h> - #include <stdlib.h>]) - # replace ether_aton_r() and ether_ntoa_r() if they are not found AC_CHECK_FUNCS(ether_aton_r ether_ntoa_r,, [AC_LIBOBJ(ether)]) @@ -726,7 +719,7 @@ then pthread_save_LIBS="$LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$LIBS $PTHREAD_LIBS" - AC_CHECK_FUNCS([pthread_mutex_lock pthread_join pthread_timedjoin_np]) + AC_CHECK_FUNCS([pthread_mutex_lock pthread_join pthread_timedjoin_np pthread_atfork]) CFLAGS="$pthread_save_CFLAGS" LIBS="$pthread_save_LIBS" 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..342c61c --- /dev/null +++ b/nslcd/daemonize.c @@ -0,0 +1,209 @@ +/* + 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> +#ifdef HAVE_PTHREAD_H +#include <pthread.h> +#endif /* HAVE_PTHREAD_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); +} + +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) + 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]; + /* 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) +{ + 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)) { |