1 /* Copyright (c) 2003-2004, Roger Dingledine
2  * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3  * Copyright (c) 2007-2021, 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
process_map_entry_hash_(const waitpid_callback_t * ent)46 process_map_entry_hash_(const waitpid_callback_t *ent)
47 {
48   return (unsigned) ent->pid;
49 }
50 
51 static inline unsigned int
process_map_entries_eq_(const waitpid_callback_t * a,const waitpid_callback_t * b)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 *
set_waitpid_callback(pid_t pid,void (* fn)(int,void *),void * arg)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
clear_waitpid_callback(waitpid_callback_t * ent)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
notify_waitpid_callback_by_pid(pid_t pid,int status)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
notify_pending_waitpid_callbacks(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     notify_waitpid_callback_by_pid(child, status);
150     status = 0; /* should be needless */
151   }
152 }
153 
154 #endif /* !defined(_WIN32) */
155