From 532a60357ef4c5852cc1bf836cfd9d6f093ef204 Mon Sep 17 00:00:00 2001 From: Siddhesh Poyarekar Date: Mon, 3 Mar 2014 22:51:39 +0530 Subject: nscd: Improved support for tracking startup failure in nscd service (BZ #16639) Currently, the nscd parent process parses commandline options and configuration, forks on startup and immediately exits with a success. If the child process encounters some error after this, it goes undetected and any services started up after it may have to repeatedly check to make sure that the nscd service did actually start up and is serving requests. To make this process more reliable, I have added a pipe between the parent and child process, through which the child process sends a notification to the parent informing it of its status. The parent waits for this status and once it receives it, exits with the corresponding exit code. So if the child service sends a success status (0), the parent exits with a success status. Similarly for error conditions, the child sends the non-zero status code, which the parent passes on as the exit code. This, along with setting the nscd service type to forking in its systemd configuration file, allows systemd to be certain that the nscd service is ready and is accepting connections. --- nscd/nscd.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 5 deletions(-) (limited to 'nscd/nscd.c') diff --git a/nscd/nscd.c b/nscd/nscd.c index 63d9d83599..56803785e1 100644 --- a/nscd/nscd.c +++ b/nscd/nscd.c @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include "dbg_log.h" #include "nscd.h" @@ -101,6 +103,7 @@ gid_t old_gid; static int check_pid (const char *file); static int write_pid (const char *file); +static int monitor_child (int fd); /* Name and version of program. */ static void print_version (FILE *stream, struct argp_state *state); @@ -142,6 +145,7 @@ static struct argp argp = /* True if only statistics are requested. */ static bool get_stats; +static int parent_fd = -1; int main (int argc, char **argv) @@ -196,11 +200,27 @@ main (int argc, char **argv) /* Behave like a daemon. */ if (run_mode == RUN_DAEMONIZE) { + int fd[2]; + + if (pipe (fd) != 0) + error (EXIT_FAILURE, errno, + _("cannot create a pipe to talk to the child")); + pid = fork (); if (pid == -1) error (EXIT_FAILURE, errno, _("cannot fork")); if (pid != 0) - exit (0); + { + /* The parent only reads from the child. */ + close (fd[1]); + exit (monitor_child (fd[0])); + } + else + { + /* The child only writes to the parent. */ + close (fd[0]); + parent_fd = fd[1]; + } } int nullfd = open (_PATH_DEVNULL, O_RDWR); @@ -242,7 +262,8 @@ main (int argc, char **argv) char *endp; long int fdn = strtol (dirent->d_name, &endp, 10); - if (*endp == '\0' && fdn != dfdn && fdn >= min_close_fd) + if (*endp == '\0' && fdn != dfdn && fdn >= min_close_fd + && fdn != parent_fd) close ((int) fdn); } @@ -250,13 +271,14 @@ main (int argc, char **argv) } else for (i = min_close_fd; i < getdtablesize (); i++) - close (i); + if (i != parent_fd) + close (i); setsid (); if (chdir ("/") != 0) - error (EXIT_FAILURE, errno, - _("cannot change current working directory to \"/\"")); + do_exit (EXIT_FAILURE, errno, + _("cannot change current working directory to \"/\"")); openlog ("nscd", LOG_CONS | LOG_ODELAY, LOG_DAEMON); @@ -592,3 +614,79 @@ write_pid (const char *file) return result; } + +static int +monitor_child (int fd) +{ + int child_ret = 0; + int ret = read (fd, &child_ret, sizeof (child_ret)); + + /* The child terminated with an error, either via exit or some other abnormal + method, like a segfault. */ + if (ret <= 0 || child_ret != 0) + { + int err = wait (&child_ret); + + if (err < 0) + { + fprintf (stderr, _("wait failed")); + return 1; + } + + fprintf (stderr, _("child exited with status %d"), + WEXITSTATUS (child_ret)); + if (WIFSIGNALED (child_ret)) + fprintf (stderr, _(", terminated by signal %d.\n"), + WTERMSIG (child_ret)); + else + fprintf (stderr, ".\n"); + } + + /* We have the child status, so exit with that code. */ + close (fd); + + return child_ret; +} + +void +do_exit (int child_ret, int errnum, const char *format, ...) +{ + if (parent_fd != -1) + { + int ret = write (parent_fd, &child_ret, sizeof (child_ret)); + assert (ret == sizeof (child_ret)); + close (parent_fd); + } + + if (format != NULL) + { + /* Emulate error() since we don't have a va_list variant for it. */ + va_list argp; + + fflush (stdout); + + fprintf (stderr, "%s: ", program_invocation_name); + + va_start (argp, format); + vfprintf (stderr, format, argp); + va_end (argp); + + fprintf (stderr, ": %s\n", strerror (errnum)); + fflush (stderr); + } + + /* Finally, exit. */ + exit (child_ret); +} + +void +notify_parent (int child_ret) +{ + if (parent_fd == -1) + return; + + int ret = write (parent_fd, &child_ret, sizeof (child_ret)); + assert (ret == sizeof (child_ret)); + close (parent_fd); + parent_fd = -1; +} -- cgit 1.4.1