[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [tor/master] Extract process-management functionality into a new lib/process



commit 315e6b59ddb91c19c5102625c6cf0f22b2d6f894
Author: Nick Mathewson <nickm@xxxxxxxxxxxxxx>
Date:   Thu Jun 28 10:53:34 2018 -0400

    Extract process-management functionality into a new lib/process
    
    Note that procmon does *not* go here, since procmon needs to
    integrate with the event loop.
---
 .gitignore                                         |    2 +
 Makefile.am                                        |    2 +
 src/common/compat.c                                |  502 -------
 src/common/compat.h                                |   18 -
 src/common/include.am                              |    2 -
 src/common/util.c                                  | 1539 +-------------------
 src/common/util.h                                  |  143 --
 src/include.am                                     |    1 +
 src/lib/process/.may_include                       |   15 +
 src/lib/process/daemon.c                           |  159 ++
 src/lib/process/daemon.h                           |   12 +
 src/lib/process/env.c                              |  215 +++
 src/lib/process/env.h                              |   36 +
 src/lib/process/include.am                         |   29 +
 src/lib/process/pidfile.c                          |   47 +
 src/lib/process/pidfile.h                          |   11 +
 src/lib/process/restrict.c                         |  144 ++
 src/lib/process/restrict.h                         |   17 +
 src/lib/process/setuid.c                           |  375 +++++
 src/lib/process/setuid.h                           |   17 +
 src/lib/process/subprocess.c                       | 1231 ++++++++++++++++
 src/lib/process/subprocess.h                       |  129 ++
 .../util_process.c => lib/process/waitpid.c}       |   20 +-
 .../util_process.h => lib/process/waitpid.h}       |   15 +-
 src/or/config.c                                    |    6 +
 src/or/main.c                                      |    2 +-
 src/or/transports.c                                |    4 +-
 src/or/transports.h                                |    5 +-
 src/test/test_logging.c                            |    1 +
 src/test/test_pt.c                                 |    3 +-
 src/test/test_switch_id.c                          |    2 +-
 src/test/test_util.c                               |    6 +-
 src/test/test_util_process.c                       |    2 +-
 src/test/test_util_slow.c                          |    5 +-
 34 files changed, 2487 insertions(+), 2230 deletions(-)

diff --git a/.gitignore b/.gitignore
index b59731441..8f2d66b0d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -191,6 +191,8 @@ uptime-*.json
 /src/lib/libtor-memarea-testing.a
 /src/lib/libtor-net.a
 /src/lib/libtor-net-testing.a
+/src/lib/libtor-process.a
+/src/lib/libtor-process-testing.a
 /src/lib/libtor-sandbox.a
 /src/lib/libtor-sandbox-testing.a
 /src/lib/libtor-string.a
diff --git a/Makefile.am b/Makefile.am
index f71c94b77..f28eae3bb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,6 +40,7 @@ endif
 # "Common" libraries used to link tor's utility code.
 TOR_UTIL_LIBS = \
 	src/common/libor.a \
+        src/lib/libtor-process.a \
         src/lib/libtor-fs.a \
         src/lib/libtor-encoding.a \
         src/lib/libtor-sandbox.a \
@@ -62,6 +63,7 @@ TOR_UTIL_LIBS = \
 # and tests)
 TOR_UTIL_TESTING_LIBS = \
 	src/common/libor-testing.a \
+        src/lib/libtor-process-testing.a \
         src/lib/libtor-fs-testing.a \
         src/lib/libtor-encoding-testing.a \
         src/lib/libtor-sandbox-testing.a \
diff --git a/src/common/compat.c b/src/common/compat.c
index 48e706456..d03c4a501 100644
--- a/src/common/compat.c
+++ b/src/common/compat.c
@@ -367,421 +367,6 @@ set_max_file_descriptors(rlim_t limit, int *max_out)
   return 0;
 }
 
-#ifndef _WIN32
-/** Log details of current user and group credentials. Return 0 on
- * success. Logs and return -1 on failure.
- */
-static int
-log_credential_status(void)
-{
-/** Log level to use when describing non-error UID/GID status. */
-#define CREDENTIAL_LOG_LEVEL LOG_INFO
-  /* Real, effective and saved UIDs */
-  uid_t ruid, euid, suid;
-  /* Read, effective and saved GIDs */
-  gid_t rgid, egid, sgid;
-  /* Supplementary groups */
-  gid_t *sup_gids = NULL;
-  int sup_gids_size;
-  /* Number of supplementary groups */
-  int ngids;
-
-  /* log UIDs */
-#ifdef HAVE_GETRESUID
-  if (getresuid(&ruid, &euid, &suid) != 0 ) {
-    log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
-    return -1;
-  } else {
-    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
-           "UID is %u (real), %u (effective), %u (saved)",
-           (unsigned)ruid, (unsigned)euid, (unsigned)suid);
-  }
-#else /* !(defined(HAVE_GETRESUID)) */
-  /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */
-  ruid = getuid();
-  euid = geteuid();
-  (void)suid;
-
-  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
-         "UID is %u (real), %u (effective), unknown (saved)",
-         (unsigned)ruid, (unsigned)euid);
-#endif /* defined(HAVE_GETRESUID) */
-
-  /* log GIDs */
-#ifdef HAVE_GETRESGID
-  if (getresgid(&rgid, &egid, &sgid) != 0 ) {
-    log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
-    return -1;
-  } else {
-    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
-           "GID is %u (real), %u (effective), %u (saved)",
-           (unsigned)rgid, (unsigned)egid, (unsigned)sgid);
-  }
-#else /* !(defined(HAVE_GETRESGID)) */
-  /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */
-  rgid = getgid();
-  egid = getegid();
-  (void)sgid;
-  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
-         "GID is %u (real), %u (effective), unknown (saved)",
-         (unsigned)rgid, (unsigned)egid);
-#endif /* defined(HAVE_GETRESGID) */
-
-  /* log supplementary groups */
-  sup_gids_size = 64;
-  sup_gids = tor_calloc(64, sizeof(gid_t));
-  while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
-         errno == EINVAL &&
-         sup_gids_size < NGROUPS_MAX) {
-    sup_gids_size *= 2;
-    sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
-  }
-
-  if (ngids < 0) {
-    log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
-             strerror(errno));
-    tor_free(sup_gids);
-    return -1;
-  } else {
-    int i, retval = 0;
-    char *s = NULL;
-    smartlist_t *elts = smartlist_new();
-
-    for (i = 0; i<ngids; i++) {
-      smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
-    }
-
-    s = smartlist_join_strings(elts, " ", 0, NULL);
-
-    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
-
-    tor_free(s);
-    SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
-    smartlist_free(elts);
-    tor_free(sup_gids);
-
-    return retval;
-  }
-
-  return 0;
-}
-#endif /* !defined(_WIN32) */
-
-/** Return true iff we were compiled with capability support, and capabilities
- * seem to work. **/
-int
-have_capability_support(void)
-{
-#ifdef HAVE_LINUX_CAPABILITIES
-  cap_t caps = cap_get_proc();
-  if (caps == NULL)
-    return 0;
-  cap_free(caps);
-  return 1;
-#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
-  return 0;
-#endif /* defined(HAVE_LINUX_CAPABILITIES) */
-}
-
-#ifdef HAVE_LINUX_CAPABILITIES
-/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
- * appropriate.
- *
- * If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
- * CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
- * setuid().
- *
- * If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
- * PR_KEEPCAPS.
- *
- * Return 0 on success, and -1 on failure.
- */
-static int
-drop_capabilities(int pre_setuid)
-{
-  /* We keep these three capabilities, and these only, as we setuid.
-   * After we setuid, we drop all but the first. */
-  const cap_value_t caplist[] = {
-    CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
-  };
-  const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
-  const int n_effective = pre_setuid ? 3 : 1;
-  const int n_permitted = pre_setuid ? 3 : 1;
-  const int n_inheritable = 1;
-  const int keepcaps = pre_setuid ? 1 : 0;
-
-  /* Sets whether we keep capabilities across a setuid. */
-  if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
-    log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
-             where, strerror(errno));
-    return -1;
-  }
-
-  cap_t caps = cap_get_proc();
-  if (!caps) {
-    log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
-             where, strerror(errno));
-    return -1;
-  }
-  cap_clear(caps);
-
-  cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
-  cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
-  cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
-
-  int r = cap_set_proc(caps);
-  cap_free(caps);
-  if (r < 0) {
-    log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
-             where, strerror(errno));
-    return -1;
-  }
-
-  return 0;
-}
-#endif /* defined(HAVE_LINUX_CAPABILITIES) */
-
-/** Call setuid and setgid to run as <b>user</b> and switch to their
- * primary group.  Return 0 on success.  On failure, log and return -1.
- *
- * If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
- * system to retain the abilitity to bind low ports.
- *
- * If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
- * don't have capability support.
- */
-int
-switch_id(const char *user, const unsigned flags)
-{
-#ifndef _WIN32
-  const struct passwd *pw = NULL;
-  uid_t old_uid;
-  gid_t old_gid;
-  static int have_already_switched_id = 0;
-  const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
-  const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
-
-  tor_assert(user);
-
-  if (have_already_switched_id)
-    return 0;
-
-  /* Log the initial credential state */
-  if (log_credential_status())
-    return -1;
-
-  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
-
-  /* Get old UID/GID to check if we changed correctly */
-  old_uid = getuid();
-  old_gid = getgid();
-
-  /* Lookup the user and group information, if we have a problem, bail out. */
-  pw = tor_getpwnam(user);
-  if (pw == NULL) {
-    log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
-    return -1;
-  }
-
-#ifdef HAVE_LINUX_CAPABILITIES
-  (void) warn_if_no_caps;
-  if (keep_bindlow) {
-    if (drop_capabilities(1))
-      return -1;
-  }
-#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
-  (void) keep_bindlow;
-  if (warn_if_no_caps) {
-    log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
-             "on this system.");
-  }
-#endif /* defined(HAVE_LINUX_CAPABILITIES) */
-
-  /* Properly switch egid,gid,euid,uid here or bail out */
-  if (setgroups(1, &pw->pw_gid)) {
-    log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
-             (int)pw->pw_gid, strerror(errno));
-    if (old_uid == pw->pw_uid) {
-      log_warn(LD_GENERAL, "Tor is already running as %s.  You do not need "
-               "the \"User\" option if you are already running as the user "
-               "you want to be.  (If you did not set the User option in your "
-               "torrc, check whether it was specified on the command line "
-               "by a startup script.)", user);
-    } else {
-      log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
-               " as root.");
-    }
-    return -1;
-  }
-
-  if (setegid(pw->pw_gid)) {
-    log_warn(LD_GENERAL, "Error setting egid to %d: %s",
-             (int)pw->pw_gid, strerror(errno));
-    return -1;
-  }
-
-  if (setgid(pw->pw_gid)) {
-    log_warn(LD_GENERAL, "Error setting gid to %d: %s",
-             (int)pw->pw_gid, strerror(errno));
-    return -1;
-  }
-
-  if (setuid(pw->pw_uid)) {
-    log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
-             user, (int)pw->pw_uid, strerror(errno));
-    return -1;
-  }
-
-  if (seteuid(pw->pw_uid)) {
-    log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
-             user, (int)pw->pw_uid, strerror(errno));
-    return -1;
-  }
-
-  /* This is how OpenBSD rolls:
-  if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) ||
-      setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) {
-      setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) {
-    log_warn(LD_GENERAL, "Error setting configured UID/GID: %s",
-    strerror(errno));
-    return -1;
-  }
-  */
-
-  /* We've properly switched egid, gid, euid, uid, and supplementary groups if
-   * we're here. */
-#ifdef HAVE_LINUX_CAPABILITIES
-  if (keep_bindlow) {
-    if (drop_capabilities(0))
-      return -1;
-  }
-#endif /* defined(HAVE_LINUX_CAPABILITIES) */
-
-#if !defined(CYGWIN) && !defined(__CYGWIN__)
-  /* If we tried to drop privilege to a group/user other than root, attempt to
-   * restore root (E)(U|G)ID, and abort if the operation succeeds */
-
-  /* Only check for privilege dropping if we were asked to be non-root */
-  if (pw->pw_uid) {
-    /* Try changing GID/EGID */
-    if (pw->pw_gid != old_gid &&
-        (setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
-      log_warn(LD_GENERAL, "Was able to restore group credentials even after "
-               "switching GID: this means that the setgid code didn't work.");
-      return -1;
-    }
-
-    /* Try changing UID/EUID */
-    if (pw->pw_uid != old_uid &&
-        (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
-      log_warn(LD_GENERAL, "Was able to restore user credentials even after "
-               "switching UID: this means that the setuid code didn't work.");
-      return -1;
-    }
-  }
-#endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */
-
-  /* Check what really happened */
-  if (log_credential_status()) {
-    return -1;
-  }
-
-  have_already_switched_id = 1; /* mark success so we never try again */
-
-#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \
-  defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
-  if (pw->pw_uid) {
-    /* Re-enable core dumps if we're not running as root. */
-    log_info(LD_CONFIG, "Re-enabling coredumps");
-    if (prctl(PR_SET_DUMPABLE, 1)) {
-      log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno));
-    }
-  }
-#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */
-  return 0;
-
-#else /* !(!defined(_WIN32)) */
-  (void)user;
-  (void)flags;
-
-  log_warn(LD_CONFIG, "Switching users is unsupported on your OS.");
-  return -1;
-#endif /* !defined(_WIN32) */
-}
-
-/* We only use the linux prctl for now. There is no Win32 support; this may
- * also work on various BSD systems and Mac OS X - send testing feedback!
- *
- * On recent Gnu/Linux kernels it is possible to create a system-wide policy
- * that will prevent non-root processes from attaching to other processes
- * unless they are the parent process; thus gdb can attach to programs that
- * they execute but they cannot attach to other processes running as the same
- * user. The system wide policy may be set with the sysctl
- * kernel.yama.ptrace_scope or by inspecting
- * /proc/sys/kernel/yama/ptrace_scope and it is 1 by default on Ubuntu 11.04.
- *
- * This ptrace scope will be ignored on Gnu/Linux for users with
- * CAP_SYS_PTRACE and so it is very likely that root will still be able to
- * attach to the Tor process.
- */
-/** Attempt to disable debugger attachment: return 1 on success, -1 on
- * failure, and 0 if we don't know how to try on this platform. */
-int
-tor_disable_debugger_attach(void)
-{
-  int r = -1;
-  log_debug(LD_CONFIG,
-            "Attemping to disable debugger attachment to Tor for "
-            "unprivileged users.");
-#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) \
-  && defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
-#define TRIED_TO_DISABLE
-  r = prctl(PR_SET_DUMPABLE, 0);
-#elif defined(__APPLE__) && defined(PT_DENY_ATTACH)
-#define TRIED_TO_ATTACH
-  r = ptrace(PT_DENY_ATTACH, 0, 0, 0);
-#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) ... || ... */
-
-  // XXX: TODO - Mac OS X has dtrace and this may be disabled.
-  // XXX: TODO - Windows probably has something similar
-#ifdef TRIED_TO_DISABLE
-  if (r == 0) {
-    log_debug(LD_CONFIG,"Debugger attachment disabled for "
-              "unprivileged users.");
-    return 1;
-  } else {
-    log_warn(LD_CONFIG, "Unable to disable debugger attaching: %s",
-             strerror(errno));
-  }
-#endif /* defined(TRIED_TO_DISABLE) */
-#undef TRIED_TO_DISABLE
-  return r;
-}
-
-#ifndef HAVE__NSGETENVIRON
-#ifndef HAVE_EXTERN_ENVIRON_DECLARED
-/* Some platforms declare environ under some circumstances, others don't. */
-#ifndef RUNNING_DOXYGEN
-extern char **environ;
-#endif
-#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */
-#endif /* !defined(HAVE__NSGETENVIRON) */
-
-/** Return the current environment. This is a portable replacement for
- * 'environ'. */
-char **
-get_environment(void)
-{
-#ifdef HAVE__NSGETENVIRON
-  /* This is for compatibility between OSX versions.  Otherwise (for example)
-   * when we do a mostly-static build on OSX 10.7, the resulting binary won't
-   * work on OSX 10.6. */
-  return *_NSGetEnviron();
-#else /* !(defined(HAVE__NSGETENVIRON)) */
-  return environ;
-#endif /* defined(HAVE__NSGETENVIRON) */
-}
-
 /** Get name of current host and write it to <b>name</b> array, whose
  * length is specified by <b>namelen</b> argument. Return 0 upon
  * successful completion; otherwise return return -1. (Currently,
@@ -965,93 +550,6 @@ compute_num_cpus(void)
   return num_cpus;
 }
 
-#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
-#define HAVE_UNIX_MLOCKALL
-#endif
-
-#ifdef HAVE_UNIX_MLOCKALL
-/** Attempt to raise the current and max rlimit to infinity for our process.
- * This only needs to be done once and can probably only be done when we have
- * not already dropped privileges.
- */
-static int
-tor_set_max_memlock(void)
-{
-  /* Future consideration for Windows is probably SetProcessWorkingSetSize
-   * This is similar to setting the memory rlimit of RLIMIT_MEMLOCK
-   * http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx
-   */
-
-  struct rlimit limit;
-
-  /* RLIM_INFINITY is -1 on some platforms. */
-  limit.rlim_cur = RLIM_INFINITY;
-  limit.rlim_max = RLIM_INFINITY;
-
-  if (setrlimit(RLIMIT_MEMLOCK, &limit) == -1) {
-    if (errno == EPERM) {
-      log_warn(LD_GENERAL, "You appear to lack permissions to change memory "
-                           "limits. Are you root?");
-    }
-    log_warn(LD_GENERAL, "Unable to raise RLIMIT_MEMLOCK: %s",
-             strerror(errno));
-    return -1;
-  }
-
-  return 0;
-}
-#endif /* defined(HAVE_UNIX_MLOCKALL) */
-
-/** Attempt to lock all current and all future memory pages.
- * This should only be called once and while we're privileged.
- * Like mlockall() we return 0 when we're successful and -1 when we're not.
- * Unlike mlockall() we return 1 if we've already attempted to lock memory.
- */
-int
-tor_mlockall(void)
-{
-  static int memory_lock_attempted = 0;
-
-  if (memory_lock_attempted) {
-    return 1;
-  }
-
-  memory_lock_attempted = 1;
-
-  /*
-   * Future consideration for Windows may be VirtualLock
-   * VirtualLock appears to implement mlock() but not mlockall()
-   *
-   * http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx
-   */
-
-#ifdef HAVE_UNIX_MLOCKALL
-  if (tor_set_max_memlock() == 0) {
-    log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY.");
-  }
-
-  if (mlockall(MCL_CURRENT|MCL_FUTURE) == 0) {
-    log_info(LD_GENERAL, "Insecure OS paging is effectively disabled.");
-    return 0;
-  } else {
-    if (errno == ENOSYS) {
-      /* Apple - it's 2009! I'm looking at you. Grrr. */
-      log_notice(LD_GENERAL, "It appears that mlockall() is not available on "
-                             "your platform.");
-    } else if (errno == EPERM) {
-      log_notice(LD_GENERAL, "It appears that you lack the permissions to "
-                             "lock memory. Are you root?");
-    }
-    log_notice(LD_GENERAL, "Unable to lock all current and future memory "
-                           "pages: %s", strerror(errno));
-    return -1;
-  }
-#else /* !(defined(HAVE_UNIX_MLOCKALL)) */
-  log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?");
-  return -1;
-#endif /* defined(HAVE_UNIX_MLOCKALL) */
-}
-
 /**
  * On Windows, WSAEWOULDBLOCK is not always correct: when you see it,
  * you need to ask the socket for its actual errno.  Also, you need to
diff --git a/src/common/compat.h b/src/common/compat.h
index 574dc5720..018caab24 100644
--- a/src/common/compat.h
+++ b/src/common/compat.h
@@ -137,28 +137,10 @@ MOCK_DECL(const char *, get_uname, (void));
 typedef unsigned long rlim_t;
 #endif
 int set_max_file_descriptors(rlim_t limit, int *max);
-int tor_disable_debugger_attach(void);
-
-#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
-#define HAVE_LINUX_CAPABILITIES
-#endif
-
-int have_capability_support(void);
-
-/** Flag for switch_id; see switch_id() for documentation */
-#define SWITCH_ID_KEEP_BINDLOW    (1<<0)
-/** Flag for switch_id; see switch_id() for documentation */
-#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
-int switch_id(const char *user, unsigned flags);
-
-char **get_environment(void);
-
 MOCK_DECL(int, get_total_system_memory, (size_t *mem_out));
 
 int compute_num_cpus(void);
 
-int tor_mlockall(void);
-
 /** Macros for MIN/MAX.  Never use these when the arguments could have
  * side-effects.
  * {With GCC extensions we could probably define a safer MIN/MAX.  But
diff --git a/src/common/include.am b/src/common/include.am
index ee35b9d56..2d7297665 100644
--- a/src/common/include.am
+++ b/src/common/include.am
@@ -29,7 +29,6 @@ LIBOR_A_SRC = \
   src/common/compat.c					\
   src/common/compat_time.c				\
   src/common/util.c					\
-  src/common/util_process.c				\
   src/common/token_bucket.c				\
   src/common/workqueue.c				\
   $(libor_extra_source)					\
@@ -71,7 +70,6 @@ COMMONHEADERS = \
   src/common/timers.h				\
   src/common/token_bucket.h			\
   src/common/util.h				\
-  src/common/util_process.h			\
   src/common/workqueue.h
 
 noinst_HEADERS+= $(COMMONHEADERS)
diff --git a/src/common/util.c b/src/common/util.c
index f641c1081..4a26998c1 100644
--- a/src/common/util.c
+++ b/src/common/util.c
@@ -23,7 +23,7 @@
 #include "lib/net/address.h"
 #include "lib/sandbox/sandbox.h"
 #include "lib/err/backtrace.h"
-#include "common/util_process.h"
+#include "lib/process/waitpid.h"
 #include "lib/encoding/binascii.h"
 
 #ifdef _WIN32
@@ -445,179 +445,6 @@ tv_to_msec(const struct timeval *tv)
   return conv;
 }
 
-/* =====
- * File helpers
- * ===== */
-
-/*
- *    Filesystem operations.
- */
-
-/* =====
- * Process helpers
- * ===== */
-
-#ifndef _WIN32
-/* Based on code contributed by christian grothoff */
-/** True iff we've called start_daemon(). */
-static int start_daemon_called = 0;
-/** True iff we've called finish_daemon(). */
-static int finish_daemon_called = 0;
-/** Socketpair used to communicate between parent and child process while
- * daemonizing. */
-static int daemon_filedes[2];
-/** Start putting the process into daemon mode: fork and drop all resources
- * except standard fds.  The parent process never returns, but stays around
- * until finish_daemon is called.  (Note: it's safe to call this more
- * than once: calls after the first are ignored.)
- */
-void
-start_daemon(void)
-{
-  pid_t pid;
-
-  if (start_daemon_called)
-    return;
-  start_daemon_called = 1;
-
-  if (pipe(daemon_filedes)) {
-    /* LCOV_EXCL_START */
-    log_err(LD_GENERAL,"pipe failed; exiting. Error was %s", strerror(errno));
-    exit(1); // exit ok: during daemonize, pipe failed.
-    /* LCOV_EXCL_STOP */
-  }
-  pid = fork();
-  if (pid < 0) {
-    /* LCOV_EXCL_START */
-    log_err(LD_GENERAL,"fork failed. Exiting.");
-    exit(1); // exit ok: during daemonize, fork failed
-    /* LCOV_EXCL_STOP */
-  }
-  if (pid) {  /* Parent */
-    int ok;
-    char c;
-
-    close(daemon_filedes[1]); /* we only read */
-    ok = -1;
-    while (0 < read(daemon_filedes[0], &c, sizeof(char))) {
-      if (c == '.')
-        ok = 1;
-    }
-    fflush(stdout);
-    if (ok == 1)
-      exit(0); // exit ok: during daemonize, daemonizing.
-    else
-      exit(1); /* child reported error. exit ok: daemonize failed. */
-  } else { /* Child */
-    close(daemon_filedes[0]); /* we only write */
-
-    (void) setsid(); /* Detach from controlling terminal */
-    /*
-     * Fork one more time, so the parent (the session group leader) can exit.
-     * This means that we, as a non-session group leader, can never regain a
-     * controlling terminal.   This part is recommended by Stevens's
-     * _Advanced Programming in the Unix Environment_.
-     */
-    if (fork() != 0) {
-      exit(0); // exit ok: during daemonize, fork failed (2)
-    }
-    set_main_thread(); /* We are now the main thread. */
-
-    return;
-  }
-}
-
-/** Finish putting the process into daemon mode: drop standard fds, and tell
- * the parent process to exit.  (Note: it's safe to call this more than once:
- * calls after the first are ignored.  Calls start_daemon first if it hasn't
- * been called already.)
- */
-void
-finish_daemon(const char *desired_cwd)
-{
-  int nullfd;
-  char c = '.';
-  if (finish_daemon_called)
-    return;
-  if (!start_daemon_called)
-    start_daemon();
-  finish_daemon_called = 1;
-
-  if (!desired_cwd)
-    desired_cwd = "/";
-   /* Don't hold the wrong FS mounted */
-  if (chdir(desired_cwd) < 0) {
-    log_err(LD_GENERAL,"chdir to \"%s\" failed. Exiting.",desired_cwd);
-    exit(1); // exit ok: during daemonize, chdir failed.
-  }
-
-  nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0);
-  if (nullfd < 0) {
-    /* LCOV_EXCL_START */
-    log_err(LD_GENERAL,"/dev/null can't be opened. Exiting.");
-    exit(1); // exit ok: during daemonize, couldn't open /dev/null
-    /* LCOV_EXCL_STOP */
-  }
-  /* close fds linking to invoking terminal, but
-   * close usual incoming fds, but redirect them somewhere
-   * useful so the fds don't get reallocated elsewhere.
-   */
-  if (dup2(nullfd,0) < 0 ||
-      dup2(nullfd,1) < 0 ||
-      dup2(nullfd,2) < 0) {
-    /* LCOV_EXCL_START */
-    log_err(LD_GENERAL,"dup2 failed. Exiting.");
-    exit(1); // exit ok: during daemonize, dup2 failed.
-    /* LCOV_EXCL_STOP */
-  }
-  if (nullfd > 2)
-    close(nullfd);
-  /* signal success */
-  if (write(daemon_filedes[1], &c, sizeof(char)) != sizeof(char)) {
-    log_err(LD_GENERAL,"write failed. Exiting.");
-  }
-  close(daemon_filedes[1]);
-}
-#else /* !(!defined(_WIN32)) */
-/* defined(_WIN32) */
-void
-start_daemon(void)
-{
-}
-void
-finish_daemon(const char *cp)
-{
-  (void)cp;
-}
-#endif /* !defined(_WIN32) */
-
-/** Write the current process ID, followed by NL, into <b>filename</b>.
- * Return 0 on success, -1 on failure.
- */
-int
-write_pidfile(const char *filename)
-{
-  FILE *pidfile;
-
-  if ((pidfile = fopen(filename, "w")) == NULL) {
-    log_warn(LD_FS, "Unable to open \"%s\" for writing: %s", filename,
-             strerror(errno));
-    return -1;
-  } else {
-#ifdef _WIN32
-    int pid = (int)_getpid();
-#else
-    int pid = (int)getpid();
-#endif
-    int rv = 0;
-    if (fprintf(pidfile, "%d\n", pid) < 0)
-      rv = -1;
-    if (fclose(pidfile) < 0)
-      rv = -1;
-    return rv;
-  }
-}
-
 #ifdef _WIN32
 HANDLE
 load_windows_system_library(const TCHAR *library_name)
@@ -633,1370 +460,6 @@ load_windows_system_library(const TCHAR *library_name)
 }
 #endif /* defined(_WIN32) */
 
-/** Format a single argument for being put on a Windows command line.
- * Returns a newly allocated string */
-static char *
-format_win_cmdline_argument(const char *arg)
-{
-  char *formatted_arg;
-  char need_quotes;
-  const char *c;
-  int i;
-  int bs_counter = 0;
-  /* Backslash we can point to when one is inserted into the string */
-  const char backslash = '\\';
-
-  /* Smartlist of *char */
-  smartlist_t *arg_chars;
-  arg_chars = smartlist_new();
-
-  /* Quote string if it contains whitespace or is empty */
-  need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]);
-
-  /* Build up smartlist of *chars */
-  for (c=arg; *c != '\0'; c++) {
-    if ('"' == *c) {
-      /* Double up backslashes preceding a quote */
-      for (i=0; i<(bs_counter*2); i++)
-        smartlist_add(arg_chars, (void*)&backslash);
-      bs_counter = 0;
-      /* Escape the quote */
-      smartlist_add(arg_chars, (void*)&backslash);
-      smartlist_add(arg_chars, (void*)c);
-    } else if ('\\' == *c) {
-      /* Count backslashes until we know whether to double up */
-      bs_counter++;
-    } else {
-      /* Don't double up slashes preceding a non-quote */
-      for (i=0; i<bs_counter; i++)
-        smartlist_add(arg_chars, (void*)&backslash);
-      bs_counter = 0;
-      smartlist_add(arg_chars, (void*)c);
-    }
-  }
-  /* Don't double up trailing backslashes */
-  for (i=0; i<bs_counter; i++)
-    smartlist_add(arg_chars, (void*)&backslash);
-
-  /* Allocate space for argument, quotes (if needed), and terminator */
-  const size_t formatted_arg_len = smartlist_len(arg_chars) +
-    (need_quotes ? 2 : 0) + 1;
-  formatted_arg = tor_malloc_zero(formatted_arg_len);
-
-  /* Add leading quote */
-  i=0;
-  if (need_quotes)
-    formatted_arg[i++] = '"';
-
-  /* Add characters */
-  SMARTLIST_FOREACH(arg_chars, char*, ch,
-  {
-    formatted_arg[i++] = *ch;
-  });
-
-  /* Add trailing quote */
-  if (need_quotes)
-    formatted_arg[i++] = '"';
-  formatted_arg[i] = '\0';
-
-  smartlist_free(arg_chars);
-  return formatted_arg;
-}
-
-/** Format a command line for use on Windows, which takes the command as a
- * string rather than string array. Follows the rules from "Parsing C++
- * Command-Line Arguments" in MSDN. Algorithm based on list2cmdline in the
- * Python subprocess module. Returns a newly allocated string */
-char *
-tor_join_win_cmdline(const char *argv[])
-{
-  smartlist_t *argv_list;
-  char *joined_argv;
-  int i;
-
-  /* Format each argument and put the result in a smartlist */
-  argv_list = smartlist_new();
-  for (i=0; argv[i] != NULL; i++) {
-    smartlist_add(argv_list, (void *)format_win_cmdline_argument(argv[i]));
-  }
-
-  /* Join the arguments with whitespace */
-  joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL);
-
-  /* Free the newly allocated arguments, and the smartlist */
-  SMARTLIST_FOREACH(argv_list, char *, arg,
-  {
-    tor_free(arg);
-  });
-  smartlist_free(argv_list);
-
-  return joined_argv;
-}
-
-#ifndef _WIN32
-/** Format <b>child_state</b> and <b>saved_errno</b> as a hex string placed in
- * <b>hex_errno</b>.  Called between fork and _exit, so must be signal-handler
- * safe.
- *
- * <b>hex_errno</b> must have at least HEX_ERRNO_SIZE+1 bytes available.
- *
- * The format of <b>hex_errno</b> is: "CHILD_STATE/ERRNO\n", left-padded
- * with spaces. CHILD_STATE indicates where
- * in the process of starting the child process did the failure occur (see
- * CHILD_STATE_* macros for definition), and SAVED_ERRNO is the value of
- * errno when the failure occurred.
- *
- * On success return the number of characters added to hex_errno, not counting
- * the terminating NUL; return -1 on error.
- */
-STATIC int
-format_helper_exit_status(unsigned char child_state, int saved_errno,
-                          char *hex_errno)
-{
-  unsigned int unsigned_errno;
-  int written, left;
-  char *cur;
-  size_t i;
-  int res = -1;
-
-  /* Fill hex_errno with spaces, and a trailing newline (memset may
-     not be signal handler safe, so we can't use it) */
-  for (i = 0; i < (HEX_ERRNO_SIZE - 1); i++)
-    hex_errno[i] = ' ';
-  hex_errno[HEX_ERRNO_SIZE - 1] = '\n';
-
-  /* Convert errno to be unsigned for hex conversion */
-  if (saved_errno < 0) {
-    // Avoid overflow on the cast to unsigned int when result is INT_MIN
-    // by adding 1 to the signed int negative value,
-    // then, after it has been negated and cast to unsigned,
-    // adding the original 1 back (the double-addition is intentional).
-    // Otherwise, the cast to signed could cause a temporary int
-    // to equal INT_MAX + 1, which is undefined.
-    unsigned_errno = ((unsigned int) -(saved_errno + 1)) + 1;
-  } else {
-    unsigned_errno = (unsigned int) saved_errno;
-  }
-
-  /*
-   * Count how many chars of space we have left, and keep a pointer into the
-   * current point in the buffer.
-   */
-  left = HEX_ERRNO_SIZE+1;
-  cur = hex_errno;
-
-  /* Emit child_state */
-  written = format_hex_number_sigsafe(child_state, cur, left);
-
-  if (written <= 0)
-    goto err;
-
-  /* Adjust left and cur */
-  left -= written;
-  cur += written;
-  if (left <= 0)
-    goto err;
-
-  /* Now the '/' */
-  *cur = '/';
-
-  /* Adjust left and cur */
-  ++cur;
-  --left;
-  if (left <= 0)
-    goto err;
-
-  /* Need minus? */
-  if (saved_errno < 0) {
-    *cur = '-';
-    ++cur;
-    --left;
-    if (left <= 0)
-      goto err;
-  }
-
-  /* Emit unsigned_errno */
-  written = format_hex_number_sigsafe(unsigned_errno, cur, left);
-
-  if (written <= 0)
-    goto err;
-
-  /* Adjust left and cur */
-  left -= written;
-  cur += written;
-
-  /* Check that we have enough space left for a newline and a NUL */
-  if (left <= 1)
-    goto err;
-
-  /* Emit the newline and NUL */
-  *cur++ = '\n';
-  *cur++ = '\0';
-
-  res = (int)(cur - hex_errno - 1);
-
-  goto done;
-
- err:
-  /*
-   * In error exit, just write a '\0' in the first char so whatever called
-   * this at least won't fall off the end.
-   */
-  *hex_errno = '\0';
-
- done:
-  return res;
-}
-#endif /* !defined(_WIN32) */
-
-/* Maximum number of file descriptors, if we cannot get it via sysconf() */
-#define DEFAULT_MAX_FD 256
-
-/** Terminate the process of <b>process_handle</b>, if that process has not
- * already exited.
- *
- * Return 0 if we succeeded in terminating the process (or if the process
- * already exited), and -1 if we tried to kill the process but failed.
- *
- * Based on code originally borrowed from Python's os.kill. */
-int
-tor_terminate_process(process_handle_t *process_handle)
-{
-#ifdef _WIN32
-  if (tor_get_exit_code(process_handle, 0, NULL) == PROCESS_EXIT_RUNNING) {
-    HANDLE handle = process_handle->pid.hProcess;
-
-    if (!TerminateProcess(handle, 0))
-      return -1;
-    else
-      return 0;
-  }
-#else /* !(defined(_WIN32)) */
-  if (process_handle->waitpid_cb) {
-    /* We haven't got a waitpid yet, so we can just kill off the process. */
-    return kill(process_handle->pid, SIGTERM);
-  }
-#endif /* defined(_WIN32) */
-
-  return 0; /* We didn't need to kill the process, so report success */
-}
-
-/** Return the Process ID of <b>process_handle</b>. */
-int
-tor_process_get_pid(process_handle_t *process_handle)
-{
-#ifdef _WIN32
-  return (int) process_handle->pid.dwProcessId;
-#else
-  return (int) process_handle->pid;
-#endif
-}
-
-#ifdef _WIN32
-HANDLE
-tor_process_get_stdout_pipe(process_handle_t *process_handle)
-{
-  return process_handle->stdout_pipe;
-}
-#else /* !(defined(_WIN32)) */
-/* DOCDOC tor_process_get_stdout_pipe */
-int
-tor_process_get_stdout_pipe(process_handle_t *process_handle)
-{
-  return process_handle->stdout_pipe;
-}
-#endif /* defined(_WIN32) */
-
-/* DOCDOC process_handle_new */
-static process_handle_t *
-process_handle_new(void)
-{
-  process_handle_t *out = tor_malloc_zero(sizeof(process_handle_t));
-
-#ifdef _WIN32
-  out->stdin_pipe = INVALID_HANDLE_VALUE;
-  out->stdout_pipe = INVALID_HANDLE_VALUE;
-  out->stderr_pipe = INVALID_HANDLE_VALUE;
-#else
-  out->stdin_pipe = -1;
-  out->stdout_pipe = -1;
-  out->stderr_pipe = -1;
-#endif /* defined(_WIN32) */
-
-  return out;
-}
-
-#ifndef _WIN32
-/** Invoked when a process that we've launched via tor_spawn_background() has
- * been found to have terminated.
- */
-static void
-process_handle_waitpid_cb(int status, void *arg)
-{
-  process_handle_t *process_handle = arg;
-
-  process_handle->waitpid_exit_status = status;
-  clear_waitpid_callback(process_handle->waitpid_cb);
-  if (process_handle->status == PROCESS_STATUS_RUNNING)
-    process_handle->status = PROCESS_STATUS_NOTRUNNING;
-  process_handle->waitpid_cb = 0;
-}
-#endif /* !defined(_WIN32) */
-
-/**
- * @name child-process states
- *
- * Each of these values represents a possible state that a child process can
- * be in.  They're used to determine what to say when telling the parent how
- * far along we were before failure.
- *
- * @{
- */
-#define CHILD_STATE_INIT 0
-#define CHILD_STATE_PIPE 1
-#define CHILD_STATE_MAXFD 2
-#define CHILD_STATE_FORK 3
-#define CHILD_STATE_DUPOUT 4
-#define CHILD_STATE_DUPERR 5
-#define CHILD_STATE_DUPIN 6
-#define CHILD_STATE_CLOSEFD 7
-#define CHILD_STATE_EXEC 8
-#define CHILD_STATE_FAILEXEC 9
-/** @} */
-/**
- * Boolean.  If true, then Tor may call execve or CreateProcess via
- * tor_spawn_background.
- **/
-static int may_spawn_background_process = 1;
-/**
- * Turn off may_spawn_background_process, so that all future calls to
- * tor_spawn_background are guaranteed to fail.
- **/
-void
-tor_disable_spawning_background_processes(void)
-{
-  may_spawn_background_process = 0;
-}
-/** Start a program in the background. If <b>filename</b> contains a '/', then
- * it will be treated as an absolute or relative path.  Otherwise, on
- * non-Windows systems, the system path will be searched for <b>filename</b>.
- * On Windows, only the current directory will be searched. Here, to search the
- * system path (as well as the application directory, current working
- * directory, and system directories), set filename to NULL.
- *
- * The strings in <b>argv</b> will be passed as the command line arguments of
- * the child program (following convention, argv[0] should normally be the
- * filename of the executable, and this must be the case if <b>filename</b> is
- * NULL). The last element of argv must be NULL. A handle to the child process
- * will be returned in process_handle (which must be non-NULL). Read
- * process_handle.status to find out if the process was successfully launched.
- * For convenience, process_handle.status is returned by this function.
- *
- * Some parts of this code are based on the POSIX subprocess module from
- * Python, and example code from
- * http://msdn.microsoft.com/en-us/library/ms682499%28v=vs.85%29.aspx.
- */
-int
-tor_spawn_background(const char *const filename, const char **argv,
-                     process_environment_t *env,
-                     process_handle_t **process_handle_out)
-{
-  if (BUG(may_spawn_background_process == 0)) {
-    /* We should never reach this point if we're forbidden to spawn
-     * processes. Instead we should have caught the attempt earlier. */
-    return PROCESS_STATUS_ERROR;
-  }
-
-#ifdef _WIN32
-  HANDLE stdout_pipe_read = NULL;
-  HANDLE stdout_pipe_write = NULL;
-  HANDLE stderr_pipe_read = NULL;
-  HANDLE stderr_pipe_write = NULL;
-  HANDLE stdin_pipe_read = NULL;
-  HANDLE stdin_pipe_write = NULL;
-  process_handle_t *process_handle;
-  int status;
-
-  STARTUPINFOA siStartInfo;
-  BOOL retval = FALSE;
-
-  SECURITY_ATTRIBUTES saAttr;
-  char *joined_argv;
-
-  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
-  saAttr.bInheritHandle = TRUE;
-  /* TODO: should we set explicit security attributes? (#2046, comment 5) */
-  saAttr.lpSecurityDescriptor = NULL;
-
-  /* Assume failure to start process */
-  status = PROCESS_STATUS_ERROR;
-
-  /* Set up pipe for stdout */
-  if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, &saAttr, 0)) {
-    log_warn(LD_GENERAL,
-      "Failed to create pipe for stdout communication with child process: %s",
-      format_win32_error(GetLastError()));
-    return status;
-  }
-  if (!SetHandleInformation(stdout_pipe_read, HANDLE_FLAG_INHERIT, 0)) {
-    log_warn(LD_GENERAL,
-      "Failed to configure pipe for stdout communication with child "
-      "process: %s", format_win32_error(GetLastError()));
-    return status;
-  }
-
-  /* Set up pipe for stderr */
-  if (!CreatePipe(&stderr_pipe_read, &stderr_pipe_write, &saAttr, 0)) {
-    log_warn(LD_GENERAL,
-      "Failed to create pipe for stderr communication with child process: %s",
-      format_win32_error(GetLastError()));
-    return status;
-  }
-  if (!SetHandleInformation(stderr_pipe_read, HANDLE_FLAG_INHERIT, 0)) {
-    log_warn(LD_GENERAL,
-      "Failed to configure pipe for stderr communication with child "
-      "process: %s", format_win32_error(GetLastError()));
-    return status;
-  }
-
-  /* Set up pipe for stdin */
-  if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, &saAttr, 0)) {
-    log_warn(LD_GENERAL,
-      "Failed to create pipe for stdin communication with child process: %s",
-      format_win32_error(GetLastError()));
-    return status;
-  }
-  if (!SetHandleInformation(stdin_pipe_write, HANDLE_FLAG_INHERIT, 0)) {
-    log_warn(LD_GENERAL,
-      "Failed to configure pipe for stdin communication with child "
-      "process: %s", format_win32_error(GetLastError()));
-    return status;
-  }
-
-  /* Create the child process */
-
-  /* Windows expects argv to be a whitespace delimited string, so join argv up
-   */
-  joined_argv = tor_join_win_cmdline(argv);
-
-  process_handle = process_handle_new();
-  process_handle->status = status;
-
-  ZeroMemory(&(process_handle->pid), sizeof(PROCESS_INFORMATION));
-  ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
-  siStartInfo.cb = sizeof(STARTUPINFO);
-  siStartInfo.hStdError = stderr_pipe_write;
-  siStartInfo.hStdOutput = stdout_pipe_write;
-  siStartInfo.hStdInput = stdin_pipe_read;
-  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
-
-  /* Create the child process */
-
-  retval = CreateProcessA(filename,      // module name
-                 joined_argv,   // command line
-  /* TODO: should we set explicit security attributes? (#2046, comment 5) */
-                 NULL,          // process security attributes
-                 NULL,          // primary thread security attributes
-                 TRUE,          // handles are inherited
-  /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess()
-   * work?) */
-                 CREATE_NO_WINDOW,             // creation flags
-                 (env==NULL) ? NULL : env->windows_environment_block,
-                 NULL,          // use parent's current directory
-                 &siStartInfo,  // STARTUPINFO pointer
-                 &(process_handle->pid));  // receives PROCESS_INFORMATION
-
-  tor_free(joined_argv);
-
-  if (!retval) {
-    log_warn(LD_GENERAL,
-      "Failed to create child process %s: %s", filename?filename:argv[0],
-      format_win32_error(GetLastError()));
-    tor_free(process_handle);
-  } else  {
-    /* TODO: Close hProcess and hThread in process_handle->pid? */
-    process_handle->stdout_pipe = stdout_pipe_read;
-    process_handle->stderr_pipe = stderr_pipe_read;
-    process_handle->stdin_pipe = stdin_pipe_write;
-    status = process_handle->status = PROCESS_STATUS_RUNNING;
-  }
-
-  /* TODO: Close pipes on exit */
-  *process_handle_out = process_handle;
-  return status;
-#else /* !(defined(_WIN32)) */
-  pid_t pid;
-  int stdout_pipe[2];
-  int stderr_pipe[2];
-  int stdin_pipe[2];
-  int fd, retval;
-  process_handle_t *process_handle;
-  int status;
-
-  const char *error_message = SPAWN_ERROR_MESSAGE;
-  size_t error_message_length;
-
-  /* Represents where in the process of spawning the program is;
-     this is used for printing out the error message */
-  unsigned char child_state = CHILD_STATE_INIT;
-
-  char hex_errno[HEX_ERRNO_SIZE + 2]; /* + 1 should be sufficient actually */
-
-  static int max_fd = -1;
-
-  status = PROCESS_STATUS_ERROR;
-
-  /* We do the strlen here because strlen() is not signal handler safe,
-     and we are not allowed to use unsafe functions between fork and exec */
-  error_message_length = strlen(error_message);
-
-  // child_state = CHILD_STATE_PIPE;
-
-  /* Set up pipe for redirecting stdout, stderr, and stdin of child */
-  retval = pipe(stdout_pipe);
-  if (-1 == retval) {
-    log_warn(LD_GENERAL,
-      "Failed to set up pipe for stdout communication with child process: %s",
-       strerror(errno));
-    return status;
-  }
-
-  retval = pipe(stderr_pipe);
-  if (-1 == retval) {
-    log_warn(LD_GENERAL,
-      "Failed to set up pipe for stderr communication with child process: %s",
-      strerror(errno));
-
-    close(stdout_pipe[0]);
-    close(stdout_pipe[1]);
-
-    return status;
-  }
-
-  retval = pipe(stdin_pipe);
-  if (-1 == retval) {
-    log_warn(LD_GENERAL,
-      "Failed to set up pipe for stdin communication with child process: %s",
-       strerror(errno));
-
-    close(stdout_pipe[0]);
-    close(stdout_pipe[1]);
-    close(stderr_pipe[0]);
-    close(stderr_pipe[1]);
-
-    return status;
-  }
-
-  // child_state = CHILD_STATE_MAXFD;
-
-#ifdef _SC_OPEN_MAX
-  if (-1 == max_fd) {
-    max_fd = (int) sysconf(_SC_OPEN_MAX);
-    if (max_fd == -1) {
-      max_fd = DEFAULT_MAX_FD;
-      log_warn(LD_GENERAL,
-               "Cannot find maximum file descriptor, assuming %d", max_fd);
-    }
-  }
-#else /* !(defined(_SC_OPEN_MAX)) */
-  max_fd = DEFAULT_MAX_FD;
-#endif /* defined(_SC_OPEN_MAX) */
-
-  // child_state = CHILD_STATE_FORK;
-
-  pid = fork();
-  if (0 == pid) {
-    /* In child */
-
-#if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
-    /* Attempt to have the kernel issue a SIGTERM if the parent
-     * goes away. Certain attributes of the binary being execve()ed
-     * will clear this during the execve() call, but it's better
-     * than nothing.
-     */
-    prctl(PR_SET_PDEATHSIG, SIGTERM);
-#endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) */
-
-    child_state = CHILD_STATE_DUPOUT;
-
-    /* Link child stdout to the write end of the pipe */
-    retval = dup2(stdout_pipe[1], STDOUT_FILENO);
-    if (-1 == retval)
-        goto error;
-
-    child_state = CHILD_STATE_DUPERR;
-
-    /* Link child stderr to the write end of the pipe */
-    retval = dup2(stderr_pipe[1], STDERR_FILENO);
-    if (-1 == retval)
-        goto error;
-
-    child_state = CHILD_STATE_DUPIN;
-
-    /* Link child stdin to the read end of the pipe */
-    retval = dup2(stdin_pipe[0], STDIN_FILENO);
-    if (-1 == retval)
-      goto error;
-
-    // child_state = CHILD_STATE_CLOSEFD;
-
-    close(stderr_pipe[0]);
-    close(stderr_pipe[1]);
-    close(stdout_pipe[0]);
-    close(stdout_pipe[1]);
-    close(stdin_pipe[0]);
-    close(stdin_pipe[1]);
-
-    /* Close all other fds, including the read end of the pipe */
-    /* XXX: We should now be doing enough FD_CLOEXEC setting to make
-     * this needless. */
-    for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) {
-      close(fd);
-    }
-
-    // child_state = CHILD_STATE_EXEC;
-
-    /* Call the requested program. We need the cast because
-       execvp doesn't define argv as const, even though it
-       does not modify the arguments */
-    if (env)
-      execve(filename, (char *const *) argv, env->unixoid_environment_block);
-    else {
-      static char *new_env[] = { NULL };
-      execve(filename, (char *const *) argv, new_env);
-    }
-
-    /* If we got here, the exec or open(/dev/null) failed */
-
-    child_state = CHILD_STATE_FAILEXEC;
-
-  error:
-    {
-      /* XXX: are we leaking fds from the pipe? */
-      int n, err=0;
-      ssize_t nbytes;
-
-      n = format_helper_exit_status(child_state, errno, hex_errno);
-
-      if (n >= 0) {
-        /* Write the error message. GCC requires that we check the return
-           value, but there is nothing we can do if it fails */
-        /* TODO: Don't use STDOUT, use a pipe set up just for this purpose */
-        nbytes = write(STDOUT_FILENO, error_message, error_message_length);
-        err = (nbytes < 0);
-        nbytes = write(STDOUT_FILENO, hex_errno, n);
-        err += (nbytes < 0);
-      }
-
-      _exit(err?254:255); // exit ok: in child.
-    }
-
-    /* Never reached, but avoids compiler warning */
-    return status; // LCOV_EXCL_LINE
-  }
-
-  /* In parent */
-
-  if (-1 == pid) {
-    log_warn(LD_GENERAL, "Failed to fork child process: %s", strerror(errno));
-    close(stdin_pipe[0]);
-    close(stdin_pipe[1]);
-    close(stdout_pipe[0]);
-    close(stdout_pipe[1]);
-    close(stderr_pipe[0]);
-    close(stderr_pipe[1]);
-    return status;
-  }
-
-  process_handle = process_handle_new();
-  process_handle->status = status;
-  process_handle->pid = pid;
-
-  /* TODO: If the child process forked but failed to exec, waitpid it */
-
-  /* Return read end of the pipes to caller, and close write end */
-  process_handle->stdout_pipe = stdout_pipe[0];
-  retval = close(stdout_pipe[1]);
-
-  if (-1 == retval) {
-    log_warn(LD_GENERAL,
-            "Failed to close write end of stdout pipe in parent process: %s",
-            strerror(errno));
-  }
-
-  process_handle->waitpid_cb = set_waitpid_callback(pid,
-                                                    process_handle_waitpid_cb,
-                                                    process_handle);
-
-  process_handle->stderr_pipe = stderr_pipe[0];
-  retval = close(stderr_pipe[1]);
-
-  if (-1 == retval) {
-    log_warn(LD_GENERAL,
-            "Failed to close write end of stderr pipe in parent process: %s",
-            strerror(errno));
-  }
-
-  /* Return write end of the stdin pipe to caller, and close the read end */
-  process_handle->stdin_pipe = stdin_pipe[1];
-  retval = close(stdin_pipe[0]);
-
-  if (-1 == retval) {
-    log_warn(LD_GENERAL,
-            "Failed to close read end of stdin pipe in parent process: %s",
-            strerror(errno));
-  }
-
-  status = process_handle->status = PROCESS_STATUS_RUNNING;
-  /* Set stdin/stdout/stderr pipes to be non-blocking */
-  if (fcntl(process_handle->stdout_pipe, F_SETFL, O_NONBLOCK) < 0 ||
-      fcntl(process_handle->stderr_pipe, F_SETFL, O_NONBLOCK) < 0 ||
-      fcntl(process_handle->stdin_pipe, F_SETFL, O_NONBLOCK) < 0) {
-    log_warn(LD_GENERAL, "Failed to set stderror/stdout/stdin pipes "
-             "nonblocking in parent process: %s", strerror(errno));
-  }
-
-  *process_handle_out = process_handle;
-  return status;
-#endif /* defined(_WIN32) */
-}
-
-/** Destroy all resources allocated by the process handle in
- *  <b>process_handle</b>.
- *  If <b>also_terminate_process</b> is true, also terminate the
- *  process of the process handle. */
-MOCK_IMPL(void,
-tor_process_handle_destroy,(process_handle_t *process_handle,
-                            int also_terminate_process))
-{
-  if (!process_handle)
-    return;
-
-  if (also_terminate_process) {
-    if (tor_terminate_process(process_handle) < 0) {
-      const char *errstr =
-#ifdef _WIN32
-        format_win32_error(GetLastError());
-#else
-        strerror(errno);
-#endif
-      log_notice(LD_GENERAL, "Failed to terminate process with "
-                 "PID '%d' ('%s').", tor_process_get_pid(process_handle),
-                 errstr);
-    } else {
-      log_info(LD_GENERAL, "Terminated process with PID '%d'.",
-               tor_process_get_pid(process_handle));
-    }
-  }
-
-  process_handle->status = PROCESS_STATUS_NOTRUNNING;
-
-#ifdef _WIN32
-  if (process_handle->stdout_pipe)
-    CloseHandle(process_handle->stdout_pipe);
-
-  if (process_handle->stderr_pipe)
-    CloseHandle(process_handle->stderr_pipe);
-
-  if (process_handle->stdin_pipe)
-    CloseHandle(process_handle->stdin_pipe);
-#else /* !(defined(_WIN32)) */
-  close(process_handle->stdout_pipe);
-  close(process_handle->stderr_pipe);
-  close(process_handle->stdin_pipe);
-
-  clear_waitpid_callback(process_handle->waitpid_cb);
-#endif /* defined(_WIN32) */
-
-  memset(process_handle, 0x0f, sizeof(process_handle_t));
-  tor_free(process_handle);
-}
-
-/** Get the exit code of a process specified by <b>process_handle</b> and store
- * it in <b>exit_code</b>, if set to a non-NULL value.  If <b>block</b> is set
- * to true, the call will block until the process has exited.  Otherwise if
- * the process is still running, the function will return
- * PROCESS_EXIT_RUNNING, and exit_code will be left unchanged. Returns
- * PROCESS_EXIT_EXITED if the process did exit. If there is a failure,
- * PROCESS_EXIT_ERROR will be returned and the contents of exit_code (if
- * non-NULL) will be undefined. N.B. Under *nix operating systems, this will
- * probably not work in Tor, because waitpid() is called in main.c to reap any
- * terminated child processes.*/
-int
-tor_get_exit_code(process_handle_t *process_handle,
-                  int block, int *exit_code)
-{
-#ifdef _WIN32
-  DWORD retval;
-  BOOL success;
-
-  if (block) {
-    /* Wait for the process to exit */
-    retval = WaitForSingleObject(process_handle->pid.hProcess, INFINITE);
-    if (retval != WAIT_OBJECT_0) {
-      log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
-              (int)retval, format_win32_error(GetLastError()));
-      return PROCESS_EXIT_ERROR;
-    }
-  } else {
-    retval = WaitForSingleObject(process_handle->pid.hProcess, 0);
-    if (WAIT_TIMEOUT == retval) {
-      /* Process has not exited */
-      return PROCESS_EXIT_RUNNING;
-    } else if (retval != WAIT_OBJECT_0) {
-      log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
-               (int)retval, format_win32_error(GetLastError()));
-      return PROCESS_EXIT_ERROR;
-    }
-  }
-
-  if (exit_code != NULL) {
-    success = GetExitCodeProcess(process_handle->pid.hProcess,
-                                 (PDWORD)exit_code);
-    if (!success) {
-      log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s",
-               format_win32_error(GetLastError()));
-      return PROCESS_EXIT_ERROR;
-    }
-  }
-#else /* !(defined(_WIN32)) */
-  int stat_loc;
-  int retval;
-
-  if (process_handle->waitpid_cb) {
-    /* We haven't processed a SIGCHLD yet. */
-    retval = waitpid(process_handle->pid, &stat_loc, block?0:WNOHANG);
-    if (retval == process_handle->pid) {
-      clear_waitpid_callback(process_handle->waitpid_cb);
-      process_handle->waitpid_cb = NULL;
-      process_handle->waitpid_exit_status = stat_loc;
-    }
-  } else {
-    /* We already got a SIGCHLD for this process, and handled it. */
-    retval = process_handle->pid;
-    stat_loc = process_handle->waitpid_exit_status;
-  }
-
-  if (!block && 0 == retval) {
-    /* Process has not exited */
-    return PROCESS_EXIT_RUNNING;
-  } else if (retval != process_handle->pid) {
-    log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s",
-             (int)process_handle->pid, strerror(errno));
-    return PROCESS_EXIT_ERROR;
-  }
-
-  if (!WIFEXITED(stat_loc)) {
-    log_warn(LD_GENERAL, "Process %d did not exit normally",
-             (int)process_handle->pid);
-    return PROCESS_EXIT_ERROR;
-  }
-
-  if (exit_code != NULL)
-    *exit_code = WEXITSTATUS(stat_loc);
-#endif /* defined(_WIN32) */
-
-  return PROCESS_EXIT_EXITED;
-}
-
-/** Helper: return the number of characters in <b>s</b> preceding the first
- * occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return
- * the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */
-static inline size_t
-str_num_before(const char *s, char ch)
-{
-  const char *cp = strchr(s, ch);
-  if (cp)
-    return cp - s;
-  else
-    return strlen(s);
-}
-
-/** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b>
- * to have the same name as strings in a process's environment. */
-int
-environment_variable_names_equal(const char *s1, const char *s2)
-{
-  size_t s1_name_len = str_num_before(s1, '=');
-  size_t s2_name_len = str_num_before(s2, '=');
-
-  return (s1_name_len == s2_name_len &&
-          tor_memeq(s1, s2, s1_name_len));
-}
-
-/** Free <b>env</b> (assuming it was produced by
- * process_environment_make). */
-void
-process_environment_free_(process_environment_t *env)
-{
-  if (env == NULL) return;
-
-  /* As both an optimization hack to reduce consing on Unixoid systems
-   * and a nice way to ensure that some otherwise-Windows-specific
-   * code will always get tested before changes to it get merged, the
-   * strings which env->unixoid_environment_block points to are packed
-   * into env->windows_environment_block. */
-  tor_free(env->unixoid_environment_block);
-  tor_free(env->windows_environment_block);
-
-  tor_free(env);
-}
-
-/** Make a process_environment_t containing the environment variables
- * specified in <b>env_vars</b> (as C strings of the form
- * "NAME=VALUE"). */
-process_environment_t *
-process_environment_make(struct smartlist_t *env_vars)
-{
-  process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t));
-  int n_env_vars = smartlist_len(env_vars);
-  int i;
-  size_t total_env_length;
-  smartlist_t *env_vars_sorted;
-
-  tor_assert(n_env_vars + 1 != 0);
-  env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *));
-  /* env->unixoid_environment_block is already NULL-terminated,
-   * because we assume that NULL == 0 (and check that during compilation). */
-
-  total_env_length = 1; /* terminating NUL of terminating empty string */
-  for (i = 0; i < n_env_vars; ++i) {
-    const char *s = smartlist_get(env_vars, (int)i);
-    size_t slen = strlen(s);
-
-    tor_assert(slen + 1 != 0);
-    tor_assert(slen + 1 < SIZE_MAX - total_env_length);
-    total_env_length += slen + 1;
-  }
-
-  env->windows_environment_block = tor_malloc_zero(total_env_length);
-  /* env->windows_environment_block is already
-   * (NUL-terminated-empty-string)-terminated. */
-
-  /* Some versions of Windows supposedly require that environment
-   * blocks be sorted.  Or maybe some Windows programs (or their
-   * runtime libraries) fail to look up strings in non-sorted
-   * environment blocks.
-   *
-   * Also, sorting strings makes it easy to find duplicate environment
-   * variables and environment-variable strings without an '=' on all
-   * OSes, and they can cause badness.  Let's complain about those. */
-  env_vars_sorted = smartlist_new();
-  smartlist_add_all(env_vars_sorted, env_vars);
-  smartlist_sort_strings(env_vars_sorted);
-
-  /* Now copy the strings into the environment blocks. */
-  {
-    char *cp = env->windows_environment_block;
-    const char *prev_env_var = NULL;
-
-    for (i = 0; i < n_env_vars; ++i) {
-      const char *s = smartlist_get(env_vars_sorted, (int)i);
-      size_t slen = strlen(s);
-      size_t s_name_len = str_num_before(s, '=');
-
-      if (s_name_len == slen) {
-        log_warn(LD_GENERAL,
-                 "Preparing an environment containing a variable "
-                 "without a value: %s",
-                 s);
-      }
-      if (prev_env_var != NULL &&
-          environment_variable_names_equal(s, prev_env_var)) {
-        log_warn(LD_GENERAL,
-                 "Preparing an environment containing two variables "
-                 "with the same name: %s and %s",
-                 prev_env_var, s);
-      }
-
-      prev_env_var = s;
-
-      /* Actually copy the string into the environment. */
-      memcpy(cp, s, slen+1);
-      env->unixoid_environment_block[i] = cp;
-      cp += slen+1;
-    }
-
-    tor_assert(cp == env->windows_environment_block + total_env_length - 1);
-  }
-
-  smartlist_free(env_vars_sorted);
-
-  return env;
-}
-
-/** Return a newly allocated smartlist containing every variable in
- * this process's environment, as a NUL-terminated string of the form
- * "NAME=VALUE".  Note that on some/many/most/all OSes, the parent
- * process can put strings not of that form in our environment;
- * callers should try to not get crashed by that.
- *
- * The returned strings are heap-allocated, and must be freed by the
- * caller. */
-struct smartlist_t *
-get_current_process_environment_variables(void)
-{
-  smartlist_t *sl = smartlist_new();
-
-  char **environ_tmp; /* Not const char ** ? Really? */
-  for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) {
-    smartlist_add_strdup(sl, *environ_tmp);
-  }
-
-  return sl;
-}
-
-/** For each string s in <b>env_vars</b> such that
- * environment_variable_names_equal(s, <b>new_var</b>), remove it; if
- * <b>free_p</b> is non-zero, call <b>free_old</b>(s).  If
- * <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */
-void
-set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
-                                      const char *new_var,
-                                      void (*free_old)(void*),
-                                      int free_p)
-{
-  SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) {
-    if (environment_variable_names_equal(s, new_var)) {
-      SMARTLIST_DEL_CURRENT(env_vars, s);
-      if (free_p) {
-        free_old((void *)s);
-      }
-    }
-  } SMARTLIST_FOREACH_END(s);
-
-  if (strchr(new_var, '=') != NULL) {
-    smartlist_add(env_vars, (void *)new_var);
-  }
-}
-
-#ifdef _WIN32
-/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes.  If
- * <b>hProcess</b> is NULL, the function will return immediately if there is
- * nothing more to read. Otherwise <b>hProcess</b> should be set to the handle
- * to the process owning the <b>h</b>. In this case, the function will exit
- * only once the process has exited, or <b>count</b> bytes are read. Returns
- * the number of bytes read, or -1 on error. */
-ssize_t
-tor_read_all_handle(HANDLE h, char *buf, size_t count,
-                    const process_handle_t *process)
-{
-  size_t numread = 0;
-  BOOL retval;
-  DWORD byte_count;
-  BOOL process_exited = FALSE;
-
-  if (count > SIZE_T_CEILING || count > SSIZE_MAX)
-    return -1;
-
-  while (numread < count) {
-    /* Check if there is anything to read */
-    retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL);
-    if (!retval) {
-      log_warn(LD_GENERAL,
-        "Failed to peek from handle: %s",
-        format_win32_error(GetLastError()));
-      return -1;
-    } else if (0 == byte_count) {
-      /* Nothing available: process exited or it is busy */
-
-      /* Exit if we don't know whether the process is running */
-      if (NULL == process)
-        break;
-
-      /* The process exited and there's nothing left to read from it */
-      if (process_exited)
-        break;
-
-      /* If process is not running, check for output one more time in case
-         it wrote something after the peek was performed. Otherwise keep on
-         waiting for output */
-      tor_assert(process != NULL);
-      byte_count = WaitForSingleObject(process->pid.hProcess, 0);
-      if (WAIT_TIMEOUT != byte_count)
-        process_exited = TRUE;
-
-      continue;
-    }
-
-    /* There is data to read; read it */
-    retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL);
-    tor_assert(byte_count + numread <= count);
-    if (!retval) {
-      log_warn(LD_GENERAL, "Failed to read from handle: %s",
-        format_win32_error(GetLastError()));
-      return -1;
-    } else if (0 == byte_count) {
-      /* End of file */
-      break;
-    }
-    numread += byte_count;
-  }
-  return (ssize_t)numread;
-}
-#else /* !(defined(_WIN32)) */
-/** Read from a handle <b>fd</b> into <b>buf</b>, up to <b>count</b> bytes.  If
- * <b>process</b> is NULL, the function will return immediately if there is
- * nothing more to read. Otherwise data will be read until end of file, or
- * <b>count</b> bytes are read.  Returns the number of bytes read, or -1 on
- * error. Sets <b>eof</b> to true if <b>eof</b> is not NULL and the end of the
- * file has been reached. */
-ssize_t
-tor_read_all_handle(int fd, char *buf, size_t count,
-                    const process_handle_t *process,
-                    int *eof)
-{
-  size_t numread = 0;
-  ssize_t result;
-
-  if (eof)
-    *eof = 0;
-
-  if (count > SIZE_T_CEILING || count > SSIZE_MAX)
-    return -1;
-
-  while (numread < count) {
-    result = read(fd, buf+numread, count-numread);
-
-    if (result == 0) {
-      log_debug(LD_GENERAL, "read() reached end of file");
-      if (eof)
-        *eof = 1;
-      break;
-    } else if (result < 0 && errno == EAGAIN) {
-      if (process)
-        continue;
-      else
-        break;
-    } else if (result < 0) {
-      log_warn(LD_GENERAL, "read() failed: %s", strerror(errno));
-      return -1;
-    }
-
-    numread += result;
-  }
-
-  log_debug(LD_GENERAL, "read() read %d bytes from handle", (int)numread);
-  return (ssize_t)numread;
-}
-#endif /* defined(_WIN32) */
-
-/** Read from stdout of a process until the process exits. */
-ssize_t
-tor_read_all_from_process_stdout(const process_handle_t *process_handle,
-                                 char *buf, size_t count)
-{
-#ifdef _WIN32
-  return tor_read_all_handle(process_handle->stdout_pipe, buf, count,
-                             process_handle);
-#else
-  return tor_read_all_handle(process_handle->stdout_pipe, buf, count,
-                             process_handle, NULL);
-#endif /* defined(_WIN32) */
-}
-
-/** Read from stdout of a process until the process exits. */
-ssize_t
-tor_read_all_from_process_stderr(const process_handle_t *process_handle,
-                                 char *buf, size_t count)
-{
-#ifdef _WIN32
-  return tor_read_all_handle(process_handle->stderr_pipe, buf, count,
-                             process_handle);
-#else
-  return tor_read_all_handle(process_handle->stderr_pipe, buf, count,
-                             process_handle, NULL);
-#endif /* defined(_WIN32) */
-}
-
-/** Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be
- * modified. The resulting smartlist will consist of pointers to buf, so there
- * is no need to free the contents of sl. <b>buf</b> must be a NUL-terminated
- * string. <b>len</b> should be set to the length of the buffer excluding the
- * NUL. Non-printable characters (including NUL) will be replaced with "." */
-int
-tor_split_lines(smartlist_t *sl, char *buf, int len)
-{
-  /* Index in buf of the start of the current line */
-  int start = 0;
-  /* Index in buf of the current character being processed */
-  int cur = 0;
-  /* Are we currently in a line */
-  char in_line = 0;
-
-  /* Loop over string */
-  while (cur < len) {
-    /* Loop until end of line or end of string */
-    for (; cur < len; cur++) {
-      if (in_line) {
-        if ('\r' == buf[cur] || '\n' == buf[cur]) {
-          /* End of line */
-          buf[cur] = '\0';
-          /* Point cur to the next line */
-          cur++;
-          /* Line starts at start and ends with a nul */
-          break;
-        } else {
-          if (!TOR_ISPRINT(buf[cur]))
-            buf[cur] = '.';
-        }
-      } else {
-        if ('\r' == buf[cur] || '\n' == buf[cur]) {
-          /* Skip leading vertical space */
-          ;
-        } else {
-          in_line = 1;
-          start = cur;
-          if (!TOR_ISPRINT(buf[cur]))
-            buf[cur] = '.';
-        }
-      }
-    }
-    /* We are at the end of the line or end of string. If in_line is true there
-     * is a line which starts at buf+start and ends at a NUL. cur points to
-     * the character after the NUL. */
-    if (in_line)
-      smartlist_add(sl, (void *)(buf+start));
-    in_line = 0;
-  }
-  return smartlist_len(sl);
-}
-
-/** Return a string corresponding to <b>stream_status</b>. */
-const char *
-stream_status_to_string(enum stream_status stream_status)
-{
-  switch (stream_status) {
-    case IO_STREAM_OKAY:
-      return "okay";
-    case IO_STREAM_EAGAIN:
-      return "temporarily unavailable";
-    case IO_STREAM_TERM:
-      return "terminated";
-    case IO_STREAM_CLOSED:
-      return "closed";
-    default:
-      tor_fragile_assert();
-      return "unknown";
-  }
-}
-
-#ifdef _WIN32
-
-/** Return a smartlist containing lines outputted from
- *  <b>handle</b>. Return NULL on error, and set
- *  <b>stream_status_out</b> appropriately. */
-MOCK_IMPL(smartlist_t *,
-tor_get_lines_from_handle, (HANDLE *handle,
-                            enum stream_status *stream_status_out))
-{
-  int pos;
-  char stdout_buf[600] = {0};
-  smartlist_t *lines = NULL;
-
-  tor_assert(stream_status_out);
-
-  *stream_status_out = IO_STREAM_TERM;
-
-  pos = tor_read_all_handle(handle, stdout_buf, sizeof(stdout_buf) - 1, NULL);
-  if (pos < 0) {
-    *stream_status_out = IO_STREAM_TERM;
-    return NULL;
-  }
-  if (pos == 0) {
-    *stream_status_out = IO_STREAM_EAGAIN;
-    return NULL;
-  }
-
-  /* End with a null even if there isn't a \r\n at the end */
-  /* TODO: What if this is a partial line? */
-  stdout_buf[pos] = '\0';
-
-  /* Split up the buffer */
-  lines = smartlist_new();
-  tor_split_lines(lines, stdout_buf, pos);
-
-  /* Currently 'lines' is populated with strings residing on the
-     stack. Replace them with their exact copies on the heap: */
-  SMARTLIST_FOREACH(lines, char *, line,
-                    SMARTLIST_REPLACE_CURRENT(lines, line, tor_strdup(line)));
-
-  *stream_status_out = IO_STREAM_OKAY;
-
-  return lines;
-}
-
-#else /* !(defined(_WIN32)) */
-
-/** Return a smartlist containing lines outputted from
- *  <b>fd</b>. Return NULL on error, and set
- *  <b>stream_status_out</b> appropriately. */
-MOCK_IMPL(smartlist_t *,
-tor_get_lines_from_handle, (int fd, enum stream_status *stream_status_out))
-{
-  enum stream_status stream_status;
-  char stdout_buf[400];
-  smartlist_t *lines = NULL;
-
-  while (1) {
-    memset(stdout_buf, 0, sizeof(stdout_buf));
-
-    stream_status = get_string_from_pipe(fd,
-                                         stdout_buf, sizeof(stdout_buf) - 1);
-    if (stream_status != IO_STREAM_OKAY)
-      goto done;
-
-    if (!lines) lines = smartlist_new();
-    smartlist_split_string(lines, stdout_buf, "\n", 0, 0);
-  }
-
- done:
-  *stream_status_out = stream_status;
-  return lines;
-}
-
-#endif /* defined(_WIN32) */
-
-/** Reads from <b>fd</b> and stores input in <b>buf_out</b> making
- *  sure it's below <b>count</b> bytes.
- *  If the string has a trailing newline, we strip it off.
- *
- * This function is specifically created to handle input from managed
- * proxies, according to the pluggable transports spec. Make sure it
- * fits your needs before using it.
- *
- * Returns:
- * IO_STREAM_CLOSED: If the stream is closed.
- * IO_STREAM_EAGAIN: If there is nothing to read and we should check back
- *  later.
- * IO_STREAM_TERM: If something is wrong with the stream.
- * IO_STREAM_OKAY: If everything went okay and we got a string
- *  in <b>buf_out</b>. */
-enum stream_status
-get_string_from_pipe(int fd, char *buf_out, size_t count)
-{
-  ssize_t ret;
-
-  tor_assert(count <= INT_MAX);
-
-  ret = read(fd, buf_out, count);
-
-  if (ret == 0)
-    return IO_STREAM_CLOSED;
-  else if (ret < 0 && errno == EAGAIN)
-    return IO_STREAM_EAGAIN;
-  else if (ret < 0)
-    return IO_STREAM_TERM;
-
-  if (buf_out[ret - 1] == '\n') {
-    /* Remove the trailing newline */
-    buf_out[ret - 1] = '\0';
-  } else
-    buf_out[ret] = '\0';
-
-  return IO_STREAM_OKAY;
-}
-
 /** Initialize the insecure RNG <b>rng</b> from a seed value <b>seed</b>. */
 void
 tor_init_weak_random(tor_weak_rng_t *rng, unsigned seed)
diff --git a/src/common/util.h b/src/common/util.h
index 549bbf9aa..1967d23e4 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -91,138 +91,10 @@ int64_t tv_to_msec(const struct timeval *tv);
   ((isSock) ? read_all_from_socket((fd), (buf), (count)) \
             : read_all_from_fd((int)(fd), (buf), (count)))
 
-/** Status of an I/O stream. */
-enum stream_status {
-  IO_STREAM_OKAY,
-  IO_STREAM_EAGAIN,
-  IO_STREAM_TERM,
-  IO_STREAM_CLOSED
-};
-
-const char *stream_status_to_string(enum stream_status stream_status);
-
-enum stream_status get_string_from_pipe(int fd, char *buf, size_t count);
-
-/* Process helpers */
-void start_daemon(void);
-void finish_daemon(const char *desired_cwd);
-int write_pidfile(const char *filename);
-
-void tor_disable_spawning_background_processes(void);
-
-typedef struct process_handle_t process_handle_t;
-typedef struct process_environment_t process_environment_t;
-int tor_spawn_background(const char *const filename, const char **argv,
-                         process_environment_t *env,
-                         process_handle_t **process_handle_out);
-
-#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
-
 #ifdef _WIN32
 HANDLE load_windows_system_library(const TCHAR *library_name);
 #endif
 
-int environment_variable_names_equal(const char *s1, const char *s2);
-
-/* DOCDOC process_environment_t */
-struct process_environment_t {
-  /** A pointer to a sorted empty-string-terminated sequence of
-   * NUL-terminated strings of the form "NAME=VALUE". */
-  char *windows_environment_block;
-  /** A pointer to a NULL-terminated array of pointers to
-   * NUL-terminated strings of the form "NAME=VALUE". */
-  char **unixoid_environment_block;
-};
-
-process_environment_t *process_environment_make(struct smartlist_t *env_vars);
-void process_environment_free_(process_environment_t *env);
-#define process_environment_free(env) \
-  FREE_AND_NULL(process_environment_t, process_environment_free_, (env))
-
-struct smartlist_t *get_current_process_environment_variables(void);
-
-void set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
-                                           const char *new_var,
-                                           void (*free_old)(void*),
-                                           int free_p);
-
-/* Values of process_handle_t.status. */
-#define PROCESS_STATUS_NOTRUNNING 0
-#define PROCESS_STATUS_RUNNING 1
-#define PROCESS_STATUS_ERROR -1
-
-#ifdef UTIL_PRIVATE
-struct waitpid_callback_t;
-/** Structure to represent the state of a process with which Tor is
- * communicating. The contents of this structure are private to util.c */
-struct process_handle_t {
-  /** One of the PROCESS_STATUS_* values */
-  int status;
-#ifdef _WIN32
-  HANDLE stdin_pipe;
-  HANDLE stdout_pipe;
-  HANDLE stderr_pipe;
-  PROCESS_INFORMATION pid;
-#else /* !(defined(_WIN32)) */
-  int stdin_pipe;
-  int stdout_pipe;
-  int stderr_pipe;
-  pid_t pid;
-  /** If the process has not given us a SIGCHLD yet, this has the
-   * waitpid_callback_t that gets invoked once it has. Otherwise this
-   * contains NULL. */
-  struct waitpid_callback_t *waitpid_cb;
-  /** The exit status reported by waitpid. */
-  int waitpid_exit_status;
-#endif /* defined(_WIN32) */
-};
-#endif /* defined(UTIL_PRIVATE) */
-
-/* Return values of tor_get_exit_code() */
-#define PROCESS_EXIT_RUNNING 1
-#define PROCESS_EXIT_EXITED 0
-#define PROCESS_EXIT_ERROR -1
-int tor_get_exit_code(process_handle_t *process_handle,
-                      int block, int *exit_code);
-int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
-#ifdef _WIN32
-ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
-                            const process_handle_t *process);
-#else
-ssize_t tor_read_all_handle(int fd, char *buf, size_t count,
-                            const process_handle_t *process,
-                            int *eof);
-#endif /* defined(_WIN32) */
-ssize_t tor_read_all_from_process_stdout(
-    const process_handle_t *process_handle, char *buf, size_t count);
-ssize_t tor_read_all_from_process_stderr(
-    const process_handle_t *process_handle, char *buf, size_t count);
-char *tor_join_win_cmdline(const char *argv[]);
-
-int tor_process_get_pid(process_handle_t *process_handle);
-#ifdef _WIN32
-HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle);
-#else
-int tor_process_get_stdout_pipe(process_handle_t *process_handle);
-#endif
-
-#ifdef _WIN32
-MOCK_DECL(struct smartlist_t *,
-tor_get_lines_from_handle,(HANDLE *handle,
-                           enum stream_status *stream_status));
-#else
-MOCK_DECL(struct smartlist_t *,
-tor_get_lines_from_handle,(int fd,
-                           enum stream_status *stream_status));
-#endif /* defined(_WIN32) */
-
-int
-tor_terminate_process(process_handle_t *process_handle);
-
-MOCK_DECL(void,
-tor_process_handle_destroy,(process_handle_t *process_handle,
-                            int also_terminate_process));
-
 /* ===== Insecure rng */
 typedef struct tor_weak_rng_t {
   uint32_t state;
@@ -237,19 +109,4 @@ int32_t tor_weak_random_range(tor_weak_rng_t *rng, int32_t top);
  * <b>n</b> */
 #define tor_weak_random_one_in_n(rng, n) (0==tor_weak_random_range((rng),(n)))
 
-#ifdef UTIL_PRIVATE
-/* Prototypes for private functions only used by util.c (and unit tests) */
-
-#ifndef _WIN32
-STATIC int format_helper_exit_status(unsigned char child_state,
-                              int saved_errno, char *hex_errno);
-
-/* Space for hex values of child state, a slash, saved_errno (with
-   leading minus) and newline (no null) */
-#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \
-                        1 + sizeof(int) * 2 + 1)
-#endif /* !defined(_WIN32) */
-
-#endif /* defined(UTIL_PRIVATE) */
-
 #endif /* !defined(TOR_UTIL_H) */
diff --git a/src/include.am b/src/include.am
index 90fafb482..621c906d9 100644
--- a/src/include.am
+++ b/src/include.am
@@ -17,6 +17,7 @@ include src/lib/log/include.am
 include src/lib/memarea/include.am
 include src/lib/malloc/include.am
 include src/lib/net/include.am
+include src/lib/process/include.am
 include src/lib/sandbox/include.am
 include src/lib/string/include.am
 include src/lib/smartlist_core/include.am
diff --git a/src/lib/process/.may_include b/src/lib/process/.may_include
new file mode 100644
index 000000000..b195fb005
--- /dev/null
+++ b/src/lib/process/.may_include
@@ -0,0 +1,15 @@
+orconfig.h
+
+lib/cc/*.h
+lib/container/*.h
+lib/ctime/*.h
+lib/err/*.h
+lib/fs/*.h
+lib/log/*.h
+lib/malloc/*.h
+lib/process/*.h
+lib/string/*.h
+lib/testsupport/*.h
+lib/thread/*.h
+
+ht.h
\ No newline at end of file
diff --git a/src/lib/process/daemon.c b/src/lib/process/daemon.c
new file mode 100644
index 000000000..edffb0468
--- /dev/null
+++ b/src/lib/process/daemon.c
@@ -0,0 +1,159 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "lib/process/daemon.h"
+
+#ifndef _WIN32
+
+#include "lib/fs/files.h"
+#include "lib/log/torlog.h"
+#include "lib/thread/threads.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Based on code contributed by christian grothoff */
+/** True iff we've called start_daemon(). */
+static int start_daemon_called = 0;
+/** True iff we've called finish_daemon(). */
+static int finish_daemon_called = 0;
+/** Socketpair used to communicate between parent and child process while
+ * daemonizing. */
+static int daemon_filedes[2];
+/** Start putting the process into daemon mode: fork and drop all resources
+ * except standard fds.  The parent process never returns, but stays around
+ * until finish_daemon is called.  (Note: it's safe to call this more
+ * than once: calls after the first are ignored.)
+ */
+void
+start_daemon(void)
+{
+  pid_t pid;
+
+  if (start_daemon_called)
+    return;
+  start_daemon_called = 1;
+
+  if (pipe(daemon_filedes)) {
+    /* LCOV_EXCL_START */
+    log_err(LD_GENERAL,"pipe failed; exiting. Error was %s", strerror(errno));
+    exit(1); // exit ok: during daemonize, pipe failed.
+    /* LCOV_EXCL_STOP */
+  }
+  pid = fork();
+  if (pid < 0) {
+    /* LCOV_EXCL_START */
+    log_err(LD_GENERAL,"fork failed. Exiting.");
+    exit(1); // exit ok: during daemonize, fork failed
+    /* LCOV_EXCL_STOP */
+  }
+  if (pid) {  /* Parent */
+    int ok;
+    char c;
+
+    close(daemon_filedes[1]); /* we only read */
+    ok = -1;
+    while (0 < read(daemon_filedes[0], &c, sizeof(char))) {
+      if (c == '.')
+        ok = 1;
+    }
+    fflush(stdout);
+    if (ok == 1)
+      exit(0); // exit ok: during daemonize, daemonizing.
+    else
+      exit(1); /* child reported error. exit ok: daemonize failed. */
+  } else { /* Child */
+    close(daemon_filedes[0]); /* we only write */
+
+    (void) setsid(); /* Detach from controlling terminal */
+    /*
+     * Fork one more time, so the parent (the session group leader) can exit.
+     * This means that we, as a non-session group leader, can never regain a
+     * controlling terminal.   This part is recommended by Stevens's
+     * _Advanced Programming in the Unix Environment_.
+     */
+    if (fork() != 0) {
+      exit(0); // exit ok: during daemonize, fork failed (2)
+    }
+    set_main_thread(); /* We are now the main thread. */
+
+    return;
+  }
+}
+
+/** Finish putting the process into daemon mode: drop standard fds, and tell
+ * the parent process to exit.  (Note: it's safe to call this more than once:
+ * calls after the first are ignored.  Calls start_daemon first if it hasn't
+ * been called already.)
+ */
+void
+finish_daemon(const char *desired_cwd)
+{
+  int nullfd;
+  char c = '.';
+  if (finish_daemon_called)
+    return;
+  if (!start_daemon_called)
+    start_daemon();
+  finish_daemon_called = 1;
+
+  if (!desired_cwd)
+    desired_cwd = "/";
+   /* Don't hold the wrong FS mounted */
+  if (chdir(desired_cwd) < 0) {
+    log_err(LD_GENERAL,"chdir to \"%s\" failed. Exiting.",desired_cwd);
+    exit(1); // exit ok: during daemonize, chdir failed.
+  }
+
+  nullfd = tor_open_cloexec("/dev/null", O_RDWR, 0);
+  if (nullfd < 0) {
+    /* LCOV_EXCL_START */
+    log_err(LD_GENERAL,"/dev/null can't be opened. Exiting.");
+    exit(1); // exit ok: during daemonize, couldn't open /dev/null
+    /* LCOV_EXCL_STOP */
+  }
+  /* close fds linking to invoking terminal, but
+   * close usual incoming fds, but redirect them somewhere
+   * useful so the fds don't get reallocated elsewhere.
+   */
+  if (dup2(nullfd,0) < 0 ||
+      dup2(nullfd,1) < 0 ||
+      dup2(nullfd,2) < 0) {
+    /* LCOV_EXCL_START */
+    log_err(LD_GENERAL,"dup2 failed. Exiting.");
+    exit(1); // exit ok: during daemonize, dup2 failed.
+    /* LCOV_EXCL_STOP */
+  }
+  if (nullfd > 2)
+    close(nullfd);
+  /* signal success */
+  if (write(daemon_filedes[1], &c, sizeof(char)) != sizeof(char)) {
+    log_err(LD_GENERAL,"write failed. Exiting.");
+  }
+  close(daemon_filedes[1]);
+}
+#else /* !(!defined(_WIN32)) */
+/* defined(_WIN32) */
+void
+start_daemon(void)
+{
+}
+void
+finish_daemon(const char *cp)
+{
+  (void)cp;
+}
+#endif /* !defined(_WIN32) */
diff --git a/src/lib/process/daemon.h b/src/lib/process/daemon.h
new file mode 100644
index 000000000..48a65b22e
--- /dev/null
+++ b/src/lib/process/daemon.h
@@ -0,0 +1,12 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_DAEMON_H
+#define TOR_DAEMON_H
+
+void start_daemon(void);
+void finish_daemon(const char *desired_cwd);
+
+#endif
diff --git a/src/lib/process/env.c b/src/lib/process/env.c
new file mode 100644
index 000000000..95e1e515d
--- /dev/null
+++ b/src/lib/process/env.c
@@ -0,0 +1,215 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "lib/process/env.h"
+
+#include "lib/malloc/util_malloc.h"
+#include "lib/ctime/di_ops.h"
+#include "lib/container/smartlist.h"
+#include "lib/log/util_bug.h"
+#include "lib/log/torlog.h"
+#include "lib/malloc/util_malloc.h"
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef HAVE__NSGETENVIRON
+#ifndef HAVE_EXTERN_ENVIRON_DECLARED
+/* Some platforms declare environ under some circumstances, others don't. */
+#ifndef RUNNING_DOXYGEN
+extern char **environ;
+#endif
+#endif /* !defined(HAVE_EXTERN_ENVIRON_DECLARED) */
+#endif /* !defined(HAVE__NSGETENVIRON) */
+
+/** Return the current environment. This is a portable replacement for
+ * 'environ'. */
+char **
+get_environment(void)
+{
+#ifdef HAVE__NSGETENVIRON
+  /* This is for compatibility between OSX versions.  Otherwise (for example)
+   * when we do a mostly-static build on OSX 10.7, the resulting binary won't
+   * work on OSX 10.6. */
+  return *_NSGetEnviron();
+#else /* !(defined(HAVE__NSGETENVIRON)) */
+  return environ;
+#endif /* defined(HAVE__NSGETENVIRON) */
+}
+
+/** Helper: return the number of characters in <b>s</b> preceding the first
+ * occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return
+ * the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */
+static inline size_t
+str_num_before(const char *s, char ch)
+{
+  const char *cp = strchr(s, ch);
+  if (cp)
+    return cp - s;
+  else
+    return strlen(s);
+}
+
+/** Return non-zero iff getenv would consider <b>s1</b> and <b>s2</b>
+ * to have the same name as strings in a process's environment. */
+int
+environment_variable_names_equal(const char *s1, const char *s2)
+{
+  size_t s1_name_len = str_num_before(s1, '=');
+  size_t s2_name_len = str_num_before(s2, '=');
+
+  return (s1_name_len == s2_name_len &&
+          tor_memeq(s1, s2, s1_name_len));
+}
+
+/** Free <b>env</b> (assuming it was produced by
+ * process_environment_make). */
+void
+process_environment_free_(process_environment_t *env)
+{
+  if (env == NULL) return;
+
+  /* As both an optimization hack to reduce consing on Unixoid systems
+   * and a nice way to ensure that some otherwise-Windows-specific
+   * code will always get tested before changes to it get merged, the
+   * strings which env->unixoid_environment_block points to are packed
+   * into env->windows_environment_block. */
+  tor_free(env->unixoid_environment_block);
+  tor_free(env->windows_environment_block);
+
+  tor_free(env);
+}
+
+/** Make a process_environment_t containing the environment variables
+ * specified in <b>env_vars</b> (as C strings of the form
+ * "NAME=VALUE"). */
+process_environment_t *
+process_environment_make(struct smartlist_t *env_vars)
+{
+  process_environment_t *env = tor_malloc_zero(sizeof(process_environment_t));
+  int n_env_vars = smartlist_len(env_vars);
+  int i;
+  size_t total_env_length;
+  smartlist_t *env_vars_sorted;
+
+  tor_assert(n_env_vars + 1 != 0);
+  env->unixoid_environment_block = tor_calloc(n_env_vars + 1, sizeof(char *));
+  /* env->unixoid_environment_block is already NULL-terminated,
+   * because we assume that NULL == 0 (and check that during compilation). */
+
+  total_env_length = 1; /* terminating NUL of terminating empty string */
+  for (i = 0; i < n_env_vars; ++i) {
+    const char *s = smartlist_get(env_vars, (int)i);
+    size_t slen = strlen(s);
+
+    tor_assert(slen + 1 != 0);
+    tor_assert(slen + 1 < SIZE_MAX - total_env_length);
+    total_env_length += slen + 1;
+  }
+
+  env->windows_environment_block = tor_malloc_zero(total_env_length);
+  /* env->windows_environment_block is already
+   * (NUL-terminated-empty-string)-terminated. */
+
+  /* Some versions of Windows supposedly require that environment
+   * blocks be sorted.  Or maybe some Windows programs (or their
+   * runtime libraries) fail to look up strings in non-sorted
+   * environment blocks.
+   *
+   * Also, sorting strings makes it easy to find duplicate environment
+   * variables and environment-variable strings without an '=' on all
+   * OSes, and they can cause badness.  Let's complain about those. */
+  env_vars_sorted = smartlist_new();
+  smartlist_add_all(env_vars_sorted, env_vars);
+  smartlist_sort_strings(env_vars_sorted);
+
+  /* Now copy the strings into the environment blocks. */
+  {
+    char *cp = env->windows_environment_block;
+    const char *prev_env_var = NULL;
+
+    for (i = 0; i < n_env_vars; ++i) {
+      const char *s = smartlist_get(env_vars_sorted, (int)i);
+      size_t slen = strlen(s);
+      size_t s_name_len = str_num_before(s, '=');
+
+      if (s_name_len == slen) {
+        log_warn(LD_GENERAL,
+                 "Preparing an environment containing a variable "
+                 "without a value: %s",
+                 s);
+      }
+      if (prev_env_var != NULL &&
+          environment_variable_names_equal(s, prev_env_var)) {
+        log_warn(LD_GENERAL,
+                 "Preparing an environment containing two variables "
+                 "with the same name: %s and %s",
+                 prev_env_var, s);
+      }
+
+      prev_env_var = s;
+
+      /* Actually copy the string into the environment. */
+      memcpy(cp, s, slen+1);
+      env->unixoid_environment_block[i] = cp;
+      cp += slen+1;
+    }
+
+    tor_assert(cp == env->windows_environment_block + total_env_length - 1);
+  }
+
+  smartlist_free(env_vars_sorted);
+
+  return env;
+}
+
+/** Return a newly allocated smartlist containing every variable in
+ * this process's environment, as a NUL-terminated string of the form
+ * "NAME=VALUE".  Note that on some/many/most/all OSes, the parent
+ * process can put strings not of that form in our environment;
+ * callers should try to not get crashed by that.
+ *
+ * The returned strings are heap-allocated, and must be freed by the
+ * caller. */
+struct smartlist_t *
+get_current_process_environment_variables(void)
+{
+  smartlist_t *sl = smartlist_new();
+
+  char **environ_tmp; /* Not const char ** ? Really? */
+  for (environ_tmp = get_environment(); *environ_tmp; ++environ_tmp) {
+    smartlist_add_strdup(sl, *environ_tmp);
+  }
+
+  return sl;
+}
+
+/** For each string s in <b>env_vars</b> such that
+ * environment_variable_names_equal(s, <b>new_var</b>), remove it; if
+ * <b>free_p</b> is non-zero, call <b>free_old</b>(s).  If
+ * <b>new_var</b> contains '=', insert it into <b>env_vars</b>. */
+void
+set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
+                                      const char *new_var,
+                                      void (*free_old)(void*),
+                                      int free_p)
+{
+  SMARTLIST_FOREACH_BEGIN(env_vars, const char *, s) {
+    if (environment_variable_names_equal(s, new_var)) {
+      SMARTLIST_DEL_CURRENT(env_vars, s);
+      if (free_p) {
+        free_old((void *)s);
+      }
+    }
+  } SMARTLIST_FOREACH_END(s);
+
+  if (strchr(new_var, '=') != NULL) {
+    smartlist_add(env_vars, (void *)new_var);
+  }
+}
diff --git a/src/lib/process/env.h b/src/lib/process/env.h
new file mode 100644
index 000000000..f22599355
--- /dev/null
+++ b/src/lib/process/env.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_ENV_H
+#define TOR_ENV_H
+
+char **get_environment(void);
+
+struct smartlist_t;
+
+int environment_variable_names_equal(const char *s1, const char *s2);
+
+/* DOCDOC process_environment_t */
+typedef struct process_environment_t {
+  /** A pointer to a sorted empty-string-terminated sequence of
+   * NUL-terminated strings of the form "NAME=VALUE". */
+  char *windows_environment_block;
+  /** A pointer to a NULL-terminated array of pointers to
+   * NUL-terminated strings of the form "NAME=VALUE". */
+  char **unixoid_environment_block;
+} process_environment_t;
+
+process_environment_t *process_environment_make(struct smartlist_t *env_vars);
+void process_environment_free_(process_environment_t *env);
+#define process_environment_free(env) \
+  FREE_AND_NULL(process_environment_t, process_environment_free_, (env))
+
+struct smartlist_t *get_current_process_environment_variables(void);
+
+void set_environment_variable_in_smartlist(struct smartlist_t *env_vars,
+                                           const char *new_var,
+                                           void (*free_old)(void*),
+                                           int free_p);
+#endif
diff --git a/src/lib/process/include.am b/src/lib/process/include.am
new file mode 100644
index 000000000..c6cc3a669
--- /dev/null
+++ b/src/lib/process/include.am
@@ -0,0 +1,29 @@
+
+noinst_LIBRARIES += src/lib/libtor-process.a
+
+if UNITTESTS_ENABLED
+noinst_LIBRARIES += src/lib/libtor-process-testing.a
+endif
+
+src_lib_libtor_process_a_SOURCES =		\
+	src/lib/process/daemon.c		\
+	src/lib/process/env.c			\
+	src/lib/process/pidfile.c		\
+	src/lib/process/restrict.c		\
+	src/lib/process/setuid.c		\
+	src/lib/process/subprocess.c		\
+	src/lib/process/waitpid.c
+
+src_lib_libtor_process_testing_a_SOURCES = \
+	$(src_lib_libtor_process_a_SOURCES)
+src_lib_libtor_process_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS)
+src_lib_libtor_process_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+
+noinst_HEADERS +=				\
+	src/lib/process/daemon.h		\
+	src/lib/process/env.h			\
+	src/lib/process/pidfile.h		\
+	src/lib/process/restrict.h		\
+	src/lib/process/setuid.h		\
+	src/lib/process/subprocess.h		\
+	src/lib/process/waitpid.h
diff --git a/src/lib/process/pidfile.c b/src/lib/process/pidfile.c
new file mode 100644
index 000000000..f016f2169
--- /dev/null
+++ b/src/lib/process/pidfile.c
@@ -0,0 +1,47 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "lib/process/pidfile.h"
+
+#include "lib/log/torlog.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+/** Write the current process ID, followed by NL, into <b>filename</b>.
+ * Return 0 on success, -1 on failure.
+ */
+int
+write_pidfile(const char *filename)
+{
+  FILE *pidfile;
+
+  if ((pidfile = fopen(filename, "w")) == NULL) {
+    log_warn(LD_FS, "Unable to open \"%s\" for writing: %s", filename,
+             strerror(errno));
+    return -1;
+  } else {
+#ifdef _WIN32
+    int pid = (int)_getpid();
+#else
+    int pid = (int)getpid();
+#endif
+    int rv = 0;
+    if (fprintf(pidfile, "%d\n", pid) < 0)
+      rv = -1;
+    if (fclose(pidfile) < 0)
+      rv = -1;
+    return rv;
+  }
+}
diff --git a/src/lib/process/pidfile.h b/src/lib/process/pidfile.h
new file mode 100644
index 000000000..c85cd1905
--- /dev/null
+++ b/src/lib/process/pidfile.h
@@ -0,0 +1,11 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_PIDFILE_H
+#define TOR_PIDFILE_H
+
+int write_pidfile(const char *filename);
+
+#endif
diff --git a/src/lib/process/restrict.c b/src/lib/process/restrict.c
new file mode 100644
index 000000000..85c04efbb
--- /dev/null
+++ b/src/lib/process/restrict.c
@@ -0,0 +1,144 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "lib/process/restrict.h"
+#include "lib/log/torlog.h"
+
+/* We only use the linux prctl for now. There is no Win32 support; this may
+ * also work on various BSD systems and Mac OS X - send testing feedback!
+ *
+ * On recent Gnu/Linux kernels it is possible to create a system-wide policy
+ * that will prevent non-root processes from attaching to other processes
+ * unless they are the parent process; thus gdb can attach to programs that
+ * they execute but they cannot attach to other processes running as the same
+ * user. The system wide policy may be set with the sysctl
+ * kernel.yama.ptrace_scope or by inspecting
+ * /proc/sys/kernel/yama/ptrace_scope and it is 1 by default on Ubuntu 11.04.
+ *
+ * This ptrace scope will be ignored on Gnu/Linux for users with
+ * CAP_SYS_PTRACE and so it is very likely that root will still be able to
+ * attach to the Tor process.
+ */
+/** Attempt to disable debugger attachment: return 1 on success, -1 on
+ * failure, and 0 if we don't know how to try on this platform. */
+int
+tor_disable_debugger_attach(void)
+{
+  int r = -1;
+  log_debug(LD_CONFIG,
+            "Attemping to disable debugger attachment to Tor for "
+            "unprivileged users.");
+#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) \
+  && defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
+#define TRIED_TO_DISABLE
+  r = prctl(PR_SET_DUMPABLE, 0);
+#elif defined(__APPLE__) && defined(PT_DENY_ATTACH)
+#define TRIED_TO_ATTACH
+  r = ptrace(PT_DENY_ATTACH, 0, 0, 0);
+#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) ... || ... */
+
+  // XXX: TODO - Mac OS X has dtrace and this may be disabled.
+  // XXX: TODO - Windows probably has something similar
+#ifdef TRIED_TO_DISABLE
+  if (r == 0) {
+    log_debug(LD_CONFIG,"Debugger attachment disabled for "
+              "unprivileged users.");
+    return 1;
+  } else {
+    log_warn(LD_CONFIG, "Unable to disable debugger attaching: %s",
+             strerror(errno));
+  }
+#endif /* defined(TRIED_TO_DISABLE) */
+#undef TRIED_TO_DISABLE
+  return r;
+}
+
+#if defined(HAVE_MLOCKALL) && HAVE_DECL_MLOCKALL && defined(RLIMIT_MEMLOCK)
+#define HAVE_UNIX_MLOCKALL
+#endif
+
+#ifdef HAVE_UNIX_MLOCKALL
+/** Attempt to raise the current and max rlimit to infinity for our process.
+ * This only needs to be done once and can probably only be done when we have
+ * not already dropped privileges.
+ */
+static int
+tor_set_max_memlock(void)
+{
+  /* Future consideration for Windows is probably SetProcessWorkingSetSize
+   * This is similar to setting the memory rlimit of RLIMIT_MEMLOCK
+   * http://msdn.microsoft.com/en-us/library/ms686234(VS.85).aspx
+   */
+
+  struct rlimit limit;
+
+  /* RLIM_INFINITY is -1 on some platforms. */
+  limit.rlim_cur = RLIM_INFINITY;
+  limit.rlim_max = RLIM_INFINITY;
+
+  if (setrlimit(RLIMIT_MEMLOCK, &limit) == -1) {
+    if (errno == EPERM) {
+      log_warn(LD_GENERAL, "You appear to lack permissions to change memory "
+                           "limits. Are you root?");
+    }
+    log_warn(LD_GENERAL, "Unable to raise RLIMIT_MEMLOCK: %s",
+             strerror(errno));
+    return -1;
+  }
+
+  return 0;
+}
+#endif /* defined(HAVE_UNIX_MLOCKALL) */
+
+/** Attempt to lock all current and all future memory pages.
+ * This should only be called once and while we're privileged.
+ * Like mlockall() we return 0 when we're successful and -1 when we're not.
+ * Unlike mlockall() we return 1 if we've already attempted to lock memory.
+ */
+int
+tor_mlockall(void)
+{
+  static int memory_lock_attempted = 0;
+
+  if (memory_lock_attempted) {
+    return 1;
+  }
+
+  memory_lock_attempted = 1;
+
+  /*
+   * Future consideration for Windows may be VirtualLock
+   * VirtualLock appears to implement mlock() but not mlockall()
+   *
+   * http://msdn.microsoft.com/en-us/library/aa366895(VS.85).aspx
+   */
+
+#ifdef HAVE_UNIX_MLOCKALL
+  if (tor_set_max_memlock() == 0) {
+    log_debug(LD_GENERAL, "RLIMIT_MEMLOCK is now set to RLIM_INFINITY.");
+  }
+
+  if (mlockall(MCL_CURRENT|MCL_FUTURE) == 0) {
+    log_info(LD_GENERAL, "Insecure OS paging is effectively disabled.");
+    return 0;
+  } else {
+    if (errno == ENOSYS) {
+      /* Apple - it's 2009! I'm looking at you. Grrr. */
+      log_notice(LD_GENERAL, "It appears that mlockall() is not available on "
+                             "your platform.");
+    } else if (errno == EPERM) {
+      log_notice(LD_GENERAL, "It appears that you lack the permissions to "
+                             "lock memory. Are you root?");
+    }
+    log_notice(LD_GENERAL, "Unable to lock all current and future memory "
+                           "pages: %s", strerror(errno));
+    return -1;
+  }
+#else /* !(defined(HAVE_UNIX_MLOCKALL)) */
+  log_warn(LD_GENERAL, "Unable to lock memory pages. mlockall() unsupported?");
+  return -1;
+#endif /* defined(HAVE_UNIX_MLOCKALL) */
+}
diff --git a/src/lib/process/restrict.h b/src/lib/process/restrict.h
new file mode 100644
index 000000000..d608c9c9b
--- /dev/null
+++ b/src/lib/process/restrict.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/**
+ * \file waitpid.h
+ * \brief Headers for waitpid.c
+ **/
+
+#ifndef TOR_RESTRICT_H
+#define TOR_RESTRICT_H
+
+int tor_disable_debugger_attach(void);
+int tor_mlockall(void);
+
+#endif /* !defined(TOR_RESTRICT_H) */
diff --git a/src/lib/process/setuid.c b/src/lib/process/setuid.c
new file mode 100644
index 000000000..6c6cb48f5
--- /dev/null
+++ b/src/lib/process/setuid.c
@@ -0,0 +1,375 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+#include "lib/process/setuid.h"
+
+#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
+#define HAVE_LINUX_CAPABILITIES
+#endif
+
+#include "lib/container/smartlist.h"
+#include "lib/fs/userdb.h"
+#include "lib/log/torlog.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/util_malloc.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+#ifndef _WIN32
+/** Log details of current user and group credentials. Return 0 on
+ * success. Logs and return -1 on failure.
+ */
+static int
+log_credential_status(void)
+{
+/** Log level to use when describing non-error UID/GID status. */
+#define CREDENTIAL_LOG_LEVEL LOG_INFO
+  /* Real, effective and saved UIDs */
+  uid_t ruid, euid, suid;
+  /* Read, effective and saved GIDs */
+  gid_t rgid, egid, sgid;
+  /* Supplementary groups */
+  gid_t *sup_gids = NULL;
+  int sup_gids_size;
+  /* Number of supplementary groups */
+  int ngids;
+
+  /* log UIDs */
+#ifdef HAVE_GETRESUID
+  if (getresuid(&ruid, &euid, &suid) != 0 ) {
+    log_warn(LD_GENERAL, "Error getting changed UIDs: %s", strerror(errno));
+    return -1;
+  } else {
+    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
+           "UID is %u (real), %u (effective), %u (saved)",
+           (unsigned)ruid, (unsigned)euid, (unsigned)suid);
+  }
+#else /* !(defined(HAVE_GETRESUID)) */
+  /* getresuid is not present on MacOS X, so we can't get the saved (E)UID */
+  ruid = getuid();
+  euid = geteuid();
+  (void)suid;
+
+  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
+         "UID is %u (real), %u (effective), unknown (saved)",
+         (unsigned)ruid, (unsigned)euid);
+#endif /* defined(HAVE_GETRESUID) */
+
+  /* log GIDs */
+#ifdef HAVE_GETRESGID
+  if (getresgid(&rgid, &egid, &sgid) != 0 ) {
+    log_warn(LD_GENERAL, "Error getting changed GIDs: %s", strerror(errno));
+    return -1;
+  } else {
+    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
+           "GID is %u (real), %u (effective), %u (saved)",
+           (unsigned)rgid, (unsigned)egid, (unsigned)sgid);
+  }
+#else /* !(defined(HAVE_GETRESGID)) */
+  /* getresgid is not present on MacOS X, so we can't get the saved (E)GID */
+  rgid = getgid();
+  egid = getegid();
+  (void)sgid;
+  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL,
+         "GID is %u (real), %u (effective), unknown (saved)",
+         (unsigned)rgid, (unsigned)egid);
+#endif /* defined(HAVE_GETRESGID) */
+
+  /* log supplementary groups */
+  sup_gids_size = 64;
+  sup_gids = tor_calloc(64, sizeof(gid_t));
+  while ((ngids = getgroups(sup_gids_size, sup_gids)) < 0 &&
+         errno == EINVAL &&
+         sup_gids_size < NGROUPS_MAX) {
+    sup_gids_size *= 2;
+    sup_gids = tor_reallocarray(sup_gids, sizeof(gid_t), sup_gids_size);
+  }
+
+  if (ngids < 0) {
+    log_warn(LD_GENERAL, "Error getting supplementary GIDs: %s",
+             strerror(errno));
+    tor_free(sup_gids);
+    return -1;
+  } else {
+    int i, retval = 0;
+    char *s = NULL;
+    smartlist_t *elts = smartlist_new();
+
+    for (i = 0; i<ngids; i++) {
+      smartlist_add_asprintf(elts, "%u", (unsigned)sup_gids[i]);
+    }
+
+    s = smartlist_join_strings(elts, " ", 0, NULL);
+
+    log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Supplementary groups are: %s",s);
+
+    tor_free(s);
+    SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
+    smartlist_free(elts);
+    tor_free(sup_gids);
+
+    return retval;
+  }
+
+  return 0;
+}
+#endif /* !defined(_WIN32) */
+
+/** Return true iff we were compiled with capability support, and capabilities
+ * seem to work. **/
+int
+have_capability_support(void)
+{
+#ifdef HAVE_LINUX_CAPABILITIES
+  cap_t caps = cap_get_proc();
+  if (caps == NULL)
+    return 0;
+  cap_free(caps);
+  return 1;
+#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
+  return 0;
+#endif /* defined(HAVE_LINUX_CAPABILITIES) */
+}
+
+#ifdef HAVE_LINUX_CAPABILITIES
+/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
+ * appropriate.
+ *
+ * If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
+ * CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
+ * setuid().
+ *
+ * If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
+ * PR_KEEPCAPS.
+ *
+ * Return 0 on success, and -1 on failure.
+ */
+static int
+drop_capabilities(int pre_setuid)
+{
+  /* We keep these three capabilities, and these only, as we setuid.
+   * After we setuid, we drop all but the first. */
+  const cap_value_t caplist[] = {
+    CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
+  };
+  const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
+  const int n_effective = pre_setuid ? 3 : 1;
+  const int n_permitted = pre_setuid ? 3 : 1;
+  const int n_inheritable = 1;
+  const int keepcaps = pre_setuid ? 1 : 0;
+
+  /* Sets whether we keep capabilities across a setuid. */
+  if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
+    log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
+             where, strerror(errno));
+    return -1;
+  }
+
+  cap_t caps = cap_get_proc();
+  if (!caps) {
+    log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
+             where, strerror(errno));
+    return -1;
+  }
+  cap_clear(caps);
+
+  cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
+  cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
+  cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
+
+  int r = cap_set_proc(caps);
+  cap_free(caps);
+  if (r < 0) {
+    log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
+             where, strerror(errno));
+    return -1;
+  }
+
+  return 0;
+}
+#endif /* defined(HAVE_LINUX_CAPABILITIES) */
+
+/** Call setuid and setgid to run as <b>user</b> and switch to their
+ * primary group.  Return 0 on success.  On failure, log and return -1.
+ *
+ * If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
+ * system to retain the abilitity to bind low ports.
+ *
+ * If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
+ * don't have capability support.
+ */
+int
+switch_id(const char *user, const unsigned flags)
+{
+#ifndef _WIN32
+  const struct passwd *pw = NULL;
+  uid_t old_uid;
+  gid_t old_gid;
+  static int have_already_switched_id = 0;
+  const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
+  const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
+
+  tor_assert(user);
+
+  if (have_already_switched_id)
+    return 0;
+
+  /* Log the initial credential state */
+  if (log_credential_status())
+    return -1;
+
+  log_fn(CREDENTIAL_LOG_LEVEL, LD_GENERAL, "Changing user and groups");
+
+  /* Get old UID/GID to check if we changed correctly */
+  old_uid = getuid();
+  old_gid = getgid();
+
+  /* Lookup the user and group information, if we have a problem, bail out. */
+  pw = tor_getpwnam(user);
+  if (pw == NULL) {
+    log_warn(LD_CONFIG, "Error setting configured user: %s not found", user);
+    return -1;
+  }
+
+#ifdef HAVE_LINUX_CAPABILITIES
+  (void) warn_if_no_caps;
+  if (keep_bindlow) {
+    if (drop_capabilities(1))
+      return -1;
+  }
+#else /* !(defined(HAVE_LINUX_CAPABILITIES)) */
+  (void) keep_bindlow;
+  if (warn_if_no_caps) {
+    log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
+             "on this system.");
+  }
+#endif /* defined(HAVE_LINUX_CAPABILITIES) */
+
+  /* Properly switch egid,gid,euid,uid here or bail out */
+  if (setgroups(1, &pw->pw_gid)) {
+    log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
+             (int)pw->pw_gid, strerror(errno));
+    if (old_uid == pw->pw_uid) {
+      log_warn(LD_GENERAL, "Tor is already running as %s.  You do not need "
+               "the \"User\" option if you are already running as the user "
+               "you want to be.  (If you did not set the User option in your "
+               "torrc, check whether it was specified on the command line "
+               "by a startup script.)", user);
+    } else {
+      log_warn(LD_GENERAL, "If you set the \"User\" option, you must start Tor"
+               " as root.");
+    }
+    return -1;
+  }
+
+  if (setegid(pw->pw_gid)) {
+    log_warn(LD_GENERAL, "Error setting egid to %d: %s",
+             (int)pw->pw_gid, strerror(errno));
+    return -1;
+  }
+
+  if (setgid(pw->pw_gid)) {
+    log_warn(LD_GENERAL, "Error setting gid to %d: %s",
+             (int)pw->pw_gid, strerror(errno));
+    return -1;
+  }
+
+  if (setuid(pw->pw_uid)) {
+    log_warn(LD_GENERAL, "Error setting configured uid to %s (%d): %s",
+             user, (int)pw->pw_uid, strerror(errno));
+    return -1;
+  }
+
+  if (seteuid(pw->pw_uid)) {
+    log_warn(LD_GENERAL, "Error setting configured euid to %s (%d): %s",
+             user, (int)pw->pw_uid, strerror(errno));
+    return -1;
+  }
+
+  /* This is how OpenBSD rolls:
+  if (setgroups(1, &pw->pw_gid) || setegid(pw->pw_gid) ||
+      setgid(pw->pw_gid) || setuid(pw->pw_uid) || seteuid(pw->pw_uid)) {
+      setgid(pw->pw_gid) || seteuid(pw->pw_uid) || setuid(pw->pw_uid)) {
+    log_warn(LD_GENERAL, "Error setting configured UID/GID: %s",
+    strerror(errno));
+    return -1;
+  }
+  */
+
+  /* We've properly switched egid, gid, euid, uid, and supplementary groups if
+   * we're here. */
+#ifdef HAVE_LINUX_CAPABILITIES
+  if (keep_bindlow) {
+    if (drop_capabilities(0))
+      return -1;
+  }
+#endif /* defined(HAVE_LINUX_CAPABILITIES) */
+
+#if !defined(CYGWIN) && !defined(__CYGWIN__)
+  /* If we tried to drop privilege to a group/user other than root, attempt to
+   * restore root (E)(U|G)ID, and abort if the operation succeeds */
+
+  /* Only check for privilege dropping if we were asked to be non-root */
+  if (pw->pw_uid) {
+    /* Try changing GID/EGID */
+    if (pw->pw_gid != old_gid &&
+        (setgid(old_gid) != -1 || setegid(old_gid) != -1)) {
+      log_warn(LD_GENERAL, "Was able to restore group credentials even after "
+               "switching GID: this means that the setgid code didn't work.");
+      return -1;
+    }
+
+    /* Try changing UID/EUID */
+    if (pw->pw_uid != old_uid &&
+        (setuid(old_uid) != -1 || seteuid(old_uid) != -1)) {
+      log_warn(LD_GENERAL, "Was able to restore user credentials even after "
+               "switching UID: this means that the setuid code didn't work.");
+      return -1;
+    }
+  }
+#endif /* !defined(CYGWIN) && !defined(__CYGWIN__) */
+
+  /* Check what really happened */
+  if (log_credential_status()) {
+    return -1;
+  }
+
+  have_already_switched_id = 1; /* mark success so we never try again */
+
+#if defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && \
+  defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
+  if (pw->pw_uid) {
+    /* Re-enable core dumps if we're not running as root. */
+    log_info(LD_CONFIG, "Re-enabling coredumps");
+    if (prctl(PR_SET_DUMPABLE, 1)) {
+      log_warn(LD_CONFIG, "Unable to re-enable coredumps: %s",strerror(errno));
+    }
+  }
+#endif /* defined(__linux__) && defined(HAVE_SYS_PRCTL_H) && ... */
+  return 0;
+
+#else /* !(!defined(_WIN32)) */
+  (void)user;
+  (void)flags;
+
+  log_warn(LD_CONFIG, "Switching users is unsupported on your OS.");
+  return -1;
+#endif /* !defined(_WIN32) */
+}
diff --git a/src/lib/process/setuid.h b/src/lib/process/setuid.h
new file mode 100644
index 000000000..61aeefe1b
--- /dev/null
+++ b/src/lib/process/setuid.h
@@ -0,0 +1,17 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_SETUID_H
+#define TOR_SETUID_H
+
+int have_capability_support(void);
+
+/** Flag for switch_id; see switch_id() for documentation */
+#define SWITCH_ID_KEEP_BINDLOW    (1<<0)
+/** Flag for switch_id; see switch_id() for documentation */
+#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
+int switch_id(const char *user, unsigned flags);
+
+#endif
diff --git a/src/lib/process/subprocess.c b/src/lib/process/subprocess.c
new file mode 100644
index 000000000..516494d10
--- /dev/null
+++ b/src/lib/process/subprocess.c
@@ -0,0 +1,1231 @@
+/* Copyright (c) 2003, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#define SUBPROCESS_PRIVATE
+#include "lib/process/subprocess.h"
+
+#include "lib/container/smartlist.h"
+#include "lib/err/torerr.h"
+#include "lib/log/torlog.h"
+#include "lib/log/util_bug.h"
+#include "lib/log/win32err.h"
+#include "lib/malloc/util_malloc.h"
+#include "lib/process/env.h"
+#include "lib/process/waitpid.h"
+#include "lib/string/compat_ctype.h"
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SIGNAL_H
+#include <signal.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#include <errno.h>
+#include <string.h>
+
+/** Format a single argument for being put on a Windows command line.
+ * Returns a newly allocated string */
+static char *
+format_win_cmdline_argument(const char *arg)
+{
+  char *formatted_arg;
+  char need_quotes;
+  const char *c;
+  int i;
+  int bs_counter = 0;
+  /* Backslash we can point to when one is inserted into the string */
+  const char backslash = '\\';
+
+  /* Smartlist of *char */
+  smartlist_t *arg_chars;
+  arg_chars = smartlist_new();
+
+  /* Quote string if it contains whitespace or is empty */
+  need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]);
+
+  /* Build up smartlist of *chars */
+  for (c=arg; *c != '\0'; c++) {
+    if ('"' == *c) {
+      /* Double up backslashes preceding a quote */
+      for (i=0; i<(bs_counter*2); i++)
+        smartlist_add(arg_chars, (void*)&backslash);
+      bs_counter = 0;
+      /* Escape the quote */
+      smartlist_add(arg_chars, (void*)&backslash);
+      smartlist_add(arg_chars, (void*)c);
+    } else if ('\\' == *c) {
+      /* Count backslashes until we know whether to double up */
+      bs_counter++;
+    } else {
+      /* Don't double up slashes preceding a non-quote */
+      for (i=0; i<bs_counter; i++)
+        smartlist_add(arg_chars, (void*)&backslash);
+      bs_counter = 0;
+      smartlist_add(arg_chars, (void*)c);
+    }
+  }
+  /* Don't double up trailing backslashes */
+  for (i=0; i<bs_counter; i++)
+    smartlist_add(arg_chars, (void*)&backslash);
+
+  /* Allocate space for argument, quotes (if needed), and terminator */
+  const size_t formatted_arg_len = smartlist_len(arg_chars) +
+    (need_quotes ? 2 : 0) + 1;
+  formatted_arg = tor_malloc_zero(formatted_arg_len);
+
+  /* Add leading quote */
+  i=0;
+  if (need_quotes)
+    formatted_arg[i++] = '"';
+
+  /* Add characters */
+  SMARTLIST_FOREACH(arg_chars, char*, ch,
+  {
+    formatted_arg[i++] = *ch;
+  });
+
+  /* Add trailing quote */
+  if (need_quotes)
+    formatted_arg[i++] = '"';
+  formatted_arg[i] = '\0';
+
+  smartlist_free(arg_chars);
+  return formatted_arg;
+}
+
+/** Format a command line for use on Windows, which takes the command as a
+ * string rather than string array. Follows the rules from "Parsing C++
+ * Command-Line Arguments" in MSDN. Algorithm based on list2cmdline in the
+ * Python subprocess module. Returns a newly allocated string */
+char *
+tor_join_win_cmdline(const char *argv[])
+{
+  smartlist_t *argv_list;
+  char *joined_argv;
+  int i;
+
+  /* Format each argument and put the result in a smartlist */
+  argv_list = smartlist_new();
+  for (i=0; argv[i] != NULL; i++) {
+    smartlist_add(argv_list, (void *)format_win_cmdline_argument(argv[i]));
+  }
+
+  /* Join the arguments with whitespace */
+  joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL);
+
+  /* Free the newly allocated arguments, and the smartlist */
+  SMARTLIST_FOREACH(argv_list, char *, arg,
+  {
+    tor_free(arg);
+  });
+  smartlist_free(argv_list);
+
+  return joined_argv;
+}
+
+#ifndef _WIN32
+/** Format <b>child_state</b> and <b>saved_errno</b> as a hex string placed in
+ * <b>hex_errno</b>.  Called between fork and _exit, so must be signal-handler
+ * safe.
+ *
+ * <b>hex_errno</b> must have at least HEX_ERRNO_SIZE+1 bytes available.
+ *
+ * The format of <b>hex_errno</b> is: "CHILD_STATE/ERRNO\n", left-padded
+ * with spaces. CHILD_STATE indicates where
+ * in the process of starting the child process did the failure occur (see
+ * CHILD_STATE_* macros for definition), and SAVED_ERRNO is the value of
+ * errno when the failure occurred.
+ *
+ * On success return the number of characters added to hex_errno, not counting
+ * the terminating NUL; return -1 on error.
+ */
+STATIC int
+format_helper_exit_status(unsigned char child_state, int saved_errno,
+                          char *hex_errno)
+{
+  unsigned int unsigned_errno;
+  int written, left;
+  char *cur;
+  size_t i;
+  int res = -1;
+
+  /* Fill hex_errno with spaces, and a trailing newline (memset may
+     not be signal handler safe, so we can't use it) */
+  for (i = 0; i < (HEX_ERRNO_SIZE - 1); i++)
+    hex_errno[i] = ' ';
+  hex_errno[HEX_ERRNO_SIZE - 1] = '\n';
+
+  /* Convert errno to be unsigned for hex conversion */
+  if (saved_errno < 0) {
+    // Avoid overflow on the cast to unsigned int when result is INT_MIN
+    // by adding 1 to the signed int negative value,
+    // then, after it has been negated and cast to unsigned,
+    // adding the original 1 back (the double-addition is intentional).
+    // Otherwise, the cast to signed could cause a temporary int
+    // to equal INT_MAX + 1, which is undefined.
+    unsigned_errno = ((unsigned int) -(saved_errno + 1)) + 1;
+  } else {
+    unsigned_errno = (unsigned int) saved_errno;
+  }
+
+  /*
+   * Count how many chars of space we have left, and keep a pointer into the
+   * current point in the buffer.
+   */
+  left = HEX_ERRNO_SIZE+1;
+  cur = hex_errno;
+
+  /* Emit child_state */
+  written = format_hex_number_sigsafe(child_state, cur, left);
+
+  if (written <= 0)
+    goto err;
+
+  /* Adjust left and cur */
+  left -= written;
+  cur += written;
+  if (left <= 0)
+    goto err;
+
+  /* Now the '/' */
+  *cur = '/';
+
+  /* Adjust left and cur */
+  ++cur;
+  --left;
+  if (left <= 0)
+    goto err;
+
+  /* Need minus? */
+  if (saved_errno < 0) {
+    *cur = '-';
+    ++cur;
+    --left;
+    if (left <= 0)
+      goto err;
+  }
+
+  /* Emit unsigned_errno */
+  written = format_hex_number_sigsafe(unsigned_errno, cur, left);
+
+  if (written <= 0)
+    goto err;
+
+  /* Adjust left and cur */
+  left -= written;
+  cur += written;
+
+  /* Check that we have enough space left for a newline and a NUL */
+  if (left <= 1)
+    goto err;
+
+  /* Emit the newline and NUL */
+  *cur++ = '\n';
+  *cur++ = '\0';
+
+  res = (int)(cur - hex_errno - 1);
+
+  goto done;
+
+ err:
+  /*
+   * In error exit, just write a '\0' in the first char so whatever called
+   * this at least won't fall off the end.
+   */
+  *hex_errno = '\0';
+
+ done:
+  return res;
+}
+#endif /* !defined(_WIN32) */
+
+/* Maximum number of file descriptors, if we cannot get it via sysconf() */
+#define DEFAULT_MAX_FD 256
+
+/** Terminate the process of <b>process_handle</b>, if that process has not
+ * already exited.
+ *
+ * Return 0 if we succeeded in terminating the process (or if the process
+ * already exited), and -1 if we tried to kill the process but failed.
+ *
+ * Based on code originally borrowed from Python's os.kill. */
+int
+tor_terminate_process(process_handle_t *process_handle)
+{
+#ifdef _WIN32
+  if (tor_get_exit_code(process_handle, 0, NULL) == PROCESS_EXIT_RUNNING) {
+    HANDLE handle = process_handle->pid.hProcess;
+
+    if (!TerminateProcess(handle, 0))
+      return -1;
+    else
+      return 0;
+  }
+#else /* !(defined(_WIN32)) */
+  if (process_handle->waitpid_cb) {
+    /* We haven't got a waitpid yet, so we can just kill off the process. */
+    return kill(process_handle->pid, SIGTERM);
+  }
+#endif /* defined(_WIN32) */
+
+  return 0; /* We didn't need to kill the process, so report success */
+}
+
+/** Return the Process ID of <b>process_handle</b>. */
+int
+tor_process_get_pid(process_handle_t *process_handle)
+{
+#ifdef _WIN32
+  return (int) process_handle->pid.dwProcessId;
+#else
+  return (int) process_handle->pid;
+#endif
+}
+
+#ifdef _WIN32
+HANDLE
+tor_process_get_stdout_pipe(process_handle_t *process_handle)
+{
+  return process_handle->stdout_pipe;
+}
+#else /* !(defined(_WIN32)) */
+/* DOCDOC tor_process_get_stdout_pipe */
+int
+tor_process_get_stdout_pipe(process_handle_t *process_handle)
+{
+  return process_handle->stdout_pipe;
+}
+#endif /* defined(_WIN32) */
+
+/* DOCDOC process_handle_new */
+static process_handle_t *
+process_handle_new(void)
+{
+  process_handle_t *out = tor_malloc_zero(sizeof(process_handle_t));
+
+#ifdef _WIN32
+  out->stdin_pipe = INVALID_HANDLE_VALUE;
+  out->stdout_pipe = INVALID_HANDLE_VALUE;
+  out->stderr_pipe = INVALID_HANDLE_VALUE;
+#else
+  out->stdin_pipe = -1;
+  out->stdout_pipe = -1;
+  out->stderr_pipe = -1;
+#endif /* defined(_WIN32) */
+
+  return out;
+}
+
+#ifndef _WIN32
+/** Invoked when a process that we've launched via tor_spawn_background() has
+ * been found to have terminated.
+ */
+static void
+process_handle_waitpid_cb(int status, void *arg)
+{
+  process_handle_t *process_handle = arg;
+
+  process_handle->waitpid_exit_status = status;
+  clear_waitpid_callback(process_handle->waitpid_cb);
+  if (process_handle->status == PROCESS_STATUS_RUNNING)
+    process_handle->status = PROCESS_STATUS_NOTRUNNING;
+  process_handle->waitpid_cb = 0;
+}
+#endif /* !defined(_WIN32) */
+
+/**
+ * @name child-process states
+ *
+ * Each of these values represents a possible state that a child process can
+ * be in.  They're used to determine what to say when telling the parent how
+ * far along we were before failure.
+ *
+ * @{
+ */
+#define CHILD_STATE_INIT 0
+#define CHILD_STATE_PIPE 1
+#define CHILD_STATE_MAXFD 2
+#define CHILD_STATE_FORK 3
+#define CHILD_STATE_DUPOUT 4
+#define CHILD_STATE_DUPERR 5
+#define CHILD_STATE_DUPIN 6
+#define CHILD_STATE_CLOSEFD 7
+#define CHILD_STATE_EXEC 8
+#define CHILD_STATE_FAILEXEC 9
+/** @} */
+/**
+ * Boolean.  If true, then Tor may call execve or CreateProcess via
+ * tor_spawn_background.
+ **/
+static int may_spawn_background_process = 1;
+/**
+ * Turn off may_spawn_background_process, so that all future calls to
+ * tor_spawn_background are guaranteed to fail.
+ **/
+void
+tor_disable_spawning_background_processes(void)
+{
+  may_spawn_background_process = 0;
+}
+/** Start a program in the background. If <b>filename</b> contains a '/', then
+ * it will be treated as an absolute or relative path.  Otherwise, on
+ * non-Windows systems, the system path will be searched for <b>filename</b>.
+ * On Windows, only the current directory will be searched. Here, to search the
+ * system path (as well as the application directory, current working
+ * directory, and system directories), set filename to NULL.
+ *
+ * The strings in <b>argv</b> will be passed as the command line arguments of
+ * the child program (following convention, argv[0] should normally be the
+ * filename of the executable, and this must be the case if <b>filename</b> is
+ * NULL). The last element of argv must be NULL. A handle to the child process
+ * will be returned in process_handle (which must be non-NULL). Read
+ * process_handle.status to find out if the process was successfully launched.
+ * For convenience, process_handle.status is returned by this function.
+ *
+ * Some parts of this code are based on the POSIX subprocess module from
+ * Python, and example code from
+ * http://msdn.microsoft.com/en-us/library/ms682499%28v=vs.85%29.aspx.
+ */
+int
+tor_spawn_background(const char *const filename, const char **argv,
+                     process_environment_t *env,
+                     process_handle_t **process_handle_out)
+{
+  if (BUG(may_spawn_background_process == 0)) {
+    /* We should never reach this point if we're forbidden to spawn
+     * processes. Instead we should have caught the attempt earlier. */
+    return PROCESS_STATUS_ERROR;
+  }
+
+#ifdef _WIN32
+  HANDLE stdout_pipe_read = NULL;
+  HANDLE stdout_pipe_write = NULL;
+  HANDLE stderr_pipe_read = NULL;
+  HANDLE stderr_pipe_write = NULL;
+  HANDLE stdin_pipe_read = NULL;
+  HANDLE stdin_pipe_write = NULL;
+  process_handle_t *process_handle;
+  int status;
+
+  STARTUPINFOA siStartInfo;
+  BOOL retval = FALSE;
+
+  SECURITY_ATTRIBUTES saAttr;
+  char *joined_argv;
+
+  saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
+  saAttr.bInheritHandle = TRUE;
+  /* TODO: should we set explicit security attributes? (#2046, comment 5) */
+  saAttr.lpSecurityDescriptor = NULL;
+
+  /* Assume failure to start process */
+  status = PROCESS_STATUS_ERROR;
+
+  /* Set up pipe for stdout */
+  if (!CreatePipe(&stdout_pipe_read, &stdout_pipe_write, &saAttr, 0)) {
+    log_warn(LD_GENERAL,
+      "Failed to create pipe for stdout communication with child process: %s",
+      format_win32_error(GetLastError()));
+    return status;
+  }
+  if (!SetHandleInformation(stdout_pipe_read, HANDLE_FLAG_INHERIT, 0)) {
+    log_warn(LD_GENERAL,
+      "Failed to configure pipe for stdout communication with child "
+      "process: %s", format_win32_error(GetLastError()));
+    return status;
+  }
+
+  /* Set up pipe for stderr */
+  if (!CreatePipe(&stderr_pipe_read, &stderr_pipe_write, &saAttr, 0)) {
+    log_warn(LD_GENERAL,
+      "Failed to create pipe for stderr communication with child process: %s",
+      format_win32_error(GetLastError()));
+    return status;
+  }
+  if (!SetHandleInformation(stderr_pipe_read, HANDLE_FLAG_INHERIT, 0)) {
+    log_warn(LD_GENERAL,
+      "Failed to configure pipe for stderr communication with child "
+      "process: %s", format_win32_error(GetLastError()));
+    return status;
+  }
+
+  /* Set up pipe for stdin */
+  if (!CreatePipe(&stdin_pipe_read, &stdin_pipe_write, &saAttr, 0)) {
+    log_warn(LD_GENERAL,
+      "Failed to create pipe for stdin communication with child process: %s",
+      format_win32_error(GetLastError()));
+    return status;
+  }
+  if (!SetHandleInformation(stdin_pipe_write, HANDLE_FLAG_INHERIT, 0)) {
+    log_warn(LD_GENERAL,
+      "Failed to configure pipe for stdin communication with child "
+      "process: %s", format_win32_error(GetLastError()));
+    return status;
+  }
+
+  /* Create the child process */
+
+  /* Windows expects argv to be a whitespace delimited string, so join argv up
+   */
+  joined_argv = tor_join_win_cmdline(argv);
+
+  process_handle = process_handle_new();
+  process_handle->status = status;
+
+  ZeroMemory(&(process_handle->pid), sizeof(PROCESS_INFORMATION));
+  ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
+  siStartInfo.cb = sizeof(STARTUPINFO);
+  siStartInfo.hStdError = stderr_pipe_write;
+  siStartInfo.hStdOutput = stdout_pipe_write;
+  siStartInfo.hStdInput = stdin_pipe_read;
+  siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+  /* Create the child process */
+
+  retval = CreateProcessA(filename,      // module name
+                 joined_argv,   // command line
+  /* TODO: should we set explicit security attributes? (#2046, comment 5) */
+                 NULL,          // process security attributes
+                 NULL,          // primary thread security attributes
+                 TRUE,          // handles are inherited
+  /*(TODO: set CREATE_NEW CONSOLE/PROCESS_GROUP to make GetExitCodeProcess()
+   * work?) */
+                 CREATE_NO_WINDOW,             // creation flags
+                 (env==NULL) ? NULL : env->windows_environment_block,
+                 NULL,          // use parent's current directory
+                 &siStartInfo,  // STARTUPINFO pointer
+                 &(process_handle->pid));  // receives PROCESS_INFORMATION
+
+  tor_free(joined_argv);
+
+  if (!retval) {
+    log_warn(LD_GENERAL,
+      "Failed to create child process %s: %s", filename?filename:argv[0],
+      format_win32_error(GetLastError()));
+    tor_free(process_handle);
+  } else  {
+    /* TODO: Close hProcess and hThread in process_handle->pid? */
+    process_handle->stdout_pipe = stdout_pipe_read;
+    process_handle->stderr_pipe = stderr_pipe_read;
+    process_handle->stdin_pipe = stdin_pipe_write;
+    status = process_handle->status = PROCESS_STATUS_RUNNING;
+  }
+
+  /* TODO: Close pipes on exit */
+  *process_handle_out = process_handle;
+  return status;
+#else /* !(defined(_WIN32)) */
+  pid_t pid;
+  int stdout_pipe[2];
+  int stderr_pipe[2];
+  int stdin_pipe[2];
+  int fd, retval;
+  process_handle_t *process_handle;
+  int status;
+
+  const char *error_message = SPAWN_ERROR_MESSAGE;
+  size_t error_message_length;
+
+  /* Represents where in the process of spawning the program is;
+     this is used for printing out the error message */
+  unsigned char child_state = CHILD_STATE_INIT;
+
+  char hex_errno[HEX_ERRNO_SIZE + 2]; /* + 1 should be sufficient actually */
+
+  static int max_fd = -1;
+
+  status = PROCESS_STATUS_ERROR;
+
+  /* We do the strlen here because strlen() is not signal handler safe,
+     and we are not allowed to use unsafe functions between fork and exec */
+  error_message_length = strlen(error_message);
+
+  // child_state = CHILD_STATE_PIPE;
+
+  /* Set up pipe for redirecting stdout, stderr, and stdin of child */
+  retval = pipe(stdout_pipe);
+  if (-1 == retval) {
+    log_warn(LD_GENERAL,
+      "Failed to set up pipe for stdout communication with child process: %s",
+       strerror(errno));
+    return status;
+  }
+
+  retval = pipe(stderr_pipe);
+  if (-1 == retval) {
+    log_warn(LD_GENERAL,
+      "Failed to set up pipe for stderr communication with child process: %s",
+      strerror(errno));
+
+    close(stdout_pipe[0]);
+    close(stdout_pipe[1]);
+
+    return status;
+  }
+
+  retval = pipe(stdin_pipe);
+  if (-1 == retval) {
+    log_warn(LD_GENERAL,
+      "Failed to set up pipe for stdin communication with child process: %s",
+       strerror(errno));
+
+    close(stdout_pipe[0]);
+    close(stdout_pipe[1]);
+    close(stderr_pipe[0]);
+    close(stderr_pipe[1]);
+
+    return status;
+  }
+
+  // child_state = CHILD_STATE_MAXFD;
+
+#ifdef _SC_OPEN_MAX
+  if (-1 == max_fd) {
+    max_fd = (int) sysconf(_SC_OPEN_MAX);
+    if (max_fd == -1) {
+      max_fd = DEFAULT_MAX_FD;
+      log_warn(LD_GENERAL,
+               "Cannot find maximum file descriptor, assuming %d", max_fd);
+    }
+  }
+#else /* !(defined(_SC_OPEN_MAX)) */
+  max_fd = DEFAULT_MAX_FD;
+#endif /* defined(_SC_OPEN_MAX) */
+
+  // child_state = CHILD_STATE_FORK;
+
+  pid = fork();
+  if (0 == pid) {
+    /* In child */
+
+#if defined(HAVE_SYS_PRCTL_H) && defined(__linux__)
+    /* Attempt to have the kernel issue a SIGTERM if the parent
+     * goes away. Certain attributes of the binary being execve()ed
+     * will clear this during the execve() call, but it's better
+     * than nothing.
+     */
+    prctl(PR_SET_PDEATHSIG, SIGTERM);
+#endif /* defined(HAVE_SYS_PRCTL_H) && defined(__linux__) */
+
+    child_state = CHILD_STATE_DUPOUT;
+
+    /* Link child stdout to the write end of the pipe */
+    retval = dup2(stdout_pipe[1], STDOUT_FILENO);
+    if (-1 == retval)
+        goto error;
+
+    child_state = CHILD_STATE_DUPERR;
+
+    /* Link child stderr to the write end of the pipe */
+    retval = dup2(stderr_pipe[1], STDERR_FILENO);
+    if (-1 == retval)
+        goto error;
+
+    child_state = CHILD_STATE_DUPIN;
+
+    /* Link child stdin to the read end of the pipe */
+    retval = dup2(stdin_pipe[0], STDIN_FILENO);
+    if (-1 == retval)
+      goto error;
+
+    // child_state = CHILD_STATE_CLOSEFD;
+
+    close(stderr_pipe[0]);
+    close(stderr_pipe[1]);
+    close(stdout_pipe[0]);
+    close(stdout_pipe[1]);
+    close(stdin_pipe[0]);
+    close(stdin_pipe[1]);
+
+    /* Close all other fds, including the read end of the pipe */
+    /* XXX: We should now be doing enough FD_CLOEXEC setting to make
+     * this needless. */
+    for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) {
+      close(fd);
+    }
+
+    // child_state = CHILD_STATE_EXEC;
+
+    /* Call the requested program. We need the cast because
+       execvp doesn't define argv as const, even though it
+       does not modify the arguments */
+    if (env)
+      execve(filename, (char *const *) argv, env->unixoid_environment_block);
+    else {
+      static char *new_env[] = { NULL };
+      execve(filename, (char *const *) argv, new_env);
+    }
+
+    /* If we got here, the exec or open(/dev/null) failed */
+
+    child_state = CHILD_STATE_FAILEXEC;
+
+  error:
+    {
+      /* XXX: are we leaking fds from the pipe? */
+      int n, err=0;
+      ssize_t nbytes;
+
+      n = format_helper_exit_status(child_state, errno, hex_errno);
+
+      if (n >= 0) {
+        /* Write the error message. GCC requires that we check the return
+           value, but there is nothing we can do if it fails */
+        /* TODO: Don't use STDOUT, use a pipe set up just for this purpose */
+        nbytes = write(STDOUT_FILENO, error_message, error_message_length);
+        err = (nbytes < 0);
+        nbytes = write(STDOUT_FILENO, hex_errno, n);
+        err += (nbytes < 0);
+      }
+
+      _exit(err?254:255); // exit ok: in child.
+    }
+
+    /* Never reached, but avoids compiler warning */
+    return status; // LCOV_EXCL_LINE
+  }
+
+  /* In parent */
+
+  if (-1 == pid) {
+    log_warn(LD_GENERAL, "Failed to fork child process: %s", strerror(errno));
+    close(stdin_pipe[0]);
+    close(stdin_pipe[1]);
+    close(stdout_pipe[0]);
+    close(stdout_pipe[1]);
+    close(stderr_pipe[0]);
+    close(stderr_pipe[1]);
+    return status;
+  }
+
+  process_handle = process_handle_new();
+  process_handle->status = status;
+  process_handle->pid = pid;
+
+  /* TODO: If the child process forked but failed to exec, waitpid it */
+
+  /* Return read end of the pipes to caller, and close write end */
+  process_handle->stdout_pipe = stdout_pipe[0];
+  retval = close(stdout_pipe[1]);
+
+  if (-1 == retval) {
+    log_warn(LD_GENERAL,
+            "Failed to close write end of stdout pipe in parent process: %s",
+            strerror(errno));
+  }
+
+  process_handle->waitpid_cb = set_waitpid_callback(pid,
+                                                    process_handle_waitpid_cb,
+                                                    process_handle);
+
+  process_handle->stderr_pipe = stderr_pipe[0];
+  retval = close(stderr_pipe[1]);
+
+  if (-1 == retval) {
+    log_warn(LD_GENERAL,
+            "Failed to close write end of stderr pipe in parent process: %s",
+            strerror(errno));
+  }
+
+  /* Return write end of the stdin pipe to caller, and close the read end */
+  process_handle->stdin_pipe = stdin_pipe[1];
+  retval = close(stdin_pipe[0]);
+
+  if (-1 == retval) {
+    log_warn(LD_GENERAL,
+            "Failed to close read end of stdin pipe in parent process: %s",
+            strerror(errno));
+  }
+
+  status = process_handle->status = PROCESS_STATUS_RUNNING;
+  /* Set stdin/stdout/stderr pipes to be non-blocking */
+  if (fcntl(process_handle->stdout_pipe, F_SETFL, O_NONBLOCK) < 0 ||
+      fcntl(process_handle->stderr_pipe, F_SETFL, O_NONBLOCK) < 0 ||
+      fcntl(process_handle->stdin_pipe, F_SETFL, O_NONBLOCK) < 0) {
+    log_warn(LD_GENERAL, "Failed to set stderror/stdout/stdin pipes "
+             "nonblocking in parent process: %s", strerror(errno));
+  }
+
+  *process_handle_out = process_handle;
+  return status;
+#endif /* defined(_WIN32) */
+}
+
+/** Destroy all resources allocated by the process handle in
+ *  <b>process_handle</b>.
+ *  If <b>also_terminate_process</b> is true, also terminate the
+ *  process of the process handle. */
+MOCK_IMPL(void,
+tor_process_handle_destroy,(process_handle_t *process_handle,
+                            int also_terminate_process))
+{
+  if (!process_handle)
+    return;
+
+  if (also_terminate_process) {
+    if (tor_terminate_process(process_handle) < 0) {
+      const char *errstr =
+#ifdef _WIN32
+        format_win32_error(GetLastError());
+#else
+        strerror(errno);
+#endif
+      log_notice(LD_GENERAL, "Failed to terminate process with "
+                 "PID '%d' ('%s').", tor_process_get_pid(process_handle),
+                 errstr);
+    } else {
+      log_info(LD_GENERAL, "Terminated process with PID '%d'.",
+               tor_process_get_pid(process_handle));
+    }
+  }
+
+  process_handle->status = PROCESS_STATUS_NOTRUNNING;
+
+#ifdef _WIN32
+  if (process_handle->stdout_pipe)
+    CloseHandle(process_handle->stdout_pipe);
+
+  if (process_handle->stderr_pipe)
+    CloseHandle(process_handle->stderr_pipe);
+
+  if (process_handle->stdin_pipe)
+    CloseHandle(process_handle->stdin_pipe);
+#else /* !(defined(_WIN32)) */
+  close(process_handle->stdout_pipe);
+  close(process_handle->stderr_pipe);
+  close(process_handle->stdin_pipe);
+
+  clear_waitpid_callback(process_handle->waitpid_cb);
+#endif /* defined(_WIN32) */
+
+  memset(process_handle, 0x0f, sizeof(process_handle_t));
+  tor_free(process_handle);
+}
+
+/** Get the exit code of a process specified by <b>process_handle</b> and store
+ * it in <b>exit_code</b>, if set to a non-NULL value.  If <b>block</b> is set
+ * to true, the call will block until the process has exited.  Otherwise if
+ * the process is still running, the function will return
+ * PROCESS_EXIT_RUNNING, and exit_code will be left unchanged. Returns
+ * PROCESS_EXIT_EXITED if the process did exit. If there is a failure,
+ * PROCESS_EXIT_ERROR will be returned and the contents of exit_code (if
+ * non-NULL) will be undefined. N.B. Under *nix operating systems, this will
+ * probably not work in Tor, because waitpid() is called in main.c to reap any
+ * terminated child processes.*/
+int
+tor_get_exit_code(process_handle_t *process_handle,
+                  int block, int *exit_code)
+{
+#ifdef _WIN32
+  DWORD retval;
+  BOOL success;
+
+  if (block) {
+    /* Wait for the process to exit */
+    retval = WaitForSingleObject(process_handle->pid.hProcess, INFINITE);
+    if (retval != WAIT_OBJECT_0) {
+      log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
+              (int)retval, format_win32_error(GetLastError()));
+      return PROCESS_EXIT_ERROR;
+    }
+  } else {
+    retval = WaitForSingleObject(process_handle->pid.hProcess, 0);
+    if (WAIT_TIMEOUT == retval) {
+      /* Process has not exited */
+      return PROCESS_EXIT_RUNNING;
+    } else if (retval != WAIT_OBJECT_0) {
+      log_warn(LD_GENERAL, "WaitForSingleObject() failed (%d): %s",
+               (int)retval, format_win32_error(GetLastError()));
+      return PROCESS_EXIT_ERROR;
+    }
+  }
+
+  if (exit_code != NULL) {
+    success = GetExitCodeProcess(process_handle->pid.hProcess,
+                                 (PDWORD)exit_code);
+    if (!success) {
+      log_warn(LD_GENERAL, "GetExitCodeProcess() failed: %s",
+               format_win32_error(GetLastError()));
+      return PROCESS_EXIT_ERROR;
+    }
+  }
+#else /* !(defined(_WIN32)) */
+  int stat_loc;
+  int retval;
+
+  if (process_handle->waitpid_cb) {
+    /* We haven't processed a SIGCHLD yet. */
+    retval = waitpid(process_handle->pid, &stat_loc, block?0:WNOHANG);
+    if (retval == process_handle->pid) {
+      clear_waitpid_callback(process_handle->waitpid_cb);
+      process_handle->waitpid_cb = NULL;
+      process_handle->waitpid_exit_status = stat_loc;
+    }
+  } else {
+    /* We already got a SIGCHLD for this process, and handled it. */
+    retval = process_handle->pid;
+    stat_loc = process_handle->waitpid_exit_status;
+  }
+
+  if (!block && 0 == retval) {
+    /* Process has not exited */
+    return PROCESS_EXIT_RUNNING;
+  } else if (retval != process_handle->pid) {
+    log_warn(LD_GENERAL, "waitpid() failed for PID %d: %s",
+             (int)process_handle->pid, strerror(errno));
+    return PROCESS_EXIT_ERROR;
+  }
+
+  if (!WIFEXITED(stat_loc)) {
+    log_warn(LD_GENERAL, "Process %d did not exit normally",
+             (int)process_handle->pid);
+    return PROCESS_EXIT_ERROR;
+  }
+
+  if (exit_code != NULL)
+    *exit_code = WEXITSTATUS(stat_loc);
+#endif /* defined(_WIN32) */
+
+  return PROCESS_EXIT_EXITED;
+}
+
+#ifdef _WIN32
+/** Read from a handle <b>h</b> into <b>buf</b>, up to <b>count</b> bytes.  If
+ * <b>hProcess</b> is NULL, the function will return immediately if there is
+ * nothing more to read. Otherwise <b>hProcess</b> should be set to the handle
+ * to the process owning the <b>h</b>. In this case, the function will exit
+ * only once the process has exited, or <b>count</b> bytes are read. Returns
+ * the number of bytes read, or -1 on error. */
+ssize_t
+tor_read_all_handle(HANDLE h, char *buf, size_t count,
+                    const process_handle_t *process)
+{
+  size_t numread = 0;
+  BOOL retval;
+  DWORD byte_count;
+  BOOL process_exited = FALSE;
+
+  if (count > SIZE_T_CEILING || count > SSIZE_MAX)
+    return -1;
+
+  while (numread < count) {
+    /* Check if there is anything to read */
+    retval = PeekNamedPipe(h, NULL, 0, NULL, &byte_count, NULL);
+    if (!retval) {
+      log_warn(LD_GENERAL,
+        "Failed to peek from handle: %s",
+        format_win32_error(GetLastError()));
+      return -1;
+    } else if (0 == byte_count) {
+      /* Nothing available: process exited or it is busy */
+
+      /* Exit if we don't know whether the process is running */
+      if (NULL == process)
+        break;
+
+      /* The process exited and there's nothing left to read from it */
+      if (process_exited)
+        break;
+
+      /* If process is not running, check for output one more time in case
+         it wrote something after the peek was performed. Otherwise keep on
+         waiting for output */
+      tor_assert(process != NULL);
+      byte_count = WaitForSingleObject(process->pid.hProcess, 0);
+      if (WAIT_TIMEOUT != byte_count)
+        process_exited = TRUE;
+
+      continue;
+    }
+
+    /* There is data to read; read it */
+    retval = ReadFile(h, buf+numread, count-numread, &byte_count, NULL);
+    tor_assert(byte_count + numread <= count);
+    if (!retval) {
+      log_warn(LD_GENERAL, "Failed to read from handle: %s",
+        format_win32_error(GetLastError()));
+      return -1;
+    } else if (0 == byte_count) {
+      /* End of file */
+      break;
+    }
+    numread += byte_count;
+  }
+  return (ssize_t)numread;
+}
+#else /* !(defined(_WIN32)) */
+/** Read from a handle <b>fd</b> into <b>buf</b>, up to <b>count</b> bytes.  If
+ * <b>process</b> is NULL, the function will return immediately if there is
+ * nothing more to read. Otherwise data will be read until end of file, or
+ * <b>count</b> bytes are read.  Returns the number of bytes read, or -1 on
+ * error. Sets <b>eof</b> to true if <b>eof</b> is not NULL and the end of the
+ * file has been reached. */
+ssize_t
+tor_read_all_handle(int fd, char *buf, size_t count,
+                    const process_handle_t *process,
+                    int *eof)
+{
+  size_t numread = 0;
+  ssize_t result;
+
+  if (eof)
+    *eof = 0;
+
+  if (count > SIZE_T_CEILING || count > SSIZE_MAX)
+    return -1;
+
+  while (numread < count) {
+    result = read(fd, buf+numread, count-numread);
+
+    if (result == 0) {
+      log_debug(LD_GENERAL, "read() reached end of file");
+      if (eof)
+        *eof = 1;
+      break;
+    } else if (result < 0 && errno == EAGAIN) {
+      if (process)
+        continue;
+      else
+        break;
+    } else if (result < 0) {
+      log_warn(LD_GENERAL, "read() failed: %s", strerror(errno));
+      return -1;
+    }
+
+    numread += result;
+  }
+
+  log_debug(LD_GENERAL, "read() read %d bytes from handle", (int)numread);
+  return (ssize_t)numread;
+}
+#endif /* defined(_WIN32) */
+
+/** Read from stdout of a process until the process exits. */
+ssize_t
+tor_read_all_from_process_stdout(const process_handle_t *process_handle,
+                                 char *buf, size_t count)
+{
+#ifdef _WIN32
+  return tor_read_all_handle(process_handle->stdout_pipe, buf, count,
+                             process_handle);
+#else
+  return tor_read_all_handle(process_handle->stdout_pipe, buf, count,
+                             process_handle, NULL);
+#endif /* defined(_WIN32) */
+}
+
+/** Read from stdout of a process until the process exits. */
+ssize_t
+tor_read_all_from_process_stderr(const process_handle_t *process_handle,
+                                 char *buf, size_t count)
+{
+#ifdef _WIN32
+  return tor_read_all_handle(process_handle->stderr_pipe, buf, count,
+                             process_handle);
+#else
+  return tor_read_all_handle(process_handle->stderr_pipe, buf, count,
+                             process_handle, NULL);
+#endif /* defined(_WIN32) */
+}
+
+/** Return a string corresponding to <b>stream_status</b>. */
+const char *
+stream_status_to_string(enum stream_status stream_status)
+{
+  switch (stream_status) {
+    case IO_STREAM_OKAY:
+      return "okay";
+    case IO_STREAM_EAGAIN:
+      return "temporarily unavailable";
+    case IO_STREAM_TERM:
+      return "terminated";
+    case IO_STREAM_CLOSED:
+      return "closed";
+    default:
+      tor_fragile_assert();
+      return "unknown";
+  }
+}
+
+/** Split buf into lines, and add to smartlist. The buffer <b>buf</b> will be
+ * modified. The resulting smartlist will consist of pointers to buf, so there
+ * is no need to free the contents of sl. <b>buf</b> must be a NUL-terminated
+ * string. <b>len</b> should be set to the length of the buffer excluding the
+ * NUL. Non-printable characters (including NUL) will be replaced with "." */
+int
+tor_split_lines(smartlist_t *sl, char *buf, int len)
+{
+  /* Index in buf of the start of the current line */
+  int start = 0;
+  /* Index in buf of the current character being processed */
+  int cur = 0;
+  /* Are we currently in a line */
+  char in_line = 0;
+
+  /* Loop over string */
+  while (cur < len) {
+    /* Loop until end of line or end of string */
+    for (; cur < len; cur++) {
+      if (in_line) {
+        if ('\r' == buf[cur] || '\n' == buf[cur]) {
+          /* End of line */
+          buf[cur] = '\0';
+          /* Point cur to the next line */
+          cur++;
+          /* Line starts at start and ends with a nul */
+          break;
+        } else {
+          if (!TOR_ISPRINT(buf[cur]))
+            buf[cur] = '.';
+        }
+      } else {
+        if ('\r' == buf[cur] || '\n' == buf[cur]) {
+          /* Skip leading vertical space */
+          ;
+        } else {
+          in_line = 1;
+          start = cur;
+          if (!TOR_ISPRINT(buf[cur]))
+            buf[cur] = '.';
+        }
+      }
+    }
+    /* We are at the end of the line or end of string. If in_line is true there
+     * is a line which starts at buf+start and ends at a NUL. cur points to
+     * the character after the NUL. */
+    if (in_line)
+      smartlist_add(sl, (void *)(buf+start));
+    in_line = 0;
+  }
+  return smartlist_len(sl);
+}
+
+#ifdef _WIN32
+
+/** Return a smartlist containing lines outputted from
+ *  <b>handle</b>. Return NULL on error, and set
+ *  <b>stream_status_out</b> appropriately. */
+MOCK_IMPL(smartlist_t *,
+tor_get_lines_from_handle, (HANDLE *handle,
+                            enum stream_status *stream_status_out))
+{
+  int pos;
+  char stdout_buf[600] = {0};
+  smartlist_t *lines = NULL;
+
+  tor_assert(stream_status_out);
+
+  *stream_status_out = IO_STREAM_TERM;
+
+  pos = tor_read_all_handle(handle, stdout_buf, sizeof(stdout_buf) - 1, NULL);
+  if (pos < 0) {
+    *stream_status_out = IO_STREAM_TERM;
+    return NULL;
+  }
+  if (pos == 0) {
+    *stream_status_out = IO_STREAM_EAGAIN;
+    return NULL;
+  }
+
+  /* End with a null even if there isn't a \r\n at the end */
+  /* TODO: What if this is a partial line? */
+  stdout_buf[pos] = '\0';
+
+  /* Split up the buffer */
+  lines = smartlist_new();
+  tor_split_lines(lines, stdout_buf, pos);
+
+  /* Currently 'lines' is populated with strings residing on the
+     stack. Replace them with their exact copies on the heap: */
+  SMARTLIST_FOREACH(lines, char *, line,
+                    SMARTLIST_REPLACE_CURRENT(lines, line, tor_strdup(line)));
+
+  *stream_status_out = IO_STREAM_OKAY;
+
+  return lines;
+}
+
+#else /* !(defined(_WIN32)) */
+
+/** Return a smartlist containing lines outputted from
+ *  <b>fd</b>. Return NULL on error, and set
+ *  <b>stream_status_out</b> appropriately. */
+MOCK_IMPL(smartlist_t *,
+tor_get_lines_from_handle, (int fd, enum stream_status *stream_status_out))
+{
+  enum stream_status stream_status;
+  char stdout_buf[400];
+  smartlist_t *lines = NULL;
+
+  while (1) {
+    memset(stdout_buf, 0, sizeof(stdout_buf));
+
+    stream_status = get_string_from_pipe(fd,
+                                         stdout_buf, sizeof(stdout_buf) - 1);
+    if (stream_status != IO_STREAM_OKAY)
+      goto done;
+
+    if (!lines) lines = smartlist_new();
+    smartlist_split_string(lines, stdout_buf, "\n", 0, 0);
+  }
+
+ done:
+  *stream_status_out = stream_status;
+  return lines;
+}
+
+#endif /* defined(_WIN32) */
+
+/** Reads from <b>fd</b> and stores input in <b>buf_out</b> making
+ *  sure it's below <b>count</b> bytes.
+ *  If the string has a trailing newline, we strip it off.
+ *
+ * This function is specifically created to handle input from managed
+ * proxies, according to the pluggable transports spec. Make sure it
+ * fits your needs before using it.
+ *
+ * Returns:
+ * IO_STREAM_CLOSED: If the stream is closed.
+ * IO_STREAM_EAGAIN: If there is nothing to read and we should check back
+ *  later.
+ * IO_STREAM_TERM: If something is wrong with the stream.
+ * IO_STREAM_OKAY: If everything went okay and we got a string
+ *  in <b>buf_out</b>. */
+enum stream_status
+get_string_from_pipe(int fd, char *buf_out, size_t count)
+{
+  ssize_t ret;
+
+  tor_assert(count <= INT_MAX);
+
+  ret = read(fd, buf_out, count);
+
+  if (ret == 0)
+    return IO_STREAM_CLOSED;
+  else if (ret < 0 && errno == EAGAIN)
+    return IO_STREAM_EAGAIN;
+  else if (ret < 0)
+    return IO_STREAM_TERM;
+
+  if (buf_out[ret - 1] == '\n') {
+    /* Remove the trailing newline */
+    buf_out[ret - 1] = '\0';
+  } else
+    buf_out[ret] = '\0';
+
+  return IO_STREAM_OKAY;
+}
diff --git a/src/lib/process/subprocess.h b/src/lib/process/subprocess.h
new file mode 100644
index 000000000..a319b3505
--- /dev/null
+++ b/src/lib/process/subprocess.h
@@ -0,0 +1,129 @@
+/* Copyright (c) 2003-2004, Roger Dingledine
+ * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
+ * Copyright (c) 2007-2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#ifndef TOR_SUBPROCESS_H
+#define TOR_SUBPROCESS_H
+
+#include "lib/cc/torint.h"
+#include "lib/testsupport/testsupport.h"
+#include <stddef.h>
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+struct smartlist_t;
+
+void tor_disable_spawning_background_processes(void);
+
+typedef struct process_handle_t process_handle_t;
+struct process_environment_t;
+int tor_spawn_background(const char *const filename, const char **argv,
+                         struct process_environment_t *env,
+                         process_handle_t **process_handle_out);
+
+#define SPAWN_ERROR_MESSAGE "ERR: Failed to spawn background process - code "
+
+/** Status of an I/O stream. */
+enum stream_status {
+  IO_STREAM_OKAY,
+  IO_STREAM_EAGAIN,
+  IO_STREAM_TERM,
+  IO_STREAM_CLOSED
+};
+
+const char *stream_status_to_string(enum stream_status stream_status);
+
+enum stream_status get_string_from_pipe(int fd, char *buf, size_t count);
+
+/* Values of process_handle_t.status. */
+#define PROCESS_STATUS_NOTRUNNING 0
+#define PROCESS_STATUS_RUNNING 1
+#define PROCESS_STATUS_ERROR -1
+
+#ifdef SUBPROCESS_PRIVATE
+struct waitpid_callback_t;
+
+/** Structure to represent the state of a process with which Tor is
+ * communicating. The contents of this structure are private to util.c */
+struct process_handle_t {
+  /** One of the PROCESS_STATUS_* values */
+  int status;
+#ifdef _WIN32
+  HANDLE stdin_pipe;
+  HANDLE stdout_pipe;
+  HANDLE stderr_pipe;
+  PROCESS_INFORMATION pid;
+#else /* !(defined(_WIN32)) */
+  int stdin_pipe;
+  int stdout_pipe;
+  int stderr_pipe;
+  pid_t pid;
+  /** If the process has not given us a SIGCHLD yet, this has the
+   * waitpid_callback_t that gets invoked once it has. Otherwise this
+   * contains NULL. */
+  struct waitpid_callback_t *waitpid_cb;
+  /** The exit status reported by waitpid. */
+  int waitpid_exit_status;
+#endif /* defined(_WIN32) */
+};
+#endif /* defined(SUBPROCESS_PRIVATE) */
+
+/* Return values of tor_get_exit_code() */
+#define PROCESS_EXIT_RUNNING 1
+#define PROCESS_EXIT_EXITED 0
+#define PROCESS_EXIT_ERROR -1
+int tor_get_exit_code(process_handle_t *process_handle,
+                      int block, int *exit_code);
+int tor_split_lines(struct smartlist_t *sl, char *buf, int len);
+#ifdef _WIN32
+ssize_t tor_read_all_handle(HANDLE h, char *buf, size_t count,
+                            const process_handle_t *process);
+#else
+ssize_t tor_read_all_handle(int fd, char *buf, size_t count,
+                            const process_handle_t *process,
+                            int *eof);
+#endif /* defined(_WIN32) */
+ssize_t tor_read_all_from_process_stdout(
+    const process_handle_t *process_handle, char *buf, size_t count);
+ssize_t tor_read_all_from_process_stderr(
+    const process_handle_t *process_handle, char *buf, size_t count);
+char *tor_join_win_cmdline(const char *argv[]);
+
+int tor_process_get_pid(process_handle_t *process_handle);
+#ifdef _WIN32
+HANDLE tor_process_get_stdout_pipe(process_handle_t *process_handle);
+#else
+int tor_process_get_stdout_pipe(process_handle_t *process_handle);
+#endif
+
+#ifdef _WIN32
+MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(HANDLE *handle,
+                           enum stream_status *stream_status));
+#else
+MOCK_DECL(struct smartlist_t *, tor_get_lines_from_handle,(int fd,
+                                       enum stream_status *stream_status));
+#endif /* defined(_WIN32) */
+
+int tor_terminate_process(process_handle_t *process_handle);
+
+MOCK_DECL(void, tor_process_handle_destroy,(process_handle_t *process_handle,
+                                            int also_terminate_process));
+
+#ifdef SUBPROCESS_PRIVATE
+/* Prototypes for private functions only used by util.c (and unit tests) */
+
+#ifndef _WIN32
+STATIC int format_helper_exit_status(unsigned char child_state,
+                              int saved_errno, char *hex_errno);
+
+/* Space for hex values of child state, a slash, saved_errno (with
+   leading minus) and newline (no null) */
+#define HEX_ERRNO_SIZE (sizeof(char) * 2 + 1 + \
+                        1 + sizeof(int) * 2 + 1)
+#endif /* !defined(_WIN32) */
+
+#endif /* defined(SUBPROCESS_PRIVATE) */
+
+#endif
diff --git a/src/common/util_process.c b/src/lib/process/waitpid.c
similarity index 96%
rename from src/common/util_process.c
rename to src/lib/process/waitpid.c
index 321258b69..66c77b05f 100644
--- a/src/common/util_process.c
+++ b/src/lib/process/waitpid.c
@@ -12,18 +12,19 @@
 
 #include "orconfig.h"
 
-#ifdef HAVE_SYS_TYPES_H
-#include <sys/types.h>
-#endif
+#ifndef _WIN32
+
+#include "lib/process/waitpid.h"
+#include "lib/log/torlog.h"
+#include "lib/log/util_bug.h"
+#include "lib/malloc/util_malloc.h"
+#include "ht.h"
+
 #ifdef HAVE_SYS_WAIT_H
 #include <sys/wait.h>
 #endif
 
-#include "common/compat.h"
-#include "common/util.h"
-#include "lib/log/torlog.h"
-#include "common/util_process.h"
-#include "ht.h"
+#include <string.h>
 
 /* ================================================== */
 /* Convenience structures for handlers for waitpid().
@@ -32,8 +33,6 @@
  * monitoring a non-child process.
  */
 
-#ifndef _WIN32
-
 /** Mapping from a PID to a userfn/userdata pair. */
 struct waitpid_callback_t {
   HT_ENTRY(waitpid_callback_t) node;
@@ -155,4 +154,3 @@ notify_pending_waitpid_callbacks(void)
 }
 
 #endif /* !defined(_WIN32) */
-
diff --git a/src/common/util_process.h b/src/lib/process/waitpid.h
similarity index 79%
rename from src/common/util_process.h
rename to src/lib/process/waitpid.h
index f63788194..85905da6b 100644
--- a/src/common/util_process.h
+++ b/src/lib/process/waitpid.h
@@ -2,14 +2,18 @@
 /* See LICENSE for licensing information */
 
 /**
- * \file util_process.h
- * \brief Headers for util_process.c
+ * \file waitpid.h
+ * \brief Headers for waitpid.c
  **/
 
-#ifndef TOR_UTIL_PROCESS_H
-#define TOR_UTIL_PROCESS_H
+#ifndef TOR_WAITPID_H
+#define TOR_WAITPID_H
 
 #ifndef _WIN32
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
 /** A callback structure waiting for us to get a SIGCHLD informing us that a
  * PID has been closed. Created by set_waitpid_callback. Cancelled or cleaned-
  * up from clear_waitpid_callback().  Do not access outside of the main thread;
@@ -22,5 +26,4 @@ void clear_waitpid_callback(waitpid_callback_t *ent);
 void notify_pending_waitpid_callbacks(void);
 #endif /* !defined(_WIN32) */
 
-#endif /* !defined(TOR_UTIL_PROCESS_H) */
-
+#endif /* !defined(TOR_WAITPID_H) */
diff --git a/src/or/config.c b/src/or/config.c
index bdc963a86..fb0ea5d59 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -111,6 +111,12 @@
 #include <shlobj.h>
 #endif
 
+#include "lib/process/daemon.h"
+#include "lib/process/pidfile.h"
+#include "lib/process/restrict.h"
+#include "lib/process/setuid.h"
+#include "lib/process/subprocess.h"
+
 #include "lib/fs/conffile.h"
 #include "common/procmon.h"
 
diff --git a/src/or/main.c b/src/or/main.c
index f06905b7b..50e51915b 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -108,7 +108,7 @@
 #include "or/status.h"
 #include "or/tor_api.h"
 #include "or/tor_api_internal.h"
-#include "common/util_process.h"
+#include "lib/process/waitpid.h"
 #include "or/ext_orport.h"
 #include "lib/memarea/memarea.h"
 #include "lib/sandbox/sandbox.h"
diff --git a/src/or/transports.c b/src/or/transports.c
index b88c42cbb..6cc7f844a 100644
--- a/src/or/transports.c
+++ b/src/or/transports.c
@@ -102,6 +102,9 @@
 #include "or/ext_orport.h"
 #include "or/control.h"
 
+#include "lib/process/env.h"
+#include "lib/process/subprocess.h"
+
 static process_environment_t *
 create_managed_proxy_environment(const managed_proxy_t *mp);
 
@@ -1696,4 +1699,3 @@ pt_free_all(void)
     managed_proxy_list=NULL;
   }
 }
-
diff --git a/src/or/transports.h b/src/or/transports.h
index 051fcdbc4..0bd96e095 100644
--- a/src/or/transports.h
+++ b/src/or/transports.h
@@ -78,6 +78,8 @@ enum pt_proto_state {
   PT_PROTO_FAILED_LAUNCH /* failed while launching */
 };
 
+struct process_handle_t;
+
 /** Structure containing information of a managed proxy. */
 typedef struct {
   enum pt_proto_state conf_state; /* the current configuration state */
@@ -90,7 +92,7 @@ typedef struct {
   int is_server; /* is it a server proxy? */
 
   /* A pointer to the process handle of this managed proxy. */
-  process_handle_t *process_handle;
+  struct process_handle_t *process_handle;
 
   int pid; /* The Process ID this managed proxy is using. */
 
@@ -140,4 +142,3 @@ STATIC void free_execve_args(char **arg);
 #endif /* defined(PT_PRIVATE) */
 
 #endif /* !defined(TOR_TRANSPORTS_H) */
-
diff --git a/src/test/test_logging.c b/src/test/test_logging.c
index 06744ebf2..d97941146 100644
--- a/src/test/test_logging.c
+++ b/src/test/test_logging.c
@@ -9,6 +9,7 @@
 #include "lib/err/torerr.h"
 #include "lib/log/torlog.h"
 #include "test/test.h"
+#include "lib/process/subprocess.h"
 
 static void
 dummy_cb_fn(int severity, uint32_t domain, const char *msg)
diff --git a/src/test/test_pt.c b/src/test/test_pt.c
index d6ab00bb6..c2c4e1989 100644
--- a/src/test/test_pt.c
+++ b/src/test/test_pt.c
@@ -8,6 +8,7 @@
 #define UTIL_PRIVATE
 #define STATEFILE_PRIVATE
 #define CONTROL_PRIVATE
+#define SUBPROCESS_PRIVATE
 #include "or/or.h"
 #include "or/config.h"
 #include "or/confparse.h"
@@ -17,6 +18,7 @@
 #include "common/util.h"
 #include "or/statefile.h"
 #include "test/test.h"
+#include "lib/process/subprocess.h"
 
 static void
 reset_mp(managed_proxy_t *mp)
@@ -544,4 +546,3 @@ struct testcase_t pt_tests[] = {
     NULL, NULL },
   END_OF_TESTCASES
 };
-
diff --git a/src/test/test_switch_id.c b/src/test/test_switch_id.c
index 149dda8f7..11fe53b7c 100644
--- a/src/test/test_switch_id.c
+++ b/src/test/test_switch_id.c
@@ -2,6 +2,7 @@
 /* See LICENSE for licensing information */
 
 #include "or/or.h"
+#include "lib/process/setuid.h"
 
 #ifdef HAVE_SYS_CAPABILITY_H
 #include <sys/capability.h>
@@ -189,4 +190,3 @@ main(int argc, char **argv)
   return (okay ? 0 : 1);
 #endif /* defined(_WIN32) */
 }
-
diff --git a/src/test/test_util.c b/src/test/test_util.c
index f1c2812f1..0c7e794b4 100644
--- a/src/test/test_util.c
+++ b/src/test/test_util.c
@@ -10,6 +10,7 @@
 #define UTIL_PRIVATE
 #define UTIL_MALLOC_PRIVATE
 #define SOCKET_PRIVATE
+#define SUBPROCESS_PRIVATE
 #include "or/or.h"
 #include "common/buffers.h"
 #include "or/config.h"
@@ -17,10 +18,13 @@
 #include "lib/crypt_ops/crypto_rand.h"
 #include "test/test.h"
 #include "lib/memarea/memarea.h"
-#include "common/util_process.h"
+#include "lib/process/waitpid.h"
 #include "test/log_test_helpers.h"
 #include "lib/compress/compress_zstd.h"
 #include "lib/fdio/fdio.h"
+#include "lib/process/env.h"
+#include "lib/process/pidfile.h"
+#include "lib/process/subprocess.h"
 
 #ifdef HAVE_PWD_H
 #include <pwd.h>
diff --git a/src/test/test_util_process.c b/src/test/test_util_process.c
index a09d12ac6..9dce520d0 100644
--- a/src/test/test_util_process.c
+++ b/src/test/test_util_process.c
@@ -7,7 +7,7 @@
 
 #include "test/test.h"
 
-#include "common/util_process.h"
+#include "lib/process/waitpid.h"
 
 #include "test/log_test_helpers.h"
 
diff --git a/src/test/test_util_slow.c b/src/test/test_util_slow.c
index 9fbcd9d2b..aeb0d422c 100644
--- a/src/test/test_util_slow.c
+++ b/src/test/test_util_slow.c
@@ -5,10 +5,12 @@
 
 #include "orconfig.h"
 #define UTIL_PRIVATE
+#define SUBPROCESS_PRIVATE
 #include "common/util.h"
-#include "common/util_process.h"
+#include "lib/process/waitpid.h"
 #include "lib/crypt_ops/crypto.h"
 #include "lib/log/torlog.h"
+#include "lib/process/subprocess.h"
 #include "test/test.h"
 
 #ifndef BUILDDIR
@@ -388,4 +390,3 @@ struct testcase_t slow_util_tests[] = {
   UTIL_TEST(spawn_background_waitpid_notify, 0),
   END_OF_TESTCASES
 };
-



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits