diff --git a/src/common/include.am b/src/common/include.am index 7b2465cd84..c9dcedd52d 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -57,9 +57,9 @@ LIBOR_A_SOURCES = \ src/common/log.c \ src/common/memarea.c \ src/common/mempool.c \ - src/common/procmon.c \ src/common/util.c \ src/common/util_codedigest.c \ + src/common/util_process.c \ src/common/sandbox.c \ src/ext/csiphash.c \ $(libor_extra_source) @@ -72,7 +72,9 @@ LIBOR_CRYPTO_A_SOURCES = \ src/common/tortls.c \ $(libcrypto_extra_source) -LIBOR_EVENT_A_SOURCES = src/common/compat_libevent.c +LIBOR_EVENT_A_SOURCES = \ + src/common/compat_libevent.c \ + src/common/procmon.c src_common_libor_a_SOURCES = $(LIBOR_A_SOURCES) src_common_libor_crypto_a_SOURCES = $(LIBOR_CRYPTO_A_SOURCES) @@ -111,7 +113,8 @@ COMMONHEADERS = \ src/common/torint.h \ src/common/torlog.h \ src/common/tortls.h \ - src/common/util.h + src/common/util.h \ + src/common/util_process.h noinst_HEADERS+= $(COMMONHEADERS) diff --git a/src/common/procmon.c b/src/common/procmon.c index 0a49689e3a..7c9b7c3c88 100644 --- a/src/common/procmon.c +++ b/src/common/procmon.c @@ -162,6 +162,7 @@ tor_validate_process_specifier(const char *process_spec, return parse_process_specifier(process_spec, &ppspec, msg); } +/* XXXX we should use periodic_timer_new() for this stuff */ #ifdef HAVE_EVENT2_EVENT_H #define PERIODIC_TIMER_FLAGS EV_PERSIST #else diff --git a/src/common/util_process.c b/src/common/util_process.c new file mode 100644 index 0000000000..8ccf7f38f9 --- /dev/null +++ b/src/common/util_process.c @@ -0,0 +1,157 @@ +/* Copyright (c) 2003-2004, Roger Dingledine + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2013, 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 +#endif +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#include "compat.h" +#include "util.h" +#include "torlog.h" +#include "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_GENERATE(process_map, waitpid_callback_t, node, process_map_entry_hash_, + process_map_entries_eq_, 0.6, malloc, realloc, free); + +/** + * Begin monitoring the child pid pid to see if we get a SIGCHLD for + * it. If we eventually do, call fn, passing it the exit status (as + * yielded by waitpid) and the pointer arg. + * + * 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 ent. + */ +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 pid; if there is one, run it, + * reporting the exit status as status. */ +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 + diff --git a/src/common/util_process.h b/src/common/util_process.h new file mode 100644 index 0000000000..877702c68e --- /dev/null +++ b/src/common/util_process.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2011-2013, 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 + +#endif diff --git a/src/or/main.c b/src/or/main.c index 6713d80368..2ca717459d 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -54,6 +54,7 @@ #include "routerparse.h" #include "statefile.h" #include "status.h" +#include "util_process.h" #include "ext_orport.h" #ifdef USE_DMALLOC #include @@ -2097,8 +2098,7 @@ process_signal(uintptr_t sig) break; #ifdef SIGCHLD case SIGCHLD: - while (waitpid(-1,NULL,WNOHANG) > 0) ; /* keep reaping until no more - zombies */ + notify_pending_waitpid_callbacks(); break; #endif case SIGNEWNYM: { diff --git a/src/or/or.h b/src/or/or.h index aeaeb8e6a9..9586034d1f 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -42,9 +42,6 @@ #include /* FreeBSD needs this to know what version it is */ #endif #include "torint.h" -#ifdef HAVE_SYS_WAIT_H -#include -#endif #ifdef HAVE_SYS_FCNTL_H #include #endif