X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fdaemon.c;h=4c7c8e16ae3f2f9b6760185e86dfa00258c5be56;hb=3834bcf2bf9106114bca70264b7a0bdb7351ff80;hp=91e0404e9c1e912a8ca9801c4874ced9663b2cec;hpb=2bf9d87ae3aa946ef93cb923e560beb582f5b655;p=sliver-openvswitch.git diff --git a/lib/daemon.c b/lib/daemon.c index 91e0404e9..4c7c8e16a 100644 --- a/lib/daemon.c +++ b/lib/daemon.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009, 2010 Nicira Networks. + * Copyright (c) 2014 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,116 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #include #include "daemon.h" +#include "daemon-private.h" #include #include -#include -#include -#include -#include -#include -#include #include -#include "command-line.h" -#include "fatal-signal.h" -#include "dirs.h" -#include "lockfile.h" -#include "process.h" -#include "socket-util.h" -#include "timeval.h" -#include "util.h" #include "vlog.h" -VLOG_DEFINE_THIS_MODULE(daemon) - -/* --detach: Should we run in the background? */ -static bool detach; - -/* --pidfile: Name of pidfile (null if none). */ -static char *pidfile; - -/* Device and inode of pidfile, so we can avoid reopening it. */ -static dev_t pidfile_dev; -static ino_t pidfile_ino; - -/* --overwrite-pidfile: Create pidfile even if one already exists and is - locked? */ -static bool overwrite_pidfile; - -/* --no-chdir: Should we chdir to "/"? */ -static bool chdir_ = true; - -/* File descriptor used by daemonize_start() and daemonize_complete(). */ -static int daemonize_fd = -1; - -/* --monitor: Should a supervisory process monitor the daemon and restart it if - * it dies due to an error signal? */ -static bool monitor; - -/* Returns the file name that would be used for a pidfile if 'name' were - * provided to set_pidfile(). The caller must free the returned string. */ -char * -make_pidfile_name(const char *name) -{ - return (!name - ? xasprintf("%s/%s.pid", ovs_rundir, program_name) - : abs_file_name(ovs_rundir, name)); -} - -/* Sets up a following call to daemonize() to create a pidfile named 'name'. - * If 'name' begins with '/', then it is treated as an absolute path. - * Otherwise, it is taken relative to RUNDIR, which is $(prefix)/var/run by - * default. - * - * If 'name' is null, then program_name followed by ".pid" is used. */ -void -set_pidfile(const char *name) -{ - free(pidfile); - pidfile = make_pidfile_name(name); -} - -/* Returns an absolute path to the configured pidfile, or a null pointer if no - * pidfile is configured. The caller must not modify or free the returned - * string. */ -const char * -get_pidfile(void) -{ - return pidfile; -} - -/* Sets that we do not chdir to "/". */ -void -set_no_chdir(void) -{ - chdir_ = false; -} +VLOG_DEFINE_THIS_MODULE(daemon); -/* Will we chdir to "/" as part of daemonizing? */ -bool -is_chdir_enabled(void) -{ - return chdir_; -} - -/* Normally, die_if_already_running() will terminate the program with a message - * if a locked pidfile already exists. If this function is called, - * die_if_already_running() will merely log a warning. */ -void -ignore_existing_pidfile(void) -{ - overwrite_pidfile = true; -} - -/* Sets up a following call to daemonize() to detach from the foreground - * session, running this process in the background. */ -void -set_detach(void) -{ - detach = true; -} +/* For each of the standard file descriptors, whether to replace it by + * /dev/null (if false) or keep it for the daemon to use (if true). */ +static bool save_fds[3]; /* Will daemonize() really detach? */ bool @@ -131,117 +34,6 @@ get_detach(void) return detach; } -/* Sets up a following call to daemonize() to fork a supervisory process to - * monitor the daemon and restart it if it dies due to an error signal. */ -void -daemon_set_monitor(void) -{ - monitor = true; -} - -/* If a pidfile has been configured and that pidfile already exists and is - * locked by a running process, returns the pid of the running process. - * Otherwise, returns 0. */ -static pid_t -already_running(void) -{ - pid_t pid = 0; - if (pidfile) { - int fd = open(pidfile, O_RDWR); - if (fd >= 0) { - struct flock lck; - lck.l_type = F_WRLCK; - lck.l_whence = SEEK_SET; - lck.l_start = 0; - lck.l_len = 0; - if (fcntl(fd, F_GETLK, &lck) != -1 && lck.l_type != F_UNLCK) { - pid = lck.l_pid; - } - close(fd); - } - } - return pid; -} - -/* If a locked pidfile exists, issue a warning message and, unless - * ignore_existing_pidfile() has been called, terminate the program. */ -void -die_if_already_running(void) -{ - pid_t pid = already_running(); - if (pid) { - if (!overwrite_pidfile) { - ovs_fatal(0, "%s: already running as pid %ld", - get_pidfile(), (long int) pid); - } else { - VLOG_WARN("%s: %s already running as pid %ld", - get_pidfile(), program_name, (long int) pid); - } - } -} - -/* If a pidfile has been configured, creates it and stores the running - * process's pid in it. Ensures that the pidfile will be deleted when the - * process exits. */ -static void -make_pidfile(void) -{ - if (pidfile) { - /* Create pidfile via temporary file, so that observers never see an - * empty pidfile or an unlocked pidfile. */ - long int pid = getpid(); - char *tmpfile; - int fd; - - tmpfile = xasprintf("%s.tmp%ld", pidfile, pid); - fatal_signal_add_file_to_unlink(tmpfile); - fd = open(tmpfile, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (fd >= 0) { - struct flock lck; - lck.l_type = F_WRLCK; - lck.l_whence = SEEK_SET; - lck.l_start = 0; - lck.l_len = 0; - if (fcntl(fd, F_SETLK, &lck) != -1) { - char *text = xasprintf("%ld\n", pid); - if (write(fd, text, strlen(text)) == strlen(text)) { - fatal_signal_add_file_to_unlink(pidfile); - if (rename(tmpfile, pidfile) < 0) { - VLOG_ERR("failed to rename \"%s\" to \"%s\": %s", - tmpfile, pidfile, strerror(errno)); - fatal_signal_remove_file_to_unlink(pidfile); - close(fd); - } else { - /* Keep 'fd' open to retain the lock. */ - struct stat s; - - if (!fstat(fd, &s)) { - pidfile_dev = s.st_dev; - pidfile_ino = s.st_ino; - } else { - VLOG_ERR("%s: fstat failed: %s", - pidfile, strerror(errno)); - } - } - free(text); - } else { - VLOG_ERR("%s: write failed: %s", tmpfile, strerror(errno)); - close(fd); - } - } else { - VLOG_ERR("%s: fcntl failed: %s", tmpfile, strerror(errno)); - close(fd); - } - } else { - VLOG_ERR("%s: create failed: %s", tmpfile, strerror(errno)); - } - fatal_signal_remove_file_to_unlink(tmpfile); - free(tmpfile); - } - free(pidfile); - pidfile = NULL; -} - /* If configured with set_pidfile() or set_detach(), creates the pid file and * detaches from the foreground session. */ void @@ -251,326 +43,78 @@ daemonize(void) daemonize_complete(); } -static pid_t -fork_and_wait_for_startup(int *fdp) -{ - int fds[2]; - pid_t pid; - - if (pipe(fds) < 0) { - ovs_fatal(errno, "pipe failed"); - } - - pid = fork(); - if (pid > 0) { - /* Running in parent process. */ - char c; - - close(fds[1]); - fatal_signal_fork(); - if (read(fds[0], &c, 1) != 1) { - int retval; - int status; - - do { - retval = waitpid(pid, &status, 0); - } while (retval == -1 && errno == EINTR); - - if (retval == pid - && WIFEXITED(status) - && WEXITSTATUS(status)) { - /* Child exited with an error. Convey the same error to - * our parent process as a courtesy. */ - exit(WEXITSTATUS(status)); - } - - ovs_fatal(errno, "fork child failed to signal startup"); - } - close(fds[0]); - *fdp = -1; - } else if (!pid) { - /* Running in child process. */ - close(fds[0]); - time_postfork(); - lockfile_postfork(); - *fdp = fds[1]; - } else { - ovs_fatal(errno, "could not fork"); - } - - return pid; -} - -static void -fork_notify_startup(int fd) -{ - if (fd != -1) { - size_t bytes_written; - int error; - - error = write_fully(fd, "", 1, &bytes_written); - if (error) { - ovs_fatal(error, "could not write to pipe"); - } - - close(fd); - } -} - -static bool -should_restart(int status) +/* Sets up a following call to daemonize() to create a pidfile named 'name'. + * If 'name' begins with '/' (or contains ':' in windows), then it is treated + * as an absolute path. Otherwise, it is taken relative to RUNDIR, + * which is $(prefix)/var/run by default. + * + * If 'name' is null, then program_name followed by ".pid" is used. */ +void +set_pidfile(const char *name) { - if (WIFSIGNALED(status)) { - static const int error_signals[] = { - SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGILL, SIGPIPE, SIGSEGV, - SIGXCPU, SIGXFSZ - }; - - size_t i; - - for (i = 0; i < ARRAY_SIZE(error_signals); i++) { - if (error_signals[i] == WTERMSIG(status)) { - return true; - } - } - } - return false; + assert_single_threaded(); + free(pidfile); + pidfile = make_pidfile_name(name); } -static void -monitor_daemon(pid_t daemon_pid) -{ - /* XXX Should log daemon's stderr output at startup time. */ - const char *saved_program_name; - time_t last_restart; - char *status_msg; - int crashes; - - saved_program_name = program_name; - program_name = xasprintf("monitor(%s)", program_name); - status_msg = xstrdup("healthy"); - last_restart = TIME_MIN; - crashes = 0; - for (;;) { - int retval; - int status; - - proctitle_set("%s: monitoring pid %lu (%s)", - saved_program_name, (unsigned long int) daemon_pid, - status_msg); - - do { - retval = waitpid(daemon_pid, &status, 0); - } while (retval == -1 && errno == EINTR); - - if (retval == -1) { - ovs_fatal(errno, "waitpid failed"); - } else if (retval == daemon_pid) { - char *s = process_status_msg(status); - if (should_restart(status)) { - free(status_msg); - status_msg = xasprintf("%d crashes: pid %lu died, %s", - ++crashes, - (unsigned long int) daemon_pid, s); - free(s); - - if (WCOREDUMP(status)) { - /* Disable further core dumps to save disk space. */ - struct rlimit r; - - r.rlim_cur = 0; - r.rlim_max = 0; - if (setrlimit(RLIMIT_CORE, &r) == -1) { - VLOG_WARN("failed to disable core dumps: %s", - strerror(errno)); - } - } - - /* Throttle restarts to no more than once every 10 seconds. */ - if (time(NULL) < last_restart + 10) { - VLOG_WARN("%s, waiting until 10 seconds since last " - "restart", status_msg); - for (;;) { - time_t now = time(NULL); - time_t wakeup = last_restart + 10; - if (now >= wakeup) { - break; - } - sleep(wakeup - now); - } - } - last_restart = time(NULL); - - VLOG_ERR("%s, restarting", status_msg); - daemon_pid = fork_and_wait_for_startup(&daemonize_fd); - if (!daemon_pid) { - break; - } - } else { - VLOG_INFO("pid %lu died, %s, exiting", - (unsigned long int) daemon_pid, s); - free(s); - exit(0); - } +/* A daemon doesn't normally have any use for the file descriptors for stdin, + * stdout, and stderr after it detaches. To keep these file descriptors from + * e.g. holding an SSH session open, by default detaching replaces each of + * these file descriptors by /dev/null. But a few daemons expect the user to + * redirect stdout or stderr to a file, in which case it is desirable to keep + * these file descriptors. This function, therefore, disables replacing 'fd' + * by /dev/null when the daemon detaches. */ +void +daemon_save_fd(int fd) +{ + ovs_assert(fd == STDIN_FILENO || + fd == STDOUT_FILENO || + fd == STDERR_FILENO); + save_fds[fd] = true; +} + +/* Returns a readable and writable fd for /dev/null, if successful, otherwise + * a negative errno value. The caller must not close the returned fd (because + * the same fd will be handed out to subsequent callers). */ +static int +get_null_fd(void) +{ + static int null_fd; +#ifndef _WIN32 + char *device = "/dev/null"; +#else + char *device = "nul"; +#endif + + if (!null_fd) { + null_fd = open(device, O_RDWR); + if (null_fd < 0) { + int error = errno; + VLOG_ERR("could not open %s: %s", device, ovs_strerror(error)); + null_fd = -error; } } - free(status_msg); - /* Running in new daemon process. */ - proctitle_restore(); - free((char *) program_name); - program_name = saved_program_name; + return null_fd; } -/* Close stdin, stdout, stderr. If we're started from e.g. an SSH session, - * then this keeps us from holding that session open artificially. */ -static void +/* Close standard file descriptors (except any that the client has requested we + * leave open by calling daemon_save_fd()). If we're started from e.g. an SSH + * session, then this keeps us from holding that session open artificially. */ +void close_standard_fds(void) { int null_fd = get_null_fd(); if (null_fd >= 0) { - dup2(null_fd, STDIN_FILENO); - dup2(null_fd, STDOUT_FILENO); - dup2(null_fd, STDERR_FILENO); - } -} - -/* If daemonization is configured, then starts daemonization, by forking and - * returning in the child process. The parent process hangs around until the - * child lets it know either that it completed startup successfully (by calling - * daemon_complete()) or that it failed to start up (by exiting with a nonzero - * exit code). */ -void -daemonize_start(void) -{ - daemonize_fd = -1; - - if (detach) { - if (fork_and_wait_for_startup(&daemonize_fd) > 0) { - /* Running in parent process. */ - exit(0); - } - /* Running in daemon or monitor process. */ - } - - if (monitor) { - int saved_daemonize_fd = daemonize_fd; - pid_t daemon_pid; - - daemon_pid = fork_and_wait_for_startup(&daemonize_fd); - if (daemon_pid > 0) { - /* Running in monitor process. */ - fork_notify_startup(saved_daemonize_fd); - close_standard_fds(); - monitor_daemon(daemon_pid); - } - /* Running in daemon process. */ - } - - make_pidfile(); - - /* Make sure that the unixctl commands for vlog get registered in a - * daemon, even before the first log message. */ - vlog_init(); -} - -/* If daemonization is configured, then this function notifies the parent - * process that the child process has completed startup successfully. */ -void -daemonize_complete(void) -{ - fork_notify_startup(daemonize_fd); - - if (detach) { - setsid(); - if (chdir_) { - ignore(chdir("/")); - } - close_standard_fds(); - } -} - -void -daemon_usage(void) -{ - printf( - "\nDaemon options:\n" - " --detach run in background as daemon\n" - " --no-chdir do not chdir to '/'\n" - " --pidfile[=FILE] create pidfile (default: %s/%s.pid)\n" - " --overwrite-pidfile with --pidfile, start even if already " - "running\n", - ovs_rundir, program_name); -} - -/* Opens and reads a PID from 'pidfile'. Returns the nonnegative PID if - * successful, otherwise a negative errno value. */ -pid_t -read_pidfile(const char *pidfile) -{ - char line[128]; - struct flock lck; - struct stat s; - FILE *file; - int error; - - if ((pidfile_ino || pidfile_dev) - && !stat(pidfile, &s) - && s.st_ino == pidfile_ino && s.st_dev == pidfile_dev) { - /* It's our own pidfile. We can't afford to open it, because closing - * *any* fd for a file that a process has locked also releases all the - * locks on that file. - * - * Fortunately, we know the associated pid anyhow: */ - return getpid(); - } - - file = fopen(pidfile, "r"); - if (!file) { - error = errno; - VLOG_WARN("%s: open: %s", pidfile, strerror(error)); - goto error; - } - - lck.l_type = F_WRLCK; - lck.l_whence = SEEK_SET; - lck.l_start = 0; - lck.l_len = 0; - if (fcntl(fileno(file), F_GETLK, &lck)) { - error = errno; - VLOG_WARN("%s: fcntl: %s", pidfile, strerror(error)); - goto error; - } - if (lck.l_type == F_UNLCK) { - error = ESRCH; - VLOG_WARN("%s: pid file is not locked", pidfile); - goto error; - } + int fd; - if (!fgets(line, sizeof line, file)) { - if (ferror(file)) { - error = errno; - VLOG_WARN("%s: read: %s", pidfile, strerror(error)); - } else { - error = ESRCH; - VLOG_WARN("%s: read: unexpected end of file", pidfile); + for (fd = 0; fd < 3; fd++) { + if (!save_fds[fd]) { + dup2(null_fd, fd); + } } - goto error; } - if (lck.l_pid != strtoul(line, NULL, 10)) { - error = ESRCH; - VLOG_WARN("l_pid (%ld) != %s pid (%s)", - (long int) lck.l_pid, pidfile, line); - goto error; - } - - fclose(file); - return lck.l_pid; - -error: - if (file) { - fclose(file); - } - return -error; + /* Disable logging to stderr to avoid wasting CPU time. */ + vlog_set_levels(NULL, VLF_CONSOLE, VLL_OFF); }