X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=lib%2Fdaemon.c;h=4c7c8e16ae3f2f9b6760185e86dfa00258c5be56;hb=3834bcf2bf9106114bca70264b7a0bdb7351ff80;hp=c4a82647f8ace909101e36d89409a024cce9b70d;hpb=e0edde6fee279cdbbf3c179f5f50adaf0c7c7f1e;p=sliver-openvswitch.git diff --git a/lib/daemon.c b/lib/daemon.c index c4a82647f..4c7c8e16a 100644 --- a/lib/daemon.c +++ b/lib/daemon.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc. + * 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,140 +13,50 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - #include #include "daemon.h" -#include +#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; - /* 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]; -static void check_already_running(void); -static int lock_pidfile(FILE *, int command); +/* Will daemonize() really detach? */ +bool +get_detach(void) +{ + return detach; +} -/* 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) +/* If configured with set_pidfile() or set_detach(), creates the pid file and + * detaches from the foreground session. */ +void +daemonize(void) { - return (!name - ? xasprintf("%s/%s.pid", ovs_rundir(), program_name) - : abs_file_name(ovs_rundir(), name)); + daemonize_start(); + daemonize_complete(); } /* 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' 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) { + assert_single_threaded(); 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; -} - -/* Will we chdir to "/" as part of daemonizing? */ -bool -is_chdir_enabled(void) -{ - return chdir_; -} - -/* Normally, daemonize() or damonize_start() will terminate the program with a - * message if a locked pidfile already exists. If this function is called, an - * existing pidfile will be replaced, with 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; -} - -/* Will daemonize() really detach? */ -bool -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; -} - /* 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 @@ -157,274 +67,41 @@ daemon_set_monitor(void) void daemon_save_fd(int fd) { - assert(fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO); + ovs_assert(fd == STDIN_FILENO || + fd == STDOUT_FILENO || + fd == STDERR_FILENO); save_fds[fd] = true; } -/* 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) -{ - long int pid = getpid(); - struct stat s; - char *tmpfile; - FILE *file; - int error; - - /* Create a temporary pidfile. */ - tmpfile = xasprintf("%s.tmp%ld", pidfile, pid); - fatal_signal_add_file_to_unlink(tmpfile); - file = fopen(tmpfile, "w+"); - if (!file) { - VLOG_FATAL("%s: create failed (%s)", tmpfile, strerror(errno)); - } - - if (fstat(fileno(file), &s) == -1) { - VLOG_FATAL("%s: fstat failed (%s)", tmpfile, strerror(errno)); - } - - fprintf(file, "%ld\n", pid); - if (fflush(file) == EOF) { - VLOG_FATAL("%s: write failed (%s)", tmpfile, strerror(errno)); - } - - error = lock_pidfile(file, F_SETLK); - if (error) { - VLOG_FATAL("%s: fcntl(F_SETLK) failed (%s)", tmpfile, strerror(error)); - } - - /* Rename or link it to the correct name. */ - if (overwrite_pidfile) { - if (rename(tmpfile, pidfile) < 0) { - VLOG_FATAL("failed to rename \"%s\" to \"%s\" (%s)", - tmpfile, pidfile, strerror(errno)); - } - } else { - do { - error = link(tmpfile, pidfile) == -1 ? errno : 0; - if (error == EEXIST) { - check_already_running(); - } - } while (error == EINTR || error == EEXIST); - if (error) { - VLOG_FATAL("failed to link \"%s\" as \"%s\" (%s)", - tmpfile, pidfile, strerror(error)); - } - } - - /* Ensure that the pidfile will get deleted on exit. */ - fatal_signal_add_file_to_unlink(pidfile); - - /* Delete the temporary pidfile if it still exists. */ - if (!overwrite_pidfile) { - error = fatal_signal_unlink_file_now(tmpfile); - if (error) { - VLOG_FATAL("%s: unlink failed (%s)", tmpfile, strerror(error)); - } - } - - /* Clean up. - * - * We don't close 'file' because its file descriptor must remain open to - * hold the lock. */ - pidfile_dev = s.st_dev; - pidfile_ino = s.st_ino; - 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 -daemonize(void) -{ - daemonize_start(); - daemonize_complete(); -} - -static pid_t -fork_and_wait_for_startup(int *fdp) -{ - int fds[2]; - pid_t pid; - - xpipe(fds); - - pid = fork(); - if (pid > 0) { - /* Running in parent process. */ - size_t bytes_read; - char c; - - close(fds[1]); - fatal_signal_fork(); - if (read_fully(fds[0], &c, 1, &bytes_read) != 0) { - int retval; - int status; - - do { - retval = waitpid(pid, &status, 0); - } while (retval == -1 && errno == EINTR); - - if (retval == pid) { - if (WIFEXITED(status) && WEXITSTATUS(status)) { - /* Child exited with an error. Convey the same error - * to our parent process as a courtesy. */ - exit(WEXITSTATUS(status)); - } else { - char *status_msg = process_status_msg(status); - VLOG_FATAL("fork child died before signaling startup (%s)", - status_msg); - } - } else if (retval < 0) { - VLOG_FATAL("waitpid failed (%s)", strerror(errno)); - } else { - NOT_REACHED(); - } - } - close(fds[0]); - *fdp = -1; - } else if (!pid) { - /* Running in child process. */ - close(fds[0]); - time_postfork(); - lockfile_postfork(); - *fdp = fds[1]; - } else { - VLOG_FATAL("fork failed (%s)", strerror(errno)); - } - - 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) { - VLOG_FATAL("pipe write failed (%s)", strerror(error)); - } - - close(fd); - } -} - -static bool -should_restart(int status) -{ - 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; -} - -static void -monitor_daemon(pid_t daemon_pid) +/* 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) { - /* 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); + static int null_fd; +#ifndef _WIN32 + char *device = "/dev/null"; +#else + char *device = "nul"; +#endif - if (retval == -1) { - VLOG_FATAL("waitpid failed (%s)", strerror(errno)); - } 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); - } + 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 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. */ -static void +void close_standard_fds(void) { int null_fd = get_null_fd(); @@ -441,234 +118,3 @@ close_standard_fds(void) /* Disable logging to stderr to avoid wasting CPU time. */ vlog_set_levels(NULL, VLF_CONSOLE, VLL_OFF); } - -/* 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. */ - } - - if (pidfile) { - 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. - * - * Calling this function more than once has no additional effect. */ -void -daemonize_complete(void) -{ - fork_notify_startup(daemonize_fd); - daemonize_fd = -1; - - if (detach) { - setsid(); - if (chdir_) { - ignore(chdir("/")); - } - close_standard_fds(); - detach = false; - } -} - -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); -} - -static int -lock_pidfile__(FILE *file, int command, struct flock *lck) -{ - int error; - - lck->l_type = F_WRLCK; - lck->l_whence = SEEK_SET; - lck->l_start = 0; - lck->l_len = 0; - lck->l_pid = 0; - - do { - error = fcntl(fileno(file), command, lck) == -1 ? errno : 0; - } while (error == EINTR); - return error; -} - -static int -lock_pidfile(FILE *file, int command) -{ - struct flock lck; - - return lock_pidfile__(file, command, &lck); -} - -static pid_t -read_pidfile__(const char *pidfile, bool delete_if_stale) -{ - struct stat s, s2; - struct flock lck; - char line[128]; - 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) { - if (errno == ENOENT && delete_if_stale) { - return 0; - } - error = errno; - VLOG_WARN("%s: open: %s", pidfile, strerror(error)); - goto error; - } - - error = lock_pidfile__(file, F_GETLK, &lck); - if (error) { - VLOG_WARN("%s: fcntl: %s", pidfile, strerror(error)); - goto error; - } - if (lck.l_type == F_UNLCK) { - /* pidfile exists but it isn't locked by anyone. We need to delete it - * so that a new pidfile can go in its place. But just calling - * unlink(pidfile) makes a nasty race: what if someone else unlinks it - * before we do and then replaces it by a valid pidfile? We'd unlink - * their valid pidfile. We do a little dance to avoid the race, by - * locking the invalid pidfile. Only one process can have the invalid - * pidfile locked, and only that process has the right to unlink it. */ - if (!delete_if_stale) { - error = ESRCH; - VLOG_DBG("%s: pid file is stale", pidfile); - goto error; - } - - /* Get the lock. */ - error = lock_pidfile(file, F_SETLK); - if (error) { - /* We lost a race with someone else doing the same thing. */ - VLOG_WARN("%s: lost race to lock pidfile", pidfile); - goto error; - } - - /* Is the file we have locked still named 'pidfile'? */ - if (stat(pidfile, &s) || fstat(fileno(file), &s2) - || s.st_ino != s2.st_ino || s.st_dev != s2.st_dev) { - /* No. We lost a race with someone else who got the lock before - * us, deleted the pidfile, and closed it (releasing the lock). */ - error = EALREADY; - VLOG_WARN("%s: lost race to delete pidfile", pidfile); - goto error; - } - - /* We won the right to delete the stale pidfile. */ - if (unlink(pidfile)) { - error = errno; - VLOG_WARN("%s: failed to delete stale pidfile (%s)", - pidfile, strerror(error)); - goto error; - } - VLOG_DBG("%s: deleted stale pidfile", pidfile); - fclose(file); - return 0; - } - - 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); - } - goto error; - } - - if (lck.l_pid != strtoul(line, NULL, 10)) { - /* The process that has the pidfile locked is not the process that - * created it. It must be stale, with the process that has it locked - * preparing to delete it. */ - error = ESRCH; - VLOG_WARN("%s: stale pidfile for pid %s being deleted by pid %ld", - pidfile, line, (long int) lck.l_pid); - goto error; - } - - fclose(file); - return lck.l_pid; - -error: - if (file) { - fclose(file); - } - return -error; -} - -/* Opens and reads a PID from 'pidfile'. Returns the positive PID if - * successful, otherwise a negative errno value. */ -pid_t -read_pidfile(const char *pidfile) -{ - return read_pidfile__(pidfile, false); -} - -/* Checks whether a process with the given 'pidfile' is already running and, - * if so, aborts. If 'pidfile' is stale, deletes it. */ -static void -check_already_running(void) -{ - long int pid = read_pidfile__(pidfile, true); - if (pid > 0) { - VLOG_FATAL("%s: already running as pid %ld, aborting", pidfile, pid); - } else if (pid < 0) { - VLOG_FATAL("%s: pidfile check failed (%s), aborting", - pidfile, strerror(-pid)); - } -}