Home | History | Annotate | Line # | Download | only in gen
      1 /*	$NetBSD: pthread_atfork.c,v 1.28 2025/05/06 23:18:27 riastradh Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2002 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Nathan J. Williams.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #if defined(LIBC_SCCS) && !defined(lint)
     34 __RCSID("$NetBSD: pthread_atfork.c,v 1.28 2025/05/06 23:18:27 riastradh Exp $");
     35 #endif /* LIBC_SCCS and not lint */
     36 
     37 #include "namespace.h"
     38 
     39 #include <errno.h>
     40 #include <stdlib.h>
     41 #include <unistd.h>
     42 
     43 #include <sys/mman.h>
     44 #include <sys/param.h>
     45 #include <sys/queue.h>
     46 #include <sys/sysctl.h>
     47 
     48 #include "extern.h"
     49 #include "reentrant.h"
     50 
     51 #ifdef __weak_alias
     52 __weak_alias(pthread_atfork, _pthread_atfork)
     53 __weak_alias(fork, _fork)
     54 #endif /* __weak_alias */
     55 
     56 pid_t
     57 __locked_fork(int *my_errno)
     58 {
     59 	return __fork();
     60 }
     61 
     62 struct atfork_callback {
     63 	SIMPLEQ_ENTRY(atfork_callback) next;
     64 	void (*fn)(void);
     65 };
     66 
     67 struct atfork_cb_header {
     68 	uint16_t	entries;
     69 	uint16_t	used;
     70 };
     71 
     72 struct atfork_cb_block {
     73 	union {
     74 		struct atfork_callback block;
     75 		struct atfork_cb_header hdr;
     76 	} u;
     77 };
     78 
     79 #define	cb_blocks(bp)	(&(bp)->u.block)
     80 #define	cb_ents(bp)	(bp)->u.hdr.entries
     81 #define	cb_used(bp)	(bp)->u.hdr.used
     82 
     83 /*
     84  * We need to keep a cache for of at least 6, one for prepare, one for parent,
     85  * one for child x 2 bexause of the two uses in the libpthread (pthread_init,
     86  * pthread_tsd_init) constructors, where it is too early to call malloc(3).
     87  * This does not guarantee that we will have enough, because other libraries
     88  * can also call pthread_atfork() from their own constructors, so this is not
     89  * a complete solution and will need to be fixed properly. For now a keep
     90  * space for 16 since it is just 256 bytes.
     91  */
     92 static struct atfork_callback atfork_builtin[16];
     93 static struct atfork_cb_block *atfork_storage = NULL;
     94 static int hw_pagesize = 0;
     95 
     96 static const int hw_pagesize_sysctl[2] = { CTL_HW, HW_PAGESIZE };
     97 
     98 /*
     99  * Hypothetically, we could protect the queues with a rwlock which is
    100  * write-locked by pthread_atfork() and read-locked by fork(), but
    101  * since the intended use of the functions is obtaining locks to hold
    102  * across the fork, forking is going to be serialized anyway.
    103  */
    104 #ifdef _REENTRANT
    105 static mutex_t atfork_lock = MUTEX_INITIALIZER;
    106 #endif
    107 SIMPLEQ_HEAD(atfork_callback_q, atfork_callback);
    108 
    109 static struct atfork_callback_q prepareq = SIMPLEQ_HEAD_INITIALIZER(prepareq);
    110 static struct atfork_callback_q parentq = SIMPLEQ_HEAD_INITIALIZER(parentq);
    111 static struct atfork_callback_q childq = SIMPLEQ_HEAD_INITIALIZER(childq);
    112 
    113 /*
    114  * Nb: nothing allocated by this allocator is ever freed.
    115  * (there is no API to free anything, and no need for one)
    116  *
    117  * The code relies upon this.
    118  */
    119 static struct atfork_callback *
    120 af_alloc(unsigned int blocks)
    121 {
    122 	struct atfork_callback *result;
    123 
    124 	if (__predict_false(blocks == 0))
    125 		return NULL;
    126 
    127 	if (__predict_true(atfork_storage == NULL)) {
    128 		for (size_t i = 0; i < __arraycount(atfork_builtin); i++) {
    129 			if (atfork_builtin[i].fn == NULL) {
    130 				if (i + blocks <= __arraycount(atfork_builtin))
    131 					return &atfork_builtin[i];
    132 				else
    133 					break;
    134 			}
    135 		}
    136 	}
    137 
    138 	if (__predict_false(atfork_storage == NULL ||
    139 	    cb_used(atfork_storage) + blocks > cb_ents(atfork_storage))) {
    140 		if (__predict_false(hw_pagesize == 0)) {
    141 			size_t len = sizeof(hw_pagesize);
    142 
    143 			if (sysctl(hw_pagesize_sysctl, 2, &hw_pagesize,
    144 			    &len, NULL, 0) != 0)
    145 				return NULL;
    146 			if (len != sizeof(hw_pagesize))
    147 				return NULL;
    148 			if (hw_pagesize == 0 || (hw_pagesize & 0xFF) != 0)
    149 				return NULL;
    150 		}
    151 		atfork_storage = mmap(0, hw_pagesize, PROT_READ|PROT_WRITE,
    152 		    MAP_PRIVATE | MAP_ANON, -1, 0);
    153 		if (__predict_false(atfork_storage == NULL))
    154 			return NULL;
    155 		cb_used(atfork_storage) = 1;
    156 		cb_ents(atfork_storage) =
    157 		    (uint16_t)(hw_pagesize / sizeof(struct atfork_cb_block));
    158 		if (__predict_false(cb_ents(atfork_storage) < blocks + 1))
    159 			return NULL;
    160 	}
    161 
    162 	result = cb_blocks(atfork_storage) + cb_used(atfork_storage);
    163 	cb_used(atfork_storage) += blocks;
    164 
    165 	return result;
    166 }
    167 
    168 int
    169 pthread_atfork(void (*prepare)(void), void (*parent)(void),
    170     void (*child)(void))
    171 {
    172 	struct atfork_callback *newprepare, *newparent, *newchild;
    173 	sigset_t mask, omask;
    174 	int error;
    175 
    176 	sigfillset(&mask);
    177 #ifdef _REENTRANT	/* XXX PR lib/59401 */
    178 	thr_sigsetmask(SIG_SETMASK, &mask, &omask);
    179 #else
    180 	sigprocmask(SIG_SETMASK, &mask, &omask);
    181 #endif
    182 
    183 	mutex_lock(&atfork_lock);
    184 
    185 	/*
    186 	 * Note here that we either get all the blocks
    187 	 * we need, in one call, or we get NULL.
    188 	 *
    189 	 * Note also that a NULL return is not an error
    190 	 * if no blocks were required (all args == NULL)
    191 	 */
    192 	newprepare = af_alloc((prepare != NULL) +
    193 	    (parent != NULL) + (child != NULL));
    194 
    195 	error = ENOMEM;		/* in case of "goto out" */
    196 
    197 	newparent = newprepare;
    198 	if (prepare != NULL) {
    199 		if (__predict_false(newprepare == NULL))
    200 			goto out;
    201 		newprepare->fn = prepare;
    202 		newparent++;
    203 	}
    204 
    205 	newchild = newparent;
    206 	if (parent != NULL) {
    207 		if (__predict_false(newparent == NULL))
    208 			goto out;
    209 		newparent->fn = parent;
    210 		newchild++;
    211 	}
    212 
    213 	if (child != NULL) {
    214 		if (__predict_false(newchild == NULL))
    215 			goto out;
    216 		newchild->fn = child;
    217 	}
    218 
    219 	/*
    220 	 * The order in which the functions are called is specified as
    221 	 * LIFO for the prepare handler and FIFO for the others; insert
    222 	 * at the head and tail as appropriate so that SIMPLEQ_FOREACH()
    223 	 * produces the right order.
    224 	 */
    225 	if (prepare)
    226 		SIMPLEQ_INSERT_HEAD(&prepareq, newprepare, next);
    227 	if (parent)
    228 		SIMPLEQ_INSERT_TAIL(&parentq, newparent, next);
    229 	if (child)
    230 		SIMPLEQ_INSERT_TAIL(&childq, newchild, next);
    231 
    232 	error = 0;
    233 
    234  out:;
    235 	mutex_unlock(&atfork_lock);
    236 #ifdef _REENTRANT	/* XXX PR lib/59401 */
    237 	thr_sigsetmask(SIG_SETMASK, &omask, NULL);
    238 #else
    239 	sigprocmask(SIG_SETMASK, &omask, NULL);
    240 #endif
    241 	return error;
    242 }
    243 
    244 pid_t
    245 fork(void)
    246 {
    247 	struct atfork_callback *iter;
    248 	pid_t ret;
    249 
    250 	mutex_lock(&atfork_lock);
    251 	SIMPLEQ_FOREACH(iter, &prepareq, next)
    252 		(*iter->fn)();
    253 	_malloc_prefork();
    254 
    255 	ret = __locked_fork(&errno);
    256 
    257 	if (ret != 0) {
    258 		/*
    259 		 * We are the parent. It doesn't matter here whether
    260 		 * the fork call succeeded or failed.
    261 		 */
    262 		_malloc_postfork();
    263 		SIMPLEQ_FOREACH(iter, &parentq, next)
    264 			(*iter->fn)();
    265 		mutex_unlock(&atfork_lock);
    266 	} else {
    267 		/* We are the child */
    268 		_malloc_postfork_child();
    269 		SIMPLEQ_FOREACH(iter, &childq, next)
    270 			(*iter->fn)();
    271 		/*
    272 		 * Note: We are explicitly *not* unlocking
    273 		 * atfork_lock.  Unlocking atfork_lock is problematic,
    274 		 * because if any threads in the parent blocked on it
    275 		 * between the initial lock and the fork() syscall,
    276 		 * unlocking in the child will try to schedule
    277 		 * threads, and either the internal mutex interlock or
    278 		 * the runqueue spinlock could have been held at the
    279 		 * moment of fork(). Since the other threads do not
    280 		 * exist in this process, the spinlock will never be
    281 		 * unlocked, and we would wedge.
    282 		 * Instead, we reinitialize atfork_lock, since we know
    283 		 * that the state of the atfork lists is consistent here,
    284 		 * and that there are no other threads to be affected by
    285 		 * the forcible cleaning of the queue.
    286 		 * This permits double-forking to work, although
    287 		 * it requires knowing that it's "safe" to initialize
    288 		 * a locked mutex in this context.
    289 		 *
    290 		 * The problem exists for users of this interface,
    291 		 * too, since the intended use of pthread_atfork() is
    292 		 * to acquire locks across the fork call to ensure
    293 		 * that the child sees consistent state. There's not
    294 		 * much that can usefully be done in a child handler,
    295 		 * and conventional wisdom discourages using them, but
    296 		 * they're part of the interface, so here we are...
    297 		 */
    298 		mutex_init(&atfork_lock, NULL);
    299 	}
    300 
    301 	return ret;
    302 }
    303