commit 4b266c6e72254d848b2ca4f594c0b41770104d81
Author: Robert Ransom <rransom.8774(a)gmail.com>
Date: Sun May 15 08:23:04 2011 -0700
Implement __OwningControllerProcess option
Implements part of feature 3049.
---
src/common/Makefile.am | 4 +-
src/common/procmon.c | 331 ++++++++++++++++++++++++++++++++++++++++++++++++
src/common/procmon.h | 29 ++++
src/or/config.c | 18 +++
src/or/control.c | 86 +++++++++++++
src/or/control.h | 2 +
src/or/or.h | 5 +
7 files changed, 473 insertions(+), 2 deletions(-)
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index b1e03cd..8889245 100644
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -12,11 +12,11 @@ libor_extra_source=
endif
libor_a_SOURCES = address.c log.c util.c compat.c container.c mempool.c \
- memarea.c util_codedigest.c $(libor_extra_source)
+ memarea.c util_codedigest.c procmon.c $(libor_extra_source)
libor_crypto_a_SOURCES = crypto.c aes.c tortls.c torgzip.c
libor_event_a_SOURCES = compat_libevent.c
-noinst_HEADERS = address.h torlog.h crypto.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h ciphers.inc compat_libevent.h tortls_states.h
+noinst_HEADERS = address.h torlog.h crypto.h util.h compat.h aes.h torint.h tortls.h strlcpy.c strlcat.c torgzip.h container.h ht.h mempool.h memarea.h procmon.h ciphers.inc compat_libevent.h tortls_states.h
common_sha1.i: $(libor_SOURCES) $(libor_crypto_a_SOURCES) $(noinst_HEADERS)
if test "@SHA1SUM@" != none; then \
diff --git a/src/common/procmon.c b/src/common/procmon.c
new file mode 100644
index 0000000..cee956a
--- /dev/null
+++ b/src/common/procmon.c
@@ -0,0 +1,331 @@
+
+/**
+ * \file procmon.c
+ * \brief Process-termination monitor functions
+ **/
+
+#include "procmon.h"
+
+#include "util.h"
+
+#ifdef HAVE_EVENT2_EVENT_H
+#include <event2/event.h>
+#else
+#include <event.h>
+#endif
+
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+
+#ifdef MS_WINDOWS
+#include <windows.h>
+
+/* Windows does not define pid_t, but _getpid() returns an int. */
+typedef int pid_t;
+#endif
+
+/* Define to 1 if process-termination monitors on this OS and Libevent
+ version must poll for process termination themselves. */
+#define PROCMON_POLLS 1
+/* Currently we need to poll in some way on all systems. */
+
+#ifdef PROCMON_POLLS
+static void tor_process_monitor_poll_cb(int unused1, short unused2,
+ void *procmon_);
+#endif
+
+/* This struct may contain pointers into the original process
+ * specifier string, but it should *never* contain anything which
+ * needs to be freed. */
+struct parsed_process_specifier_t {
+ pid_t pid;
+};
+
+/** Parse the process specifier given in <b>process_spec</b> into
+ * *<b>ppspec</b>. Return 0 on success; return -1 and store an error
+ * message into *<b>msg</b> on failure. The caller must not free the
+ * returned error message. */
+static int
+parse_process_specifier(const char *process_spec,
+ struct parsed_process_specifier_t *ppspec,
+ const char **msg)
+{
+ long pid_l;
+ int pid_ok = 0;
+ char *pspec_next;
+
+ /* If we're lucky, long will turn out to be large enough to hold a
+ * PID everywhere that Tor runs. */
+ pid_l = tor_parse_long(process_spec, 0, 1, LONG_MAX, &pid_ok, &pspec_next);
+
+ /* Reserve room in the ‘process specifier’ for additional
+ * (platform-specific) identifying information beyond the PID, to
+ * make our process-existence checks a bit less racy in a future
+ * version. */
+ if ((*pspec_next != 0) && (*pspec_next != ' ') && (*pspec_next != ':')) {
+ pid_ok = 0;
+ }
+
+ ppspec->pid = (pid_t)(pid_l);
+ if (!pid_ok || (pid_l != (long)(ppspec->pid))) {
+ *msg = "invalid PID";
+ goto err;
+ }
+
+ return 0;
+ err:
+ return -1;
+}
+
+struct tor_process_monitor_t {
+ /** Log domain for warning messages. */
+ log_domain_mask_t log_domain;
+
+ /** All systems: The best we can do in general is poll for the
+ * process's existence by PID periodically, and hope that the kernel
+ * doesn't reassign the same PID to another process between our
+ * polls. */
+ pid_t pid;
+
+#ifdef MS_WINDOWS
+ /** Windows-only: Should we poll hproc? If false, poll pid
+ * instead. */
+ int poll_hproc;
+
+ /** Windows-only: Get a handle to the process (if possible) and
+ * periodically check whether the process we have a handle to has
+ * ended. */
+ HANDLE hproc;
+ /* XXX023 We can and should have Libevent watch hproc for us,
+ * if/when some version of Libevent 2.x can be told to do so. */
+#endif
+
+ /* XXX023 On Linux, we can and should receive the 22nd
+ * (space-delimited) field (‘starttime’) of /proc/$PID/stat from the
+ * owning controller and store it, and poll once in a while to see
+ * whether it has changed -- if so, the kernel has *definitely*
+ * reassigned the owning controller's PID and we should exit. On
+ * FreeBSD, we can do the same trick using either the 8th
+ * space-delimited field of /proc/$PID/status on the seven FBSD
+ * systems whose admins have mounted procfs, or the start-time field
+ * of the process-information structure returned by kvmgetprocs() on
+ * any system. The latter is ickier. */
+ /* XXX023 On FreeBSD (and possibly other kqueue systems), we can and
+ * should arrange to receive EVFILT_PROC NOTE_EXIT notifications for
+ * pid, so we don't have to do such a heavyweight poll operation in
+ * order to avoid the PID-reassignment race condition. (We would
+ * still need to poll our own kqueue periodically until some version
+ * of Libevent 2.x learns to receive these events for us.) */
+
+ /** A Libevent event structure, to either poll for the process's
+ * existence or receive a notification when the process ends. */
+ struct event *e;
+
+ /** A callback to be called when the process ends. */
+ tor_procmon_callback_t cb;
+ void *cb_arg; /**< A user-specified pointer to be passed to cb. */
+};
+
+/** Verify that the process specifier given in <b>process_spec</b> is
+ * syntactically valid. Return 0 on success; return -1 and store an
+ * error message into *<b>msg</b> on failure. The caller must not
+ * free the returned error message. */
+int
+tor_validate_process_specifier(const char *process_spec,
+ const char **msg)
+{
+ struct parsed_process_specifier_t ppspec;
+
+ tor_assert(msg != NULL);
+ *msg = NULL;
+
+ return parse_process_specifier(process_spec, &ppspec, msg);
+}
+
+#ifdef HAVE_EVENT2_EVENT_H
+#define PERIODIC_TIMER_FLAGS EV_PERSIST
+#else
+#define PERIODIC_TIMER_FLAGS (0)
+#endif
+
+static const struct timeval poll_interval_tv = {15, 0};
+
+/** Create a process-termination monitor for the process specifier
+ * given in <b>process_spec</b>. Return a newly allocated
+ * tor_process_monitor_t on success; return NULL and store an error
+ * message into *<b>msg</b> on failure. The caller must not free
+ * the returned error message.
+ *
+ * When the monitored process terminates, call
+ * <b>cb</b>(<b>cb_arg</b>).
+ */
+tor_process_monitor_t *
+tor_process_monitor_new(struct event_base *base,
+ const char *process_spec,
+ log_domain_mask_t log_domain,
+ tor_procmon_callback_t cb, void *cb_arg,
+ const char **msg)
+{
+ tor_process_monitor_t *procmon = tor_malloc(sizeof(tor_process_monitor_t));
+ struct parsed_process_specifier_t ppspec;
+
+ tor_assert(msg != NULL);
+ *msg = NULL;
+
+ if (procmon == NULL) {
+ *msg = "out of memory";
+ goto err;
+ }
+
+ procmon->log_domain = log_domain;
+
+ if (parse_process_specifier(process_spec, &ppspec, msg))
+ goto err;
+
+ procmon->pid = ppspec.pid;
+
+#ifdef MS_WINDOWS
+ procmon->hproc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
+ FALSE,
+ procmon->pid);
+
+ if (procmon->hproc != NULL) {
+ procmon->poll_hproc = 1;
+ log_info(procmon->log_domain, "Successfully opened handle to process %d; "
+ "monitoring it.",
+ (int)(procmon->pid));
+ } else {
+ /* If we couldn't get a handle to the process, we'll try again the
+ * first time we poll. */
+ log_info(procmon->log_domain, "Failed to open handle to process %d; will "
+ "try again later.",
+ (int)(procmon->pid));
+ }
+#endif
+
+ procmon->cb = cb;
+ procmon->cb_arg = cb_arg;
+
+#ifdef PROCMON_POLLS
+ procmon->e = tor_event_new(base, -1 /* no FD */, PERIODIC_TIMER_FLAGS,
+ tor_process_monitor_poll_cb, procmon);
+ /* Note: If you port this file to plain Libevent 2, check that
+ * procmon->e is non-NULL. We don't need to here because
+ * tor_evtimer_new never returns NULL. */
+
+ evtimer_add(procmon->e, &poll_interval_tv);
+#else
+#error OOPS?
+#endif
+
+ return procmon;
+ err:
+ tor_process_monitor_free(procmon);
+ return NULL;
+}
+
+#ifdef PROCMON_POLLS
+/** Libevent callback to poll for the existence of the process
+ * monitored by <b>procmon_</b>. */
+static void
+tor_process_monitor_poll_cb(int unused1, short unused2, void *procmon_)
+{
+ tor_process_monitor_t *procmon = (tor_process_monitor_t *)(procmon_);
+ int its_dead_jim;
+
+ (void)unused1; (void)unused2;
+
+ tor_assert(procmon != NULL);
+
+#ifdef MS_WINDOWS
+ if (procmon->poll_hproc) {
+ DWORD exit_code;
+ if (!GetExitCodeProcess(procmon->hproc, &exit_code)) {
+ char *errmsg = format_win32_error(GetLastError());
+ log_warn(procmon->log_domain, "Error \"%s\" occurred while polling "
+ "handle for monitored process %d; assuming it's dead."
+ errmsg, procmon->pid);
+ tor_free(errmsg);
+ its_dead_jim = 1;
+ } else {
+ its_dead_jim = (exit_code != STILL_ACTIVE);
+ }
+ } else {
+ /* All we can do is try to open the process, and look at the error
+ * code if it fails again. */
+ procmon->hproc = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE,
+ FALSE,
+ procmon->pid);
+
+ if (procmon->hproc != NULL) {
+ log_info(procmon->log_domain, "Successfully opened handle to monitored "
+ "process %d.",
+ procmon->pid);
+ its_dead_jim = 0;
+ procmon->poll_hproc = 1;
+ } else {
+ DWORD err_code = GetLastError();
+ char *errmsg = format_win32_error(err_code);
+
+ /* When I tested OpenProcess's error codes on Windows 7, I
+ * received error code 5 (ERROR_ACCESS_DENIED) for PIDs of
+ * existing processes that I could not open and error code 87
+ * (ERROR_INVALID_PARAMETER) for PIDs that were not in use.
+ * Since the nonexistent-process error code is sane, I'm going
+ * to assume that all errors other than ERROR_INVALID_PARAMETER
+ * mean that the process we are monitoring is still alive. */
+ its_dead_jim = (err_code == ERROR_INVALID_PARAMETER);
+
+ if (!its_dead_jim)
+ log_info(procmon->log_domain, "Failed to open handle to monitored "
+ "process %d, and error code %d (%s) is not 'invalid "
+ "parameter' -- assuming the process is still alive.",
+ procmon->pid,
+ err_code, err_msg);
+
+ tor_free(err_msg);
+ }
+ }
+#else
+ /* Unix makes this part easy, if a bit racy. */
+ its_dead_jim = kill(procmon->pid, 0);
+ its_dead_jim = its_dead_jim && (errno == ESRCH);
+#endif
+
+ log(its_dead_jim ? LOG_NOTICE : LOG_INFO,
+ procmon->log_domain, "Monitored process %d is %s.",
+ (int)procmon->pid,
+ its_dead_jim ? "dead" : "still alive");
+
+ if (its_dead_jim) {
+ procmon->cb(procmon->cb_arg);
+#ifndef HAVE_EVENT2_EVENT_H
+ } else {
+ evtimer_add(procmon->e, &poll_interval_tv);
+#endif
+ }
+}
+#endif
+
+/** Free the process-termination monitor <b>procmon</b>. */
+void
+tor_process_monitor_free(tor_process_monitor_t *procmon)
+{
+ if (procmon == NULL)
+ return;
+
+#ifdef MS_WINDOWS
+ if (procmon->hproc != NULL)
+ CloseHandle(procmon->hproc);
+#endif
+
+ if (procmon->e != NULL)
+ tor_event_free(procmon->e);
+
+ tor_free(procmon);
+}
+
diff --git a/src/common/procmon.h b/src/common/procmon.h
new file mode 100644
index 0000000..f8c7444
--- /dev/null
+++ b/src/common/procmon.h
@@ -0,0 +1,29 @@
+
+/**
+ * \file procmon.h
+ * \brief Headers for procmon.c
+ **/
+
+#ifndef TOR_PROCMON_H
+#define TOR_PROCMON_H
+
+#include "compat.h"
+#include "compat_libevent.h"
+
+#include "torlog.h"
+
+typedef struct tor_process_monitor_t tor_process_monitor_t;
+
+typedef void (*tor_procmon_callback_t)(void *);
+
+int tor_validate_process_specifier(const char *process_spec,
+ const char **msg);
+tor_process_monitor_t *tor_process_monitor_new(struct event_base *base,
+ const char *process_spec,
+ log_domain_mask_t log_domain,
+ tor_procmon_callback_t cb,
+ void *cb_arg,
+ const char **msg);
+void tor_process_monitor_free(tor_process_monitor_t *procmon);
+
+#endif
diff --git a/src/or/config.c b/src/or/config.c
index 34208e8..b2bc9f3 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -38,6 +38,8 @@
#include <shlobj.h>
#endif
+#include "procmon.h"
+
/** Enumeration of types which option values can take */
typedef enum config_type_t {
CONFIG_TYPE_STRING = 0, /**< An arbitrary string. */
@@ -393,6 +395,7 @@ static config_var_t _option_vars[] = {
VAR("__LeaveStreamsUnattached",BOOL, LeaveStreamsUnattached, "0"),
VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword,
NULL),
+ VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL),
V(MinUptimeHidServDirectoryV2, INTERVAL, "24 hours"),
V(_UsingTestNetworkDefaults, BOOL, "0"),
@@ -1229,6 +1232,11 @@ options_act(or_options_t *old_options)
return -1;
}
+ if (monitor_owning_controller_process(options->OwningControllerProcess)) {
+ log_warn(LD_CONFIG, "Error monitoring owning controller process");
+ return -1;
+ }
+
/* reload keys as needed for rendezvous services. */
if (rend_service_load_keys()<0) {
log_warn(LD_GENERAL,"Error loading rendezvous service keys");
@@ -3446,6 +3454,16 @@ options_validate(or_options_t *old_options, or_options_t *options,
}
}
+ if (options->OwningControllerProcess) {
+ const char *validate_pspec_msg = NULL;
+ if (tor_validate_process_specifier(options->OwningControllerProcess,
+ &validate_pspec_msg)) {
+ tor_asprintf(msg, "Bad OwningControllerProcess: %s",
+ validate_pspec_msg);
+ return -1;
+ }
+ }
+
if (options->ControlListenAddress) {
int all_are_local = 1;
config_line_t *ln;
diff --git a/src/or/control.c b/src/or/control.c
index 926a465..3a96904 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -32,6 +32,8 @@
#include "routerlist.h"
#include "routerparse.h"
+#include "procmon.h"
+
/** Yield true iff <b>s</b> is the state of a control_connection_t that has
* finished authentication and is accepting commands. */
#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN)
@@ -3779,6 +3781,90 @@ init_cookie_authentication(int enabled)
return 0;
}
+/** A copy of the process specifier of Tor's owning controller, or
+ * NULL if this Tor instance is not currently owned by a process. */
+static char *owning_controller_process_spec = NULL;
+
+/** A process-termination monitor for Tor's owning controller, or NULL
+ * if this Tor instance is not currently owned by a process. */
+static tor_process_monitor_t *owning_controller_process_monitor = NULL;
+
+/** Process-termination monitor callback for Tor's owning controller
+ * process. */
+static void
+owning_controller_procmon_cb(void *unused)
+{
+ int shutdown_slowly = server_mode(get_options());
+
+ (void)unused;
+
+ log_notice(LD_CONTROL, "Owning controller process has vanished -- "
+ "%s.",
+ shutdown_slowly ? "shutting down" : "exiting now");
+
+ /* XXXX This chunk of code should be a separate function, called
+ * here and by process_signal(SIGINT). */
+
+ if (!shutdown_slowly) {
+ tor_cleanup();
+ exit(0);
+ }
+ /* XXXX This will close all listening sockets except control-port
+ * listeners. Perhaps we should close those too. */
+ hibernate_begin_shutdown();
+}
+
+/** Set <b>process_spec</b> as Tor's owning controller process.
+ * Return 0 on success, -1 on failure. */
+int
+monitor_owning_controller_process(const char *process_spec)
+{
+ const char *msg;
+
+ tor_assert((owning_controller_process_spec == NULL) ==
+ (owning_controller_process_monitor == NULL));
+
+ if (owning_controller_process_spec != NULL) {
+ if ((process_spec != NULL) && !strcmp(process_spec,
+ owning_controller_process_spec)) {
+ /* Same process -- return now, instead of disposing of and
+ * recreating the process-termination monitor. */
+ return 0;
+ }
+
+ /* We are currently owned by a process, and we should no longer be
+ * owned by it. Free the process-termination monitor. */
+ tor_process_monitor_free(owning_controller_process_monitor);
+ owning_controller_process_monitor = NULL;
+
+ tor_free(owning_controller_process_spec);
+ owning_controller_process_spec = NULL;
+ }
+
+ tor_assert((owning_controller_process_spec == NULL) &&
+ (owning_controller_process_monitor == NULL));
+
+ if (process_spec == NULL)
+ return 0;
+
+ owning_controller_process_spec = tor_strdup(process_spec);
+ owning_controller_process_monitor =
+ tor_process_monitor_new(tor_libevent_get_base(),
+ owning_controller_process_spec,
+ LD_CONTROL,
+ owning_controller_procmon_cb, NULL,
+ &msg);
+
+ if (owning_controller_process_monitor == NULL) {
+ log_warn(LD_CONTROL, "Couldn't create process-termination monitor for "
+ "owning controller: %s",
+ msg);
+ return -1;
+ }
+
+ return 0;
+}
+
/** Convert the name of a bootstrapping phase <b>s</b> into strings
* <b>tag</b> and <b>summary</b> suitable for display by the controller. */
static int
diff --git a/src/or/control.h b/src/or/control.h
index 2ae96b4..81c2301 100644
--- a/src/or/control.h
+++ b/src/or/control.h
@@ -70,6 +70,8 @@ smartlist_t *decode_hashed_passwords(config_line_t *passwords);
void disable_control_logging(void);
void enable_control_logging(void);
+int monitor_owning_controller_process(const char *process_spec);
+
void control_event_bootstrap(bootstrap_status_t status, int progress);
void control_event_bootstrap_problem(const char *warn, int reason);
diff --git a/src/or/or.h b/src/or/or.h
index d667358..546c38a 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -2669,6 +2669,11 @@ typedef struct {
int DisablePredictedCircuits; /**< Boolean: does Tor preemptively
* make circuits in the background (0),
* or not (1)? */
+
+ /** Process specifier for a controller that ‘owns’ this Tor
+ * instance. Tor will terminate if its owning controller does. */
+ char *OwningControllerProcess;
+
int ShutdownWaitLength; /**< When we get a SIGINT and we're a server, how
* long do we wait before exiting? */
char *SafeLogging; /**< Contains "relay", "1", "0" (meaning no scrubbing). */