/src/samba/lib/util/tfork.c
Line | Count | Source |
1 | | /* |
2 | | fork on steroids to avoid SIGCHLD and waitpid |
3 | | |
4 | | Copyright (C) Stefan Metzmacher 2010 |
5 | | Copyright (C) Ralph Boehme 2017 |
6 | | |
7 | | This program is free software; you can redistribute it and/or modify |
8 | | it under the terms of the GNU General Public License as published by |
9 | | the Free Software Foundation; either version 3 of the License, or |
10 | | (at your option) any later version. |
11 | | |
12 | | This program is distributed in the hope that it will be useful, |
13 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | GNU General Public License for more details. |
16 | | |
17 | | You should have received a copy of the GNU General Public License |
18 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | #include "replace.h" |
22 | | #include "system/wait.h" |
23 | | #include "system/filesys.h" |
24 | | #include "system/network.h" |
25 | | #include "lib/util/samba_util.h" |
26 | | #include "lib/util/sys_rw.h" |
27 | | #include "lib/util/tfork.h" |
28 | | #include "lib/util/debug.h" |
29 | | #include "lib/util/util_process.h" |
30 | | |
31 | | #ifdef HAVE_PTHREAD |
32 | | #include <pthread.h> |
33 | | #endif |
34 | | |
35 | | #ifdef NDEBUG |
36 | | #undef NDEBUG |
37 | | #endif |
38 | | #include <assert.h> |
39 | | |
40 | | /* |
41 | | * This is how the process hierarchy looks like: |
42 | | * |
43 | | * +----------+ |
44 | | * | caller | |
45 | | * +----------+ |
46 | | * | |
47 | | * fork |
48 | | * | |
49 | | * v |
50 | | * +----------+ |
51 | | * | waiter | |
52 | | * +----------+ |
53 | | * | |
54 | | * fork |
55 | | * | |
56 | | * v |
57 | | * +----------+ |
58 | | * | worker | |
59 | | * +----------+ |
60 | | */ |
61 | | |
62 | | #ifdef HAVE_VALGRIND_HELGRIND_H |
63 | | #include <valgrind/helgrind.h> |
64 | | #endif |
65 | | #ifndef ANNOTATE_BENIGN_RACE_SIZED |
66 | | #define ANNOTATE_BENIGN_RACE_SIZED(obj, size, description) |
67 | | #endif |
68 | | |
69 | | #define TFORK_ANNOTATE_BENIGN_RACE(obj) \ |
70 | 0 | ANNOTATE_BENIGN_RACE_SIZED( \ |
71 | 0 | (obj), sizeof(*(obj)), \ |
72 | 0 | "no race, serialized by tfork_[un]install_sigchld_handler"); |
73 | | |
74 | | /* |
75 | | * The resulting (private) state per tfork_create() call, returned as a opaque |
76 | | * handle to the caller. |
77 | | */ |
78 | | struct tfork { |
79 | | /* |
80 | | * This is returned to the caller with tfork_event_fd() |
81 | | */ |
82 | | int event_fd; |
83 | | |
84 | | /* |
85 | | * This is used in the caller by tfork_status() to read the worker exit |
86 | | * status and to tell the waiter to exit by closing the fd. |
87 | | */ |
88 | | int status_fd; |
89 | | |
90 | | pid_t waiter_pid; |
91 | | pid_t worker_pid; |
92 | | }; |
93 | | |
94 | | /* |
95 | | * Internal per-thread state maintained while inside tfork. |
96 | | */ |
97 | | struct tfork_state { |
98 | | pid_t waiter_pid; |
99 | | int waiter_errno; |
100 | | |
101 | | pid_t worker_pid; |
102 | | }; |
103 | | |
104 | | /* |
105 | | * A global state that synchronizes access to handling SIGCHLD and waiting for |
106 | | * children. |
107 | | */ |
108 | | struct tfork_signal_state { |
109 | | bool available; |
110 | | |
111 | | #ifdef HAVE_PTHREAD |
112 | | pthread_cond_t cond; |
113 | | pthread_mutex_t mutex; |
114 | | #endif |
115 | | |
116 | | /* |
117 | | * pid of the waiter child. This points at waiter_pid in either struct |
118 | | * tfork or struct tfork_state, depending on who called |
119 | | * tfork_install_sigchld_handler(). |
120 | | * |
121 | | * When tfork_install_sigchld_handler() is called the waiter_pid is |
122 | | * still -1 and only set later after fork(), that's why this is must be |
123 | | * a pointer. The signal handler checks this. |
124 | | */ |
125 | | pid_t *pid; |
126 | | |
127 | | struct sigaction oldact; |
128 | | sigset_t oldset; |
129 | | }; |
130 | | |
131 | | static struct tfork_signal_state signal_state; |
132 | | |
133 | | #ifdef HAVE_PTHREAD |
134 | | static pthread_once_t tfork_global_is_initialized = PTHREAD_ONCE_INIT; |
135 | | static pthread_key_t tfork_global_key; |
136 | | #else |
137 | | static struct tfork_state *global_state; |
138 | | #endif |
139 | | |
140 | | static void tfork_sigchld_handler(int signum, siginfo_t *si, void *p); |
141 | | |
142 | | #ifdef HAVE_PTHREAD |
143 | | static void tfork_global_destructor(void *state) |
144 | 0 | { |
145 | 0 | anonymous_shared_free(state); |
146 | 0 | } |
147 | | #endif |
148 | | |
149 | | static int tfork_acquire_sighandling(void) |
150 | 0 | { |
151 | 0 | int ret = 0; |
152 | |
|
153 | 0 | #ifdef HAVE_PTHREAD |
154 | 0 | ret = pthread_mutex_lock(&signal_state.mutex); |
155 | 0 | if (ret != 0) { |
156 | 0 | return ret; |
157 | 0 | } |
158 | | |
159 | 0 | while (!signal_state.available) { |
160 | 0 | ret = pthread_cond_wait(&signal_state.cond, |
161 | 0 | &signal_state.mutex); |
162 | 0 | if (ret != 0) { |
163 | 0 | return ret; |
164 | 0 | } |
165 | 0 | } |
166 | | |
167 | 0 | signal_state.available = false; |
168 | |
|
169 | 0 | ret = pthread_mutex_unlock(&signal_state.mutex); |
170 | 0 | if (ret != 0) { |
171 | 0 | return ret; |
172 | 0 | } |
173 | 0 | #endif |
174 | | |
175 | 0 | return ret; |
176 | 0 | } |
177 | | |
178 | | static int tfork_release_sighandling(void) |
179 | 0 | { |
180 | 0 | int ret = 0; |
181 | |
|
182 | 0 | #ifdef HAVE_PTHREAD |
183 | 0 | ret = pthread_mutex_lock(&signal_state.mutex); |
184 | 0 | if (ret != 0) { |
185 | 0 | return ret; |
186 | 0 | } |
187 | | |
188 | 0 | signal_state.available = true; |
189 | |
|
190 | 0 | ret = pthread_cond_signal(&signal_state.cond); |
191 | 0 | if (ret != 0) { |
192 | 0 | pthread_mutex_unlock(&signal_state.mutex); |
193 | 0 | return ret; |
194 | 0 | } |
195 | | |
196 | 0 | ret = pthread_mutex_unlock(&signal_state.mutex); |
197 | 0 | if (ret != 0) { |
198 | 0 | return ret; |
199 | 0 | } |
200 | 0 | #endif |
201 | | |
202 | 0 | return ret; |
203 | 0 | } |
204 | | |
205 | | #ifdef HAVE_PTHREAD |
206 | | static void tfork_atfork_prepare(void) |
207 | 0 | { |
208 | 0 | int ret; |
209 | |
|
210 | 0 | ret = pthread_mutex_lock(&signal_state.mutex); |
211 | 0 | assert(ret == 0); |
212 | 0 | } |
213 | | |
214 | | static void tfork_atfork_parent(void) |
215 | 0 | { |
216 | 0 | int ret; |
217 | |
|
218 | 0 | ret = pthread_mutex_unlock(&signal_state.mutex); |
219 | 0 | assert(ret == 0); |
220 | 0 | } |
221 | | #endif |
222 | | |
223 | | static void tfork_atfork_child(void) |
224 | 0 | { |
225 | 0 | int ret; |
226 | |
|
227 | 0 | #ifdef HAVE_PTHREAD |
228 | 0 | ret = pthread_mutex_unlock(&signal_state.mutex); |
229 | 0 | assert(ret == 0); |
230 | |
|
231 | 0 | ret = pthread_key_delete(tfork_global_key); |
232 | 0 | assert(ret == 0); |
233 | |
|
234 | 0 | ret = pthread_key_create(&tfork_global_key, tfork_global_destructor); |
235 | 0 | assert(ret == 0); |
236 | | |
237 | | /* |
238 | | * There's no data race on the cond variable from the signal state, we |
239 | | * are writing here, but there are no readers yet. Some data race |
240 | | * detection tools report a race, but the readers are in the parent |
241 | | * process. |
242 | | */ |
243 | 0 | TFORK_ANNOTATE_BENIGN_RACE(&signal_state.cond); |
244 | | |
245 | | /* |
246 | | * There's no way to destroy a condition variable if there are waiters, |
247 | | * pthread_cond_destroy() will return EBUSY. Just zero out memory and |
248 | | * then initialize again. This is not backed by POSIX but should be ok. |
249 | | */ |
250 | 0 | ZERO_STRUCT(signal_state.cond); |
251 | 0 | ret = pthread_cond_init(&signal_state.cond, NULL); |
252 | 0 | assert(ret == 0); |
253 | 0 | #endif |
254 | |
|
255 | 0 | if (signal_state.pid != NULL) { |
256 | |
|
257 | 0 | ret = sigaction(SIGCHLD, &signal_state.oldact, NULL); |
258 | 0 | assert(ret == 0); |
259 | |
|
260 | 0 | #ifdef HAVE_PTHREAD |
261 | 0 | ret = pthread_sigmask(SIG_SETMASK, &signal_state.oldset, NULL); |
262 | | #else |
263 | | ret = sigprocmask(SIG_SETMASK, &signal_state.oldset, NULL); |
264 | | #endif |
265 | 0 | assert(ret == 0); |
266 | |
|
267 | 0 | signal_state.pid = NULL; |
268 | 0 | } |
269 | |
|
270 | 0 | signal_state.available = true; |
271 | 0 | } |
272 | | |
273 | | static void tfork_global_initialize(void) |
274 | 0 | { |
275 | 0 | #ifdef HAVE_PTHREAD |
276 | 0 | int ret; |
277 | |
|
278 | 0 | pthread_atfork(tfork_atfork_prepare, |
279 | 0 | tfork_atfork_parent, |
280 | 0 | tfork_atfork_child); |
281 | |
|
282 | 0 | ret = pthread_key_create(&tfork_global_key, tfork_global_destructor); |
283 | 0 | assert(ret == 0); |
284 | |
|
285 | 0 | ret = pthread_mutex_init(&signal_state.mutex, NULL); |
286 | 0 | assert(ret == 0); |
287 | |
|
288 | 0 | ret = pthread_cond_init(&signal_state.cond, NULL); |
289 | 0 | assert(ret == 0); |
290 | | |
291 | | /* |
292 | | * In a threaded process there's no data race on t->waiter_pid as |
293 | | * we're serializing globally via tfork_acquire_sighandling() and |
294 | | * tfork_release_sighandling(). |
295 | | */ |
296 | 0 | TFORK_ANNOTATE_BENIGN_RACE(&signal_state.pid); |
297 | 0 | #endif |
298 | |
|
299 | 0 | signal_state.available = true; |
300 | 0 | } |
301 | | |
302 | | static struct tfork_state *tfork_global_get(void) |
303 | 0 | { |
304 | 0 | struct tfork_state *state = NULL; |
305 | 0 | #ifdef HAVE_PTHREAD |
306 | 0 | int ret; |
307 | 0 | #endif |
308 | |
|
309 | 0 | #ifdef HAVE_PTHREAD |
310 | 0 | state = (struct tfork_state *)pthread_getspecific(tfork_global_key); |
311 | | #else |
312 | | state = global_state; |
313 | | #endif |
314 | 0 | if (state != NULL) { |
315 | 0 | return state; |
316 | 0 | } |
317 | | |
318 | 0 | state = (struct tfork_state *)anonymous_shared_allocate( |
319 | 0 | sizeof(struct tfork_state)); |
320 | 0 | if (state == NULL) { |
321 | 0 | return NULL; |
322 | 0 | } |
323 | | |
324 | 0 | #ifdef HAVE_PTHREAD |
325 | 0 | ret = pthread_setspecific(tfork_global_key, state); |
326 | 0 | if (ret != 0) { |
327 | 0 | anonymous_shared_free(state); |
328 | 0 | return NULL; |
329 | 0 | } |
330 | 0 | #endif |
331 | 0 | return state; |
332 | 0 | } |
333 | | |
334 | | static void tfork_global_free(void) |
335 | 0 | { |
336 | 0 | struct tfork_state *state = NULL; |
337 | 0 | #ifdef HAVE_PTHREAD |
338 | 0 | int ret; |
339 | 0 | #endif |
340 | |
|
341 | 0 | #ifdef HAVE_PTHREAD |
342 | 0 | state = (struct tfork_state *)pthread_getspecific(tfork_global_key); |
343 | | #else |
344 | | state = global_state; |
345 | | #endif |
346 | 0 | if (state == NULL) { |
347 | 0 | return; |
348 | 0 | } |
349 | | |
350 | 0 | #ifdef HAVE_PTHREAD |
351 | 0 | ret = pthread_setspecific(tfork_global_key, NULL); |
352 | 0 | if (ret != 0) { |
353 | 0 | return; |
354 | 0 | } |
355 | 0 | #endif |
356 | 0 | anonymous_shared_free(state); |
357 | 0 | } |
358 | | |
359 | | /** |
360 | | * Only one thread at a time is allowed to handle SIGCHLD signals |
361 | | **/ |
362 | | static int tfork_install_sigchld_handler(pid_t *pid) |
363 | 0 | { |
364 | 0 | int ret; |
365 | 0 | struct sigaction act; |
366 | 0 | sigset_t set; |
367 | |
|
368 | 0 | ret = tfork_acquire_sighandling(); |
369 | 0 | if (ret != 0) { |
370 | 0 | return -1; |
371 | 0 | } |
372 | | |
373 | 0 | assert(signal_state.pid == NULL); |
374 | 0 | signal_state.pid = pid; |
375 | |
|
376 | 0 | act = (struct sigaction) { |
377 | 0 | .sa_sigaction = tfork_sigchld_handler, |
378 | 0 | .sa_flags = SA_SIGINFO, |
379 | 0 | }; |
380 | |
|
381 | 0 | ret = sigaction(SIGCHLD, &act, &signal_state.oldact); |
382 | 0 | if (ret != 0) { |
383 | 0 | return -1; |
384 | 0 | } |
385 | | |
386 | 0 | sigemptyset(&set); |
387 | 0 | sigaddset(&set, SIGCHLD); |
388 | 0 | #ifdef HAVE_PTHREAD |
389 | 0 | ret = pthread_sigmask(SIG_UNBLOCK, &set, &signal_state.oldset); |
390 | | #else |
391 | | ret = sigprocmask(SIG_UNBLOCK, &set, &signal_state.oldset); |
392 | | #endif |
393 | 0 | if (ret != 0) { |
394 | 0 | return -1; |
395 | 0 | } |
396 | | |
397 | 0 | return 0; |
398 | 0 | } |
399 | | |
400 | | static int tfork_uninstall_sigchld_handler(void) |
401 | 0 | { |
402 | 0 | int ret; |
403 | |
|
404 | 0 | signal_state.pid = NULL; |
405 | |
|
406 | 0 | ret = sigaction(SIGCHLD, &signal_state.oldact, NULL); |
407 | 0 | if (ret != 0) { |
408 | 0 | return -1; |
409 | 0 | } |
410 | | |
411 | 0 | #ifdef HAVE_PTHREAD |
412 | 0 | ret = pthread_sigmask(SIG_SETMASK, &signal_state.oldset, NULL); |
413 | | #else |
414 | | ret = sigprocmask(SIG_SETMASK, &signal_state.oldset, NULL); |
415 | | #endif |
416 | 0 | if (ret != 0) { |
417 | 0 | return -1; |
418 | 0 | } |
419 | | |
420 | 0 | ret = tfork_release_sighandling(); |
421 | 0 | if (ret != 0) { |
422 | 0 | return -1; |
423 | 0 | } |
424 | | |
425 | 0 | return 0; |
426 | 0 | } |
427 | | |
428 | | static void tfork_sigchld_handler(int signum, siginfo_t *si, void *p) |
429 | 0 | { |
430 | 0 | if ((signal_state.pid != NULL) && |
431 | 0 | (*signal_state.pid != -1) && |
432 | 0 | (si->si_pid == *signal_state.pid)) |
433 | 0 | { |
434 | 0 | return; |
435 | 0 | } |
436 | | |
437 | | /* |
438 | | * Not our child, forward to old handler |
439 | | */ |
440 | 0 | if (signal_state.oldact.sa_flags & SA_SIGINFO) { |
441 | 0 | signal_state.oldact.sa_sigaction(signum, si, p); |
442 | 0 | return; |
443 | 0 | } |
444 | | |
445 | 0 | if (signal_state.oldact.sa_handler == SIG_IGN) { |
446 | 0 | return; |
447 | 0 | } |
448 | 0 | if (signal_state.oldact.sa_handler == SIG_DFL) { |
449 | 0 | return; |
450 | 0 | } |
451 | 0 | signal_state.oldact.sa_handler(signum); |
452 | 0 | } |
453 | | |
454 | | static pid_t tfork_start_waiter_and_worker(struct tfork_state *state, |
455 | | int *_event_fd, |
456 | | int *_status_fd) |
457 | 0 | { |
458 | 0 | int p[2]; |
459 | 0 | int status_sp_caller_fd = -1; |
460 | 0 | int status_sp_waiter_fd = -1; |
461 | 0 | int event_pipe_caller_fd = -1; |
462 | 0 | int event_pipe_waiter_fd = -1; |
463 | 0 | int ready_pipe_caller_fd = -1; |
464 | 0 | int ready_pipe_worker_fd = -1; |
465 | 0 | ssize_t nwritten; |
466 | 0 | ssize_t nread; |
467 | 0 | pid_t pid; |
468 | 0 | int status; |
469 | 0 | int fd; |
470 | 0 | char c; |
471 | 0 | int ret; |
472 | |
|
473 | 0 | *_event_fd = -1; |
474 | 0 | *_status_fd = -1; |
475 | |
|
476 | 0 | if (state == NULL) { |
477 | 0 | return -1; |
478 | 0 | } |
479 | | |
480 | 0 | ret = socketpair(AF_UNIX, SOCK_STREAM, 0, p); |
481 | 0 | if (ret != 0) { |
482 | 0 | return -1; |
483 | 0 | } |
484 | 0 | set_close_on_exec(p[0]); |
485 | 0 | set_close_on_exec(p[1]); |
486 | 0 | status_sp_caller_fd = p[0]; |
487 | 0 | status_sp_waiter_fd = p[1]; |
488 | |
|
489 | 0 | ret = pipe(p); |
490 | 0 | if (ret != 0) { |
491 | 0 | close(status_sp_caller_fd); |
492 | 0 | close(status_sp_waiter_fd); |
493 | 0 | return -1; |
494 | 0 | } |
495 | 0 | set_close_on_exec(p[0]); |
496 | 0 | set_close_on_exec(p[1]); |
497 | 0 | event_pipe_caller_fd = p[0]; |
498 | 0 | event_pipe_waiter_fd = p[1]; |
499 | | |
500 | |
|
501 | 0 | ret = pipe(p); |
502 | 0 | if (ret != 0) { |
503 | 0 | close(status_sp_caller_fd); |
504 | 0 | close(status_sp_waiter_fd); |
505 | 0 | close(event_pipe_caller_fd); |
506 | 0 | close(event_pipe_waiter_fd); |
507 | 0 | return -1; |
508 | 0 | } |
509 | 0 | set_close_on_exec(p[0]); |
510 | 0 | set_close_on_exec(p[1]); |
511 | 0 | ready_pipe_worker_fd = p[0]; |
512 | 0 | ready_pipe_caller_fd = p[1]; |
513 | |
|
514 | 0 | pid = fork(); |
515 | 0 | if (pid == -1) { |
516 | 0 | close(status_sp_caller_fd); |
517 | 0 | close(status_sp_waiter_fd); |
518 | 0 | close(event_pipe_caller_fd); |
519 | 0 | close(event_pipe_waiter_fd); |
520 | 0 | close(ready_pipe_caller_fd); |
521 | 0 | close(ready_pipe_worker_fd); |
522 | 0 | return -1; |
523 | 0 | } |
524 | 0 | if (pid != 0) { |
525 | | /* The caller */ |
526 | | |
527 | | /* |
528 | | * In a threaded process there's no data race on |
529 | | * state->waiter_pid as we're serializing globally via |
530 | | * tfork_acquire_sighandling() and tfork_release_sighandling(). |
531 | | */ |
532 | 0 | TFORK_ANNOTATE_BENIGN_RACE(&state->waiter_pid); |
533 | |
|
534 | 0 | state->waiter_pid = pid; |
535 | |
|
536 | 0 | close(status_sp_waiter_fd); |
537 | 0 | close(event_pipe_waiter_fd); |
538 | 0 | close(ready_pipe_worker_fd); |
539 | |
|
540 | 0 | set_blocking(event_pipe_caller_fd, false); |
541 | | |
542 | | /* |
543 | | * wait for the waiter to get ready. |
544 | | */ |
545 | 0 | nread = sys_read(status_sp_caller_fd, &c, sizeof(char)); |
546 | 0 | if (nread != sizeof(char)) { |
547 | 0 | return -1; |
548 | 0 | } |
549 | | |
550 | | /* |
551 | | * Notify the worker to start. |
552 | | */ |
553 | 0 | nwritten = sys_write(ready_pipe_caller_fd, |
554 | 0 | &(char){0}, sizeof(char)); |
555 | 0 | if (nwritten != sizeof(char)) { |
556 | 0 | close(ready_pipe_caller_fd); |
557 | 0 | return -1; |
558 | 0 | } |
559 | 0 | close(ready_pipe_caller_fd); |
560 | |
|
561 | 0 | *_event_fd = event_pipe_caller_fd; |
562 | 0 | *_status_fd = status_sp_caller_fd; |
563 | |
|
564 | 0 | return pid; |
565 | 0 | } |
566 | | |
567 | | #ifndef HAVE_PTHREAD |
568 | | /* cleanup sigchld_handler */ |
569 | | tfork_atfork_child(); |
570 | | #endif |
571 | | |
572 | | /* |
573 | | * The "waiter" child. |
574 | | */ |
575 | 0 | process_set_title("tfork waiter", "tfork waiter process"); |
576 | |
|
577 | 0 | CatchSignal(SIGCHLD, SIG_DFL); |
578 | |
|
579 | 0 | close(status_sp_caller_fd); |
580 | 0 | close(event_pipe_caller_fd); |
581 | 0 | close(ready_pipe_caller_fd); |
582 | |
|
583 | 0 | pid = fork(); |
584 | 0 | if (pid == -1) { |
585 | 0 | state->waiter_errno = errno; |
586 | 0 | _exit(0); |
587 | 0 | } |
588 | 0 | if (pid == 0) { |
589 | | /* |
590 | | * The worker child. |
591 | | */ |
592 | |
|
593 | 0 | close(status_sp_waiter_fd); |
594 | 0 | close(event_pipe_waiter_fd); |
595 | | |
596 | | /* |
597 | | * Wait for the caller to give us a go! |
598 | | */ |
599 | 0 | nread = sys_read(ready_pipe_worker_fd, &c, sizeof(char)); |
600 | 0 | if (nread != sizeof(char)) { |
601 | 0 | _exit(1); |
602 | 0 | } |
603 | 0 | close(ready_pipe_worker_fd); |
604 | |
|
605 | 0 | return 0; |
606 | 0 | } |
607 | 0 | state->worker_pid = pid; |
608 | 0 | process_set_title("tfork(%d)", "tfork waiter process(%d)", pid); |
609 | |
|
610 | 0 | close(ready_pipe_worker_fd); |
611 | | |
612 | | /* |
613 | | * We're going to stay around until child2 exits, so lets close all fds |
614 | | * other than the pipe fd we may have inherited from the caller. |
615 | | * |
616 | | * Dup event_sp_waiter_fd and status_sp_waiter_fd onto fds 0 and 1 so we |
617 | | * can then call closefrom(2). |
618 | | */ |
619 | 0 | if (event_pipe_waiter_fd > 0) { |
620 | 0 | int dup_fd = 0; |
621 | |
|
622 | 0 | if (status_sp_waiter_fd == 0) { |
623 | 0 | dup_fd = 1; |
624 | 0 | } |
625 | |
|
626 | 0 | do { |
627 | 0 | fd = dup2(event_pipe_waiter_fd, dup_fd); |
628 | 0 | } while ((fd == -1) && (errno == EINTR)); |
629 | 0 | if (fd == -1) { |
630 | 0 | state->waiter_errno = errno; |
631 | 0 | kill(state->worker_pid, SIGKILL); |
632 | 0 | state->worker_pid = -1; |
633 | 0 | _exit(1); |
634 | 0 | } |
635 | 0 | event_pipe_waiter_fd = fd; |
636 | 0 | } |
637 | | |
638 | 0 | if (status_sp_waiter_fd > 1) { |
639 | 0 | do { |
640 | 0 | fd = dup2(status_sp_waiter_fd, 1); |
641 | 0 | } while ((fd == -1) && (errno == EINTR)); |
642 | 0 | if (fd == -1) { |
643 | 0 | state->waiter_errno = errno; |
644 | 0 | kill(state->worker_pid, SIGKILL); |
645 | 0 | state->worker_pid = -1; |
646 | 0 | _exit(1); |
647 | 0 | } |
648 | 0 | status_sp_waiter_fd = fd; |
649 | 0 | } |
650 | | |
651 | 0 | closefrom(2); |
652 | | |
653 | | /* Tell the caller we're ready */ |
654 | 0 | nwritten = sys_write(status_sp_waiter_fd, &(char){0}, sizeof(char)); |
655 | 0 | if (nwritten != sizeof(char)) { |
656 | 0 | _exit(1); |
657 | 0 | } |
658 | | |
659 | 0 | tfork_global_free(); |
660 | 0 | state = NULL; |
661 | |
|
662 | 0 | do { |
663 | 0 | ret = waitpid(pid, &status, 0); |
664 | 0 | } while ((ret == -1) && (errno == EINTR)); |
665 | 0 | if (ret == -1) { |
666 | 0 | status = errno; |
667 | 0 | kill(pid, SIGKILL); |
668 | 0 | } |
669 | | |
670 | | /* |
671 | | * This writes the worker child exit status via our internal socketpair |
672 | | * so the tfork_status() implementation can read it from its end. |
673 | | */ |
674 | 0 | nwritten = sys_write(status_sp_waiter_fd, &status, sizeof(status)); |
675 | 0 | if (nwritten == -1) { |
676 | 0 | if (errno != EPIPE && errno != ECONNRESET) { |
677 | 0 | _exit(errno); |
678 | 0 | } |
679 | | /* |
680 | | * The caller exited and didn't call tfork_status(). |
681 | | */ |
682 | 0 | _exit(0); |
683 | 0 | } |
684 | 0 | if (nwritten != sizeof(status)) { |
685 | 0 | _exit(1); |
686 | 0 | } |
687 | | |
688 | | /* |
689 | | * This write to the event_fd returned by tfork_event_fd() and notifies |
690 | | * the caller that the worker child is done and he may now call |
691 | | * tfork_status(). |
692 | | */ |
693 | 0 | nwritten = sys_write(event_pipe_waiter_fd, &(char){0}, sizeof(char)); |
694 | 0 | if (nwritten != sizeof(char)) { |
695 | 0 | _exit(1); |
696 | 0 | } |
697 | | |
698 | | /* |
699 | | * Wait for our parent (the process that called tfork_create()) to |
700 | | * close() the socketpair fd in tfork_status(). |
701 | | * |
702 | | * Again, the caller might have exited without calling tfork_status(). |
703 | | */ |
704 | 0 | nread = sys_read(status_sp_waiter_fd, &c, 1); |
705 | 0 | if (nread == -1) { |
706 | 0 | if (errno == EPIPE || errno == ECONNRESET) { |
707 | 0 | _exit(0); |
708 | 0 | } |
709 | 0 | _exit(errno); |
710 | 0 | } |
711 | 0 | if (nread != 1) { |
712 | 0 | _exit(255); |
713 | 0 | } |
714 | | |
715 | 0 | _exit(0); |
716 | 0 | } |
717 | | |
718 | | static int tfork_create_reap_waiter(pid_t waiter_pid) |
719 | 0 | { |
720 | 0 | pid_t pid; |
721 | 0 | int waiter_status; |
722 | |
|
723 | 0 | if (waiter_pid == -1) { |
724 | 0 | return 0; |
725 | 0 | } |
726 | | |
727 | 0 | kill(waiter_pid, SIGKILL); |
728 | |
|
729 | 0 | do { |
730 | 0 | pid = waitpid(waiter_pid, &waiter_status, 0); |
731 | 0 | } while ((pid == -1) && (errno == EINTR)); |
732 | 0 | assert(pid == waiter_pid); |
733 | |
|
734 | 0 | return 0; |
735 | 0 | } |
736 | | |
737 | | struct tfork *tfork_create(void) |
738 | 0 | { |
739 | 0 | struct tfork_state *state = NULL; |
740 | 0 | struct tfork *t = NULL; |
741 | 0 | pid_t pid; |
742 | 0 | int saved_errno = 0; |
743 | 0 | int ret = 0; |
744 | 0 | int ret2; |
745 | |
|
746 | 0 | #ifdef HAVE_PTHREAD |
747 | 0 | ret = pthread_once(&tfork_global_is_initialized, |
748 | 0 | tfork_global_initialize); |
749 | 0 | if (ret != 0) { |
750 | 0 | return NULL; |
751 | 0 | } |
752 | | #else |
753 | | tfork_global_initialize(); |
754 | | #endif |
755 | | |
756 | 0 | state = tfork_global_get(); |
757 | 0 | if (state == NULL) { |
758 | 0 | return NULL; |
759 | 0 | } |
760 | 0 | *state = (struct tfork_state) { |
761 | 0 | .waiter_pid = -1, |
762 | 0 | .waiter_errno = ECANCELED, |
763 | 0 | .worker_pid = -1, |
764 | 0 | }; |
765 | |
|
766 | 0 | t = malloc(sizeof(struct tfork)); |
767 | 0 | if (t == NULL) { |
768 | 0 | ret = -1; |
769 | 0 | goto cleanup; |
770 | 0 | } |
771 | | |
772 | 0 | *t = (struct tfork) { |
773 | 0 | .event_fd = -1, |
774 | 0 | .status_fd = -1, |
775 | 0 | .waiter_pid = -1, |
776 | 0 | .worker_pid = -1, |
777 | 0 | }; |
778 | |
|
779 | 0 | ret = tfork_install_sigchld_handler(&state->waiter_pid); |
780 | 0 | if (ret != 0) { |
781 | 0 | goto cleanup; |
782 | 0 | } |
783 | | |
784 | 0 | pid = tfork_start_waiter_and_worker(state, |
785 | 0 | &t->event_fd, |
786 | 0 | &t->status_fd); |
787 | 0 | if (pid == -1) { |
788 | 0 | ret = -1; |
789 | 0 | goto cleanup; |
790 | 0 | } |
791 | 0 | if (pid == 0) { |
792 | | /* In the worker */ |
793 | 0 | tfork_global_free(); |
794 | 0 | t->worker_pid = 0; |
795 | 0 | return t; |
796 | 0 | } |
797 | | |
798 | | /* |
799 | | * In a threaded process there's no data race on t->waiter_pid as |
800 | | * we're serializing globally via tfork_acquire_sighandling() and |
801 | | * tfork_release_sighandling(). |
802 | | */ |
803 | 0 | TFORK_ANNOTATE_BENIGN_RACE(&t->waiter_pid); |
804 | |
|
805 | 0 | t->waiter_pid = pid; |
806 | 0 | t->worker_pid = state->worker_pid; |
807 | |
|
808 | 0 | cleanup: |
809 | 0 | if (ret == -1) { |
810 | 0 | saved_errno = errno; |
811 | |
|
812 | 0 | if (t != NULL) { |
813 | 0 | if (t->status_fd != -1) { |
814 | 0 | close(t->status_fd); |
815 | 0 | } |
816 | 0 | if (t->event_fd != -1) { |
817 | 0 | close(t->event_fd); |
818 | 0 | } |
819 | |
|
820 | 0 | ret2 = tfork_create_reap_waiter(state->waiter_pid); |
821 | 0 | assert(ret2 == 0); |
822 | |
|
823 | 0 | free(t); |
824 | 0 | t = NULL; |
825 | 0 | } |
826 | 0 | } |
827 | |
|
828 | 0 | ret2 = tfork_uninstall_sigchld_handler(); |
829 | 0 | assert(ret2 == 0); |
830 | |
|
831 | 0 | tfork_global_free(); |
832 | |
|
833 | 0 | if (ret == -1) { |
834 | 0 | errno = saved_errno; |
835 | 0 | } |
836 | 0 | return t; |
837 | 0 | } |
838 | | |
839 | | pid_t tfork_child_pid(const struct tfork *t) |
840 | 0 | { |
841 | 0 | return t->worker_pid; |
842 | 0 | } |
843 | | |
844 | | int tfork_event_fd(struct tfork *t) |
845 | 0 | { |
846 | 0 | int fd = t->event_fd; |
847 | |
|
848 | 0 | assert(t->event_fd != -1); |
849 | 0 | t->event_fd = -1; |
850 | |
|
851 | 0 | return fd; |
852 | 0 | } |
853 | | |
854 | | int tfork_status(struct tfork **_t, bool wait) |
855 | 0 | { |
856 | 0 | struct tfork *t = *_t; |
857 | 0 | int status; |
858 | 0 | ssize_t nread; |
859 | 0 | int waiter_status; |
860 | 0 | pid_t pid; |
861 | 0 | int ret; |
862 | |
|
863 | 0 | if (t == NULL) { |
864 | 0 | return -1; |
865 | 0 | } |
866 | | |
867 | 0 | if (wait) { |
868 | 0 | set_blocking(t->status_fd, true); |
869 | |
|
870 | 0 | nread = sys_read(t->status_fd, &status, sizeof(int)); |
871 | 0 | } else { |
872 | 0 | set_blocking(t->status_fd, false); |
873 | |
|
874 | 0 | nread = read(t->status_fd, &status, sizeof(int)); |
875 | 0 | if ((nread == -1) && |
876 | 0 | ((errno == EAGAIN) || (errno == EWOULDBLOCK) || errno == EINTR)) { |
877 | 0 | errno = EAGAIN; |
878 | 0 | return -1; |
879 | 0 | } |
880 | 0 | } |
881 | 0 | if (nread != sizeof(int)) { |
882 | 0 | return -1; |
883 | 0 | } |
884 | | |
885 | 0 | ret = tfork_install_sigchld_handler(&t->waiter_pid); |
886 | 0 | if (ret != 0) { |
887 | 0 | return -1; |
888 | 0 | } |
889 | | |
890 | | /* |
891 | | * This triggers process exit in the waiter. |
892 | | * We write to the fd as well as closing it, as any tforked sibling |
893 | | * processes will also have the writable end of this socket open. |
894 | | * |
895 | | */ |
896 | 0 | { |
897 | 0 | size_t nwritten; |
898 | 0 | nwritten = sys_write(t->status_fd, &(char){0}, sizeof(char)); |
899 | 0 | if (nwritten != sizeof(char)) { |
900 | 0 | close(t->status_fd); |
901 | 0 | return -1; |
902 | 0 | } |
903 | 0 | } |
904 | 0 | close(t->status_fd); |
905 | |
|
906 | 0 | do { |
907 | 0 | pid = waitpid(t->waiter_pid, &waiter_status, 0); |
908 | 0 | } while ((pid == -1) && (errno == EINTR)); |
909 | 0 | assert(pid == t->waiter_pid); |
910 | |
|
911 | 0 | if (t->event_fd != -1) { |
912 | 0 | close(t->event_fd); |
913 | 0 | t->event_fd = -1; |
914 | 0 | } |
915 | |
|
916 | 0 | free(t); |
917 | 0 | t = NULL; |
918 | 0 | *_t = NULL; |
919 | |
|
920 | 0 | ret = tfork_uninstall_sigchld_handler(); |
921 | 0 | assert(ret == 0); |
922 | |
|
923 | 0 | return status; |
924 | 0 | } |
925 | | |
926 | | int tfork_destroy(struct tfork **_t) |
927 | 0 | { |
928 | 0 | struct tfork *t = *_t; |
929 | 0 | int ret; |
930 | |
|
931 | 0 | if (t == NULL) { |
932 | 0 | errno = EINVAL; |
933 | 0 | return -1; |
934 | 0 | } |
935 | | |
936 | 0 | kill(t->worker_pid, SIGKILL); |
937 | |
|
938 | 0 | ret = tfork_status(_t, true); |
939 | 0 | if (ret == -1) { |
940 | 0 | return -1; |
941 | 0 | } |
942 | | |
943 | 0 | return 0; |
944 | 0 | } |