pthread_atfork.c revision 1.24 1 1.24 riastrad /* $NetBSD: pthread_atfork.c,v 1.24 2025/03/02 22:46:23 riastradh Exp $ */
2 1.1 nathanw
3 1.1 nathanw /*-
4 1.1 nathanw * Copyright (c) 2002 The NetBSD Foundation, Inc.
5 1.1 nathanw * All rights reserved.
6 1.1 nathanw *
7 1.1 nathanw * This code is derived from software contributed to The NetBSD Foundation
8 1.1 nathanw * by Nathan J. Williams.
9 1.1 nathanw *
10 1.1 nathanw * Redistribution and use in source and binary forms, with or without
11 1.1 nathanw * modification, are permitted provided that the following conditions
12 1.1 nathanw * are met:
13 1.1 nathanw * 1. Redistributions of source code must retain the above copyright
14 1.1 nathanw * notice, this list of conditions and the following disclaimer.
15 1.1 nathanw * 2. Redistributions in binary form must reproduce the above copyright
16 1.1 nathanw * notice, this list of conditions and the following disclaimer in the
17 1.1 nathanw * documentation and/or other materials provided with the distribution.
18 1.1 nathanw *
19 1.1 nathanw * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 1.1 nathanw * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 1.1 nathanw * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 1.1 nathanw * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 1.1 nathanw * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 1.1 nathanw * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 1.1 nathanw * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 1.1 nathanw * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 1.1 nathanw * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 1.1 nathanw * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 1.1 nathanw * POSSIBILITY OF SUCH DAMAGE.
30 1.1 nathanw */
31 1.1 nathanw
32 1.1 nathanw #include <sys/cdefs.h>
33 1.1 nathanw #if defined(LIBC_SCCS) && !defined(lint)
34 1.24 riastrad __RCSID("$NetBSD: pthread_atfork.c,v 1.24 2025/03/02 22:46:23 riastradh Exp $");
35 1.1 nathanw #endif /* LIBC_SCCS and not lint */
36 1.1 nathanw
37 1.1 nathanw #include "namespace.h"
38 1.1 nathanw
39 1.24 riastrad #include <sys/queue.h>
40 1.24 riastrad
41 1.1 nathanw #include <errno.h>
42 1.1 nathanw #include <stdlib.h>
43 1.1 nathanw #include <unistd.h>
44 1.24 riastrad
45 1.24 riastrad #include "atfork.h"
46 1.15 joerg #include "extern.h"
47 1.1 nathanw #include "reentrant.h"
48 1.1 nathanw
49 1.1 nathanw #ifdef __weak_alias
50 1.1 nathanw __weak_alias(pthread_atfork, _pthread_atfork)
51 1.1 nathanw __weak_alias(fork, _fork)
52 1.1 nathanw #endif /* __weak_alias */
53 1.1 nathanw
54 1.13 joerg pid_t
55 1.14 joerg __locked_fork(int *my_errno)
56 1.13 joerg {
57 1.13 joerg return __fork();
58 1.13 joerg }
59 1.1 nathanw
60 1.22 christos /*
61 1.22 christos * Keep a cache for of 3, one for prepare, one for parent, one for child.
62 1.22 christos * This is so that we don't have to allocate memory for the call from the
63 1.22 christos * pthread_tsd_init() constructor, where it is too early to call malloc(3).
64 1.22 christos */
65 1.22 christos static struct atfork_callback atfork_builtin[3];
66 1.22 christos
67 1.1 nathanw /*
68 1.1 nathanw * Hypothetically, we could protect the queues with a rwlock which is
69 1.1 nathanw * write-locked by pthread_atfork() and read-locked by fork(), but
70 1.1 nathanw * since the intended use of the functions is obtaining locks to hold
71 1.1 nathanw * across the fork, forking is going to be serialized anyway.
72 1.1 nathanw */
73 1.10 christos #ifdef _REENTRANT
74 1.1 nathanw static mutex_t atfork_lock = MUTEX_INITIALIZER;
75 1.10 christos #endif
76 1.1 nathanw SIMPLEQ_HEAD(atfork_callback_q, atfork_callback);
77 1.1 nathanw
78 1.2 nathanw static struct atfork_callback_q prepareq = SIMPLEQ_HEAD_INITIALIZER(prepareq);
79 1.2 nathanw static struct atfork_callback_q parentq = SIMPLEQ_HEAD_INITIALIZER(parentq);
80 1.2 nathanw static struct atfork_callback_q childq = SIMPLEQ_HEAD_INITIALIZER(childq);
81 1.1 nathanw
82 1.7 ad static struct atfork_callback *
83 1.7 ad af_alloc(void)
84 1.7 ad {
85 1.23 christos
86 1.22 christos for (size_t i = 0; i < __arraycount(atfork_builtin); i++) {
87 1.22 christos if (atfork_builtin[i].fn == NULL)
88 1.22 christos return &atfork_builtin[i];
89 1.22 christos }
90 1.7 ad
91 1.21 christos return malloc(sizeof(atfork_builtin));
92 1.7 ad }
93 1.7 ad
94 1.7 ad static void
95 1.7 ad af_free(struct atfork_callback *af)
96 1.7 ad {
97 1.23 christos
98 1.23 christos if (af >= atfork_builtin
99 1.23 christos && af < atfork_builtin + __arraycount(atfork_builtin))
100 1.23 christos af->fn = NULL;
101 1.23 christos else
102 1.23 christos free(af);
103 1.7 ad }
104 1.7 ad
105 1.24 riastrad static void
106 1.24 riastrad __libc_atfork_locked(
107 1.24 riastrad struct atfork_callback *restrict newprepare, void (*prepare)(void),
108 1.24 riastrad struct atfork_callback *restrict newparent, void (*parent)(void),
109 1.24 riastrad struct atfork_callback *restrict newchild, void (*child)(void))
110 1.24 riastrad {
111 1.24 riastrad
112 1.24 riastrad /*
113 1.24 riastrad * The order in which the functions are called is specified as
114 1.24 riastrad * LIFO for the prepare handler and FIFO for the others; insert
115 1.24 riastrad * at the head and tail as appropriate so that SIMPLEQ_FOREACH()
116 1.24 riastrad * produces the right order.
117 1.24 riastrad */
118 1.24 riastrad if (prepare) {
119 1.24 riastrad newprepare->fn = prepare;
120 1.24 riastrad SIMPLEQ_INSERT_HEAD(&prepareq, newprepare, next);
121 1.24 riastrad }
122 1.24 riastrad if (parent) {
123 1.24 riastrad newparent->fn = parent;
124 1.24 riastrad SIMPLEQ_INSERT_TAIL(&parentq, newparent, next);
125 1.24 riastrad }
126 1.24 riastrad if (child) {
127 1.24 riastrad newchild->fn = child;
128 1.24 riastrad SIMPLEQ_INSERT_TAIL(&childq, newchild, next);
129 1.24 riastrad }
130 1.24 riastrad }
131 1.24 riastrad
132 1.24 riastrad void
133 1.24 riastrad __libc_atfork(
134 1.24 riastrad struct atfork_callback *restrict newprepare, void (*prepare)(void),
135 1.24 riastrad struct atfork_callback *restrict newparent, void (*parent)(void),
136 1.24 riastrad struct atfork_callback *restrict newchild, void (*child)(void))
137 1.24 riastrad {
138 1.24 riastrad sigset_t mask, omask;
139 1.24 riastrad
140 1.24 riastrad sigfillset(&mask);
141 1.24 riastrad thr_sigsetmask(SIG_SETMASK, &mask, &omask);
142 1.24 riastrad
143 1.24 riastrad mutex_lock(&atfork_lock);
144 1.24 riastrad __libc_atfork_locked(newprepare, prepare,
145 1.24 riastrad newparent, parent,
146 1.24 riastrad newchild, child);
147 1.24 riastrad mutex_unlock(&atfork_lock);
148 1.24 riastrad
149 1.24 riastrad thr_sigsetmask(SIG_SETMASK, &omask, NULL);
150 1.24 riastrad }
151 1.24 riastrad
152 1.1 nathanw int
153 1.1 nathanw pthread_atfork(void (*prepare)(void), void (*parent)(void),
154 1.1 nathanw void (*child)(void))
155 1.1 nathanw {
156 1.1 nathanw struct atfork_callback *newprepare, *newparent, *newchild;
157 1.17 riastrad sigset_t mask, omask;
158 1.17 riastrad int error;
159 1.1 nathanw
160 1.4 lukem newprepare = newparent = newchild = NULL;
161 1.4 lukem
162 1.17 riastrad sigfillset(&mask);
163 1.17 riastrad thr_sigsetmask(SIG_SETMASK, &mask, &omask);
164 1.17 riastrad
165 1.7 ad mutex_lock(&atfork_lock);
166 1.1 nathanw if (prepare != NULL) {
167 1.7 ad newprepare = af_alloc();
168 1.7 ad if (newprepare == NULL) {
169 1.17 riastrad error = ENOMEM;
170 1.17 riastrad goto out;
171 1.7 ad }
172 1.1 nathanw }
173 1.1 nathanw
174 1.1 nathanw if (parent != NULL) {
175 1.7 ad newparent = af_alloc();
176 1.1 nathanw if (newparent == NULL) {
177 1.1 nathanw if (newprepare != NULL)
178 1.7 ad af_free(newprepare);
179 1.17 riastrad error = ENOMEM;
180 1.17 riastrad goto out;
181 1.1 nathanw }
182 1.1 nathanw }
183 1.1 nathanw
184 1.1 nathanw if (child != NULL) {
185 1.7 ad newchild = af_alloc();
186 1.1 nathanw if (newchild == NULL) {
187 1.1 nathanw if (newprepare != NULL)
188 1.7 ad af_free(newprepare);
189 1.1 nathanw if (newparent != NULL)
190 1.7 ad af_free(newparent);
191 1.17 riastrad error = ENOMEM;
192 1.17 riastrad goto out;
193 1.1 nathanw }
194 1.1 nathanw }
195 1.1 nathanw
196 1.24 riastrad __libc_atfork_locked(newprepare, prepare,
197 1.24 riastrad newparent, parent,
198 1.24 riastrad newchild, child);
199 1.17 riastrad error = 0;
200 1.1 nathanw
201 1.17 riastrad out: mutex_unlock(&atfork_lock);
202 1.17 riastrad thr_sigsetmask(SIG_SETMASK, &omask, NULL);
203 1.17 riastrad return error;
204 1.1 nathanw }
205 1.1 nathanw
206 1.3 lukem pid_t
207 1.3 lukem fork(void)
208 1.1 nathanw {
209 1.1 nathanw struct atfork_callback *iter;
210 1.1 nathanw pid_t ret;
211 1.1 nathanw
212 1.1 nathanw mutex_lock(&atfork_lock);
213 1.1 nathanw SIMPLEQ_FOREACH(iter, &prepareq, next)
214 1.6 yamt (*iter->fn)();
215 1.15 joerg _malloc_prefork();
216 1.1 nathanw
217 1.14 joerg ret = __locked_fork(&errno);
218 1.1 nathanw
219 1.1 nathanw if (ret != 0) {
220 1.1 nathanw /*
221 1.1 nathanw * We are the parent. It doesn't matter here whether
222 1.1 nathanw * the fork call succeeded or failed.
223 1.1 nathanw */
224 1.15 joerg _malloc_postfork();
225 1.1 nathanw SIMPLEQ_FOREACH(iter, &parentq, next)
226 1.6 yamt (*iter->fn)();
227 1.1 nathanw mutex_unlock(&atfork_lock);
228 1.1 nathanw } else {
229 1.1 nathanw /* We are the child */
230 1.15 joerg _malloc_postfork_child();
231 1.1 nathanw SIMPLEQ_FOREACH(iter, &childq, next)
232 1.6 yamt (*iter->fn)();
233 1.1 nathanw /*
234 1.1 nathanw * Note: We are explicitly *not* unlocking
235 1.1 nathanw * atfork_lock. Unlocking atfork_lock is problematic,
236 1.1 nathanw * because if any threads in the parent blocked on it
237 1.1 nathanw * between the initial lock and the fork() syscall,
238 1.1 nathanw * unlocking in the child will try to schedule
239 1.1 nathanw * threads, and either the internal mutex interlock or
240 1.1 nathanw * the runqueue spinlock could have been held at the
241 1.1 nathanw * moment of fork(). Since the other threads do not
242 1.1 nathanw * exist in this process, the spinlock will never be
243 1.1 nathanw * unlocked, and we would wedge.
244 1.1 nathanw * Instead, we reinitialize atfork_lock, since we know
245 1.1 nathanw * that the state of the atfork lists is consistent here,
246 1.1 nathanw * and that there are no other threads to be affected by
247 1.1 nathanw * the forcible cleaning of the queue.
248 1.1 nathanw * This permits double-forking to work, although
249 1.1 nathanw * it requires knowing that it's "safe" to initialize
250 1.1 nathanw * a locked mutex in this context.
251 1.1 nathanw *
252 1.1 nathanw * The problem exists for users of this interface,
253 1.16 andvar * too, since the intended use of pthread_atfork() is
254 1.1 nathanw * to acquire locks across the fork call to ensure
255 1.1 nathanw * that the child sees consistent state. There's not
256 1.1 nathanw * much that can usefully be done in a child handler,
257 1.1 nathanw * and conventional wisdom discourages using them, but
258 1.1 nathanw * they're part of the interface, so here we are...
259 1.1 nathanw */
260 1.1 nathanw mutex_init(&atfork_lock, NULL);
261 1.1 nathanw }
262 1.1 nathanw
263 1.1 nathanw return ret;
264 1.1 nathanw }
265