Line data Source code
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
46 11 : process_map_entry_hash_(const waitpid_callback_t *ent)
47 : {
48 11 : return (unsigned) ent->pid;
49 : }
50 :
51 : static inline unsigned int
52 6 : process_map_entries_eq_(const waitpid_callback_t *a,
53 : const waitpid_callback_t *b)
54 : {
55 6 : return a->pid == b->pid;
56 : }
57 :
58 : static HT_HEAD(process_map, waitpid_callback_t) process_map = HT_INITIALIZER();
59 :
60 22 : HT_PROTOTYPE(process_map, waitpid_callback_t, node, process_map_entry_hash_,
61 : process_map_entries_eq_);
62 2 : 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 6 : set_waitpid_callback(pid_t pid, void (*fn)(int, void *), void *arg)
75 : {
76 6 : waitpid_callback_t *old_ent;
77 6 : waitpid_callback_t *ent = tor_malloc_zero(sizeof(waitpid_callback_t));
78 6 : ent->pid = pid;
79 6 : ent->userfn = fn;
80 6 : ent->userdata = arg;
81 6 : ent->running = 1;
82 :
83 6 : old_ent = HT_REPLACE(process_map, &process_map, ent);
84 6 : if (old_ent) {
85 1 : log_warn(LD_BUG, "Replaced a waitpid monitor on pid %u. That should be "
86 : "impossible.", (unsigned) pid);
87 1 : old_ent->running = 0;
88 : }
89 :
90 6 : 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 36 : clear_waitpid_callback(waitpid_callback_t *ent)
99 : {
100 36 : waitpid_callback_t *old_ent;
101 36 : if (ent == NULL)
102 : return;
103 :
104 6 : if (ent->running) {
105 2 : old_ent = HT_REMOVE(process_map, &process_map, ent);
106 2 : if (old_ent != ent) {
107 0 : log_warn(LD_BUG, "Couldn't remove waitpid monitor for pid %u.",
108 : (unsigned) ent->pid);
109 0 : return;
110 : }
111 : }
112 :
113 6 : 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 3 : notify_waitpid_callback_by_pid(pid_t pid, int status)
120 : {
121 3 : waitpid_callback_t search, *ent;
122 :
123 3 : search.pid = pid;
124 3 : ent = HT_REMOVE(process_map, &process_map, &search);
125 3 : if (!ent || !ent->running) {
126 0 : log_info(LD_GENERAL, "Child process %u has exited; no callback was "
127 : "registered", (unsigned)pid);
128 0 : return;
129 : }
130 :
131 3 : log_info(LD_GENERAL, "Child process %u has exited; running callback.",
132 : (unsigned)pid);
133 :
134 3 : ent->running = 0;
135 3 : 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 8 : 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 8 : pid_t child;
146 8 : int status = 0;
147 :
148 11 : while ((child = waitpid(-1, &status, WNOHANG)) > 0) {
149 3 : notify_waitpid_callback_by_pid(child, status);
150 3 : status = 0; /* should be needless */
151 : }
152 8 : }
153 :
154 : #endif /* !defined(_WIN32) */
|