/* 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 #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_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 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 /* !defined(_WIN32) */