diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-06-28 10:53:34 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-06-28 11:18:13 -0400 |
commit | 315e6b59ddb91c19c5102625c6cf0f22b2d6f894 (patch) | |
tree | 6b8b4a72d3ebd1292307de21b9acf8c4c3810d35 /src/common | |
parent | ec4eee63561c3f6a8bb0c466f17b92e99f832c5d (diff) | |
download | tor-315e6b59ddb91c19c5102625c6cf0f22b2d6f894.tar.gz tor-315e6b59ddb91c19c5102625c6cf0f22b2d6f894.zip |
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.
Diffstat (limited to 'src/common')
-rw-r--r-- | src/common/compat.c | 502 | ||||
-rw-r--r-- | src/common/compat.h | 18 | ||||
-rw-r--r-- | src/common/include.am | 2 | ||||
-rw-r--r-- | src/common/util.c | 1539 | ||||
-rw-r--r-- | src/common/util.h | 143 | ||||
-rw-r--r-- | src/common/util_process.c | 158 | ||||
-rw-r--r-- | src/common/util_process.h | 26 |
7 files changed, 1 insertions, 2387 deletions
diff --git a/src/common/compat.c b/src/common/compat.c index 48e706456e..d03c4a501a 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 574dc57201..018caab240 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 ee35b9d56a..2d7297665c 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 f641c1081c..4a26998c19 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 549bbf9aaa..1967d23e41 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/common/util_process.c b/src/common/util_process.c deleted file mode 100644 index 321258b694..0000000000 --- a/src/common/util_process.c +++ /dev/null @@ -1,158 +0,0 @@ -/* 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 util_process.c - * \brief utility functions for launching processes and checking their - * status. These functions are kept separately from procmon so that they - * won't require linking against libevent. - **/ - -#include "orconfig.h" - -#ifdef HAVE_SYS_TYPES_H -#include <sys/types.h> -#endif -#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" - -/* ================================================== */ -/* Convenience structures for handlers for waitpid(). - * - * The tor_process_monitor*() code above doesn't use them, since it is for - * 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; - pid_t pid; - - void (*userfn)(int, void *userdata); - void *userdata; - - unsigned running; -}; - -static inline unsigned int -process_map_entry_hash_(const waitpid_callback_t *ent) -{ - return (unsigned) ent->pid; -} - -static inline unsigned int -process_map_entries_eq_(const waitpid_callback_t *a, - const waitpid_callback_t *b) -{ - return a->pid == b->pid; -} - -static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER(); - -HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_, - process_map_entries_eq_) -HT_GENERATE2(process_map, waitpid_callback_t, node, process_map_entry_hash_, - process_map_entries_eq_, 0.6, tor_reallocarray_, tor_free_) - -/** - * Begin monitoring the child pid <b>pid</b> to see if we get a SIGCHLD for - * it. If we eventually do, call <b>fn</b>, passing it the exit status (as - * yielded by waitpid) and the pointer <b>arg</b>. - * - * To cancel this, or clean up after it has triggered, call - * clear_waitpid_callback(). - */ -waitpid_callback_t * -set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg) -{ - waitpid_callback_t *old_ent; - waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t)); - ent->pid = pid; - ent->userfn = fn; - ent->userdata = arg; - ent->running = 1; - - old_ent = HT_REPLACE(process_map, &process_map, ent); - if (old_ent) { - log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be " - "impossible.", (unsigned) pid); - old_ent->running = 0; - } - - return ent; -} - -/** - * Cancel a waitpid_callback_t, or clean up after one has triggered. Releases - * all storage held by <b>ent</b>. - */ -void -clear_waitpid_callback(waitpid_callback_t *ent) -{ - waitpid_callback_t *old_ent; - if (ent == NULL) - return; - - if (ent->running) { - old_ent = HT_REMOVE(process_map, &process_map, ent); - if (old_ent != ent) { - log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.", - (unsigned) ent->pid); - return; - } - } - - tor_free(ent); -} - -/** Helper: find the callack for <b>pid</b>; if there is one, run it, - * reporting the exit status as <b>status</b>. */ -static void -notify_waitpid_callback_by_pid(pid_t pid, int status) -{ - waitpid_callback_t search, *ent; - - search.pid = pid; - ent = HT_REMOVE(process_map, &process_map, &search); - if (!ent || !ent->running) { - log_info(LD_GENERAL, "Child process %u has exited; no callback was " - "registered", (unsigned)pid); - return; - } - - log_info(LD_GENERAL, "Child process %u has exited; running callback.", - (unsigned)pid); - - ent->running = 0; - ent->userfn(status, ent->userdata); -} - -/** Use waitpid() to wait for all children that have exited, and invoke any - * callbacks registered for them. */ -void -notify_pending_waitpid_callbacks(void) -{ - /* I was going to call this function reap_zombie_children(), but - * that makes it sound way more exciting than it really is. */ - pid_t child; - int status = 0; - - while ((child = waitpid(-1, &status, WNOHANG)) > 0) { - notify_waitpid_callback_by_pid(child, status); - status = 0; /* should be needless */ - } -} - -#endif /* !defined(_WIN32) */ - diff --git a/src/common/util_process.h b/src/common/util_process.h deleted file mode 100644 index f637881940..0000000000 --- a/src/common/util_process.h +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright (c) 2011-2018, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file util_process.h - * \brief Headers for util_process.c - **/ - -#ifndef TOR_UTIL_PROCESS_H -#define TOR_UTIL_PROCESS_H - -#ifndef _WIN32 -/** 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; - * do not access from inside a signal handler. */ -typedef struct waitpid_callback_t waitpid_callback_t; - -waitpid_callback_t *set_waitpid_callback(pid_t pid, - void (*fn)(int, void *), void *arg); -void clear_waitpid_callback(waitpid_callback_t *ent); -void notify_pending_waitpid_callbacks(void); -#endif /* !defined(_WIN32) */ - -#endif /* !defined(TOR_UTIL_PROCESS_H) */ - |