/*
- * Copyright (c) 2008, 2009, 2010 Nicira Networks.
+ * Copyright (c) 2008, 2009, 2010, 2011 Nicira Networks.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include "daemon.h"
#include <errno.h>
#include <fcntl.h>
+#include <signal.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/resource.h>
#include <sys/wait.h>
+#include <sys/stat.h>
#include <unistd.h>
#include "command-line.h"
#include "fatal-signal.h"
#include "socket-util.h"
#include "timeval.h"
#include "util.h"
-
-#define THIS_MODULE VLM_daemon
#include "vlog.h"
-/* Should we run in the background? */
+VLOG_DEFINE_THIS_MODULE(daemon);
+
+/* --detach: Should we run in the background? */
static bool detach;
-/* Name of pidfile (null if none). */
+/* --pidfile: Name of pidfile (null if none). */
static char *pidfile;
-/* Create pidfile even if one already exists and is locked? */
+/* 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;
-/* Should we chdir to "/"? */
+/* --no-chdir: Should we chdir to "/"? */
static bool chdir_ = true;
/* File descriptor used by daemonize_start() and daemonize_complete(). */
/* 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)
+make_pidfile_name(const char *name)
{
- return (!name ? xasprintf("%s/%s.pid", ovs_rundir, program_name)
- : *name == '/' ? xstrdup(name)
- : xasprintf("%s/%s", ovs_rundir, 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'.
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) {
+ pid_t pid;
+ if (!pidfile) {
+ return;
+ }
+ pid = read_pidfile_if_exists(pidfile);
+ if (pid > 0) {
if (!overwrite_pidfile) {
+ VLOG_ERR("%s: %s already running as pid %ld, aborting",
+ get_pidfile(), program_name, (long int) pid);
ovs_fatal(0, "%s: already running as pid %ld",
get_pidfile(), (long int) pid);
} else {
}
}
-/* If a pidfile has been configured, creates it and stores the running process'
- * pid init. Ensures that the pidfile will be deleted when the process
- * exits. */
+/* 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)
{
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);
}
+ free(text);
} else {
VLOG_ERR("%s: fcntl failed: %s", tmpfile, strerror(errno));
close(fd);
static void
monitor_daemon(pid_t daemon_pid)
{
- /* XXX Should limit the rate at which we restart the daemon. */
/* 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;
ovs_fatal(errno, "waitpid failed");
} else if (retval == daemon_pid) {
char *s = process_status_msg(status);
- free(status_msg);
- status_msg = xasprintf("pid %lu died, %s",
- (unsigned long int) daemon_pid, s);
- free(s);
-
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("%s, exiting", status_msg);
+ VLOG_INFO("pid %lu died, %s, exiting",
+ (unsigned long int) daemon_pid, s);
+ free(s);
exit(0);
}
}
}
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. */
+ * 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();
ignore(chdir("/"));
}
close_standard_fds();
+ detach = false;
}
}
" --pidfile[=FILE] create pidfile (default: %s/%s.pid)\n"
" --overwrite-pidfile with --pidfile, start even if already "
"running\n",
- ovs_rundir, program_name);
+ 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)
+static pid_t
+read_pidfile__(const char *pidfile, bool must_exist)
{
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) {
+ if (errno == ENOENT && !must_exist) {
+ return 0;
+ }
error = errno;
VLOG_WARN("%s: open: %s", pidfile, strerror(error));
goto error;
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
+ lck.l_pid = 0;
if (fcntl(fileno(file), F_GETLK, &lck)) {
error = errno;
VLOG_WARN("%s: fcntl: %s", pidfile, strerror(error));
}
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, true);
+}
+
+
+/* Opens and reads a PID from 'pidfile', if it exists. Returns 0 if 'pidfile'
+ * doesn't exist, the positive PID if successful, otherwise a negative errno
+ * value. */
+pid_t
+read_pidfile_if_exists(const char *pidfile)
+{
+ return read_pidfile__(pidfile, false);
+}