Commit 4b266c6e authored by Robert Ransom's avatar Robert Ransom
Browse files

Implement __OwningControllerProcess option

Implements part of feature 3049.
parent c714a098
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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 \

src/common/procmon.c

0 → 100644
+331 −0
Original line number Diff line number Diff line

/**
 * \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);
}

src/common/procmon.h

0 → 100644
+29 −0
Original line number Diff line number Diff line

/**
 * \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
+18 −0
Original line number Diff line number Diff line
@@ -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;
+86 −0
Original line number Diff line number Diff line
@@ -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
Loading