waitpid.c (4145B)
1 /* Copyright (c) 2003-2004, Roger Dingledine 2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. 3 * Copyright (c) 2007-2025, The Tor Project, Inc. */ 4 /* See LICENSE for licensing information */ 5 6 /** 7 * \file waitpid.c 8 * \brief Convenience structures for handlers for handling waitpid(). 9 **/ 10 11 #include "orconfig.h" 12 13 #ifndef _WIN32 14 15 #include "lib/process/waitpid.h" 16 #include "lib/log/log.h" 17 #include "lib/log/util_bug.h" 18 #include "lib/malloc/malloc.h" 19 #include "ext/ht.h" 20 21 #ifdef HAVE_SYS_WAIT_H 22 #include <sys/wait.h> 23 #endif 24 25 #include <string.h> 26 27 /* ================================================== */ 28 /* Convenience structures for handlers for waitpid(). 29 * 30 * The tor_process_monitor*() code above doesn't use them, since it is for 31 * monitoring a non-child process. 32 */ 33 34 /** Mapping from a PID to a userfn/userdata pair. */ 35 struct waitpid_callback_t { 36 HT_ENTRY(waitpid_callback_t) node; 37 pid_t pid; 38 39 void (*userfn)(int, void *userdata); 40 void *userdata; 41 42 unsigned running; 43 }; 44 45 static inline unsigned int 46 process_map_entry_hash_(const waitpid_callback_t *ent) 47 { 48 return (unsigned) ent->pid; 49 } 50 51 static inline unsigned int 52 process_map_entries_eq_(const waitpid_callback_t *a, 53 const waitpid_callback_t *b) 54 { 55 return a->pid == b->pid; 56 } 57 58 static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER(); 59 60 HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_, 61 process_map_entries_eq_); 62 HT_GENERATE2(process_map, waitpid_callback_t, node, process_map_entry_hash_, 63 process_map_entries_eq_, 0.6, tor_reallocarray_, tor_free_); 64 65 /** 66 * Begin monitoring the child pid <b>pid</b> to see if we get a SIGCHLD for 67 * it. If we eventually do, call <b>fn</b>, passing it the exit status (as 68 * yielded by waitpid) and the pointer <b>arg</b>. 69 * 70 * To cancel this, or clean up after it has triggered, call 71 * clear_waitpid_callback(). 72 */ 73 waitpid_callback_t * 74 set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg) 75 { 76 waitpid_callback_t *old_ent; 77 waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t)); 78 ent->pid = pid; 79 ent->userfn = fn; 80 ent->userdata = arg; 81 ent->running = 1; 82 83 old_ent = HT_REPLACE(process_map, &process_map, ent); 84 if (old_ent) { 85 log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be " 86 "impossible.", (unsigned) pid); 87 old_ent->running = 0; 88 } 89 90 return ent; 91 } 92 93 /** 94 * Cancel a waitpid_callback_t, or clean up after one has triggered. Releases 95 * all storage held by <b>ent</b>. 96 */ 97 void 98 clear_waitpid_callback(waitpid_callback_t *ent) 99 { 100 waitpid_callback_t *old_ent; 101 if (ent == NULL) 102 return; 103 104 if (ent->running) { 105 old_ent = HT_REMOVE(process_map, &process_map, ent); 106 if (old_ent != ent) { 107 log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.", 108 (unsigned) ent->pid); 109 return; 110 } 111 } 112 113 tor_free(ent); 114 } 115 116 /** Helper: find the callback for <b>pid</b>; if there is one, run it, 117 * reporting the exit status as <b>status</b>. */ 118 static void 119 notify_waitpid_callback_by_pid(pid_t pid, int status) 120 { 121 waitpid_callback_t search, *ent; 122 123 search.pid = pid; 124 ent = HT_REMOVE(process_map, &process_map, &search); 125 if (!ent || !ent->running) { 126 log_info(LD_GENERAL, "Child process %u has exited; no callback was " 127 "registered", (unsigned)pid); 128 return; 129 } 130 131 log_info(LD_GENERAL, "Child process %u has exited; running callback.", 132 (unsigned)pid); 133 134 ent->running = 0; 135 ent->userfn(status, ent->userdata); 136 } 137 138 /** Use waitpid() to wait for all children that have exited, and invoke any 139 * callbacks registered for them. */ 140 void 141 notify_pending_waitpid_callbacks(void) 142 { 143 /* I was going to call this function reap_zombie_children(), but 144 * that makes it sound way more exciting than it really is. */ 145 pid_t child; 146 int status = 0; 147 148 while ((child = waitpid(-1, &status, WNOHANG)) > 0) { 149 status = WIFEXITED(status) ? WEXITSTATUS(status) : -1; 150 notify_waitpid_callback_by_pid(child, status); 151 status = 0; /* should be needless */ 152 } 153 } 154 155 #endif /* !defined(_WIN32) */