root/lib/libc/gen/pthread_atfork.c
/*      $NetBSD: pthread_atfork.c,v 1.28 2025/05/06 23:18:27 riastradh Exp $    */

/*-
 * Copyright (c) 2002 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Nathan J. Williams.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#if defined(LIBC_SCCS) && !defined(lint)
__RCSID("$NetBSD: pthread_atfork.c,v 1.28 2025/05/06 23:18:27 riastradh Exp $");
#endif /* LIBC_SCCS and not lint */

#include "namespace.h"

#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/mman.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/sysctl.h>

#include "extern.h"
#include "reentrant.h"

#ifdef __weak_alias
__weak_alias(pthread_atfork, _pthread_atfork)
__weak_alias(fork, _fork)
#endif /* __weak_alias */

pid_t
__locked_fork(int *my_errno)
{
        return __fork();
}

struct atfork_callback {
        SIMPLEQ_ENTRY(atfork_callback) next;
        void (*fn)(void);
};

struct atfork_cb_header {
        uint16_t        entries;
        uint16_t        used;
};

struct atfork_cb_block {
        union {
                struct atfork_callback block;
                struct atfork_cb_header hdr;
        } u;
};

#define cb_blocks(bp)   (&(bp)->u.block)
#define cb_ents(bp)     (bp)->u.hdr.entries
#define cb_used(bp)     (bp)->u.hdr.used

/*
 * We need to keep a cache for of at least 6, one for prepare, one for parent,
 * one for child x 2 bexause of the two uses in the libpthread (pthread_init,
 * pthread_tsd_init) constructors, where it is too early to call malloc(3).
 * This does not guarantee that we will have enough, because other libraries
 * can also call pthread_atfork() from their own constructors, so this is not
 * a complete solution and will need to be fixed properly. For now a keep
 * space for 16 since it is just 256 bytes.
 */
static struct atfork_callback atfork_builtin[16];
static struct atfork_cb_block *atfork_storage = NULL;
static int hw_pagesize = 0;

static const int hw_pagesize_sysctl[2] = { CTL_HW, HW_PAGESIZE };

/*
 * Hypothetically, we could protect the queues with a rwlock which is
 * write-locked by pthread_atfork() and read-locked by fork(), but
 * since the intended use of the functions is obtaining locks to hold
 * across the fork, forking is going to be serialized anyway.
 */
#ifdef _REENTRANT
static mutex_t atfork_lock = MUTEX_INITIALIZER;
#endif
SIMPLEQ_HEAD(atfork_callback_q, atfork_callback);

static struct atfork_callback_q prepareq = SIMPLEQ_HEAD_INITIALIZER(prepareq);
static struct atfork_callback_q parentq = SIMPLEQ_HEAD_INITIALIZER(parentq);
static struct atfork_callback_q childq = SIMPLEQ_HEAD_INITIALIZER(childq);

/*
 * Nb: nothing allocated by this allocator is ever freed.
 * (there is no API to free anything, and no need for one)
 *
 * The code relies upon this.
 */
static struct atfork_callback *
af_alloc(unsigned int blocks)
{
        struct atfork_callback *result;

        if (__predict_false(blocks == 0))
                return NULL;

        if (__predict_true(atfork_storage == NULL)) {
                for (size_t i = 0; i < __arraycount(atfork_builtin); i++) {
                        if (atfork_builtin[i].fn == NULL) {
                                if (i + blocks <= __arraycount(atfork_builtin))
                                        return &atfork_builtin[i];
                                else
                                        break;
                        }
                }
        }

        if (__predict_false(atfork_storage == NULL ||
            cb_used(atfork_storage) + blocks > cb_ents(atfork_storage))) {
                if (__predict_false(hw_pagesize == 0)) {
                        size_t len = sizeof(hw_pagesize);

                        if (sysctl(hw_pagesize_sysctl, 2, &hw_pagesize,
                            &len, NULL, 0) != 0)
                                return NULL;
                        if (len != sizeof(hw_pagesize))
                                return NULL;
                        if (hw_pagesize == 0 || (hw_pagesize & 0xFF) != 0)
                                return NULL;
                }
                atfork_storage = mmap(0, hw_pagesize, PROT_READ|PROT_WRITE,
                    MAP_PRIVATE | MAP_ANON, -1, 0);
                if (__predict_false(atfork_storage == NULL))
                        return NULL;
                cb_used(atfork_storage) = 1;
                cb_ents(atfork_storage) =
                    (uint16_t)(hw_pagesize / sizeof(struct atfork_cb_block));
                if (__predict_false(cb_ents(atfork_storage) < blocks + 1))
                        return NULL;
        }

        result = cb_blocks(atfork_storage) + cb_used(atfork_storage);
        cb_used(atfork_storage) += blocks;

        return result;
}

int
pthread_atfork(void (*prepare)(void), void (*parent)(void),
    void (*child)(void))
{
        struct atfork_callback *newprepare, *newparent, *newchild;
        sigset_t mask, omask;
        int error;

        sigfillset(&mask);
#ifdef _REENTRANT       /* XXX PR lib/59401 */
        thr_sigsetmask(SIG_SETMASK, &mask, &omask);
#else
        sigprocmask(SIG_SETMASK, &mask, &omask);
#endif

        mutex_lock(&atfork_lock);

        /*
         * Note here that we either get all the blocks
         * we need, in one call, or we get NULL.
         *
         * Note also that a NULL return is not an error
         * if no blocks were required (all args == NULL)
         */
        newprepare = af_alloc((prepare != NULL) +
            (parent != NULL) + (child != NULL));

        error = ENOMEM;         /* in case of "goto out" */

        newparent = newprepare;
        if (prepare != NULL) {
                if (__predict_false(newprepare == NULL))
                        goto out;
                newprepare->fn = prepare;
                newparent++;
        }

        newchild = newparent;
        if (parent != NULL) {
                if (__predict_false(newparent == NULL))
                        goto out;
                newparent->fn = parent;
                newchild++;
        }

        if (child != NULL) {
                if (__predict_false(newchild == NULL))
                        goto out;
                newchild->fn = child;
        }

        /*
         * The order in which the functions are called is specified as
         * LIFO for the prepare handler and FIFO for the others; insert
         * at the head and tail as appropriate so that SIMPLEQ_FOREACH()
         * produces the right order.
         */
        if (prepare)
                SIMPLEQ_INSERT_HEAD(&prepareq, newprepare, next);
        if (parent)
                SIMPLEQ_INSERT_TAIL(&parentq, newparent, next);
        if (child)
                SIMPLEQ_INSERT_TAIL(&childq, newchild, next);

        error = 0;

 out:;
        mutex_unlock(&atfork_lock);
#ifdef _REENTRANT       /* XXX PR lib/59401 */
        thr_sigsetmask(SIG_SETMASK, &omask, NULL);
#else
        sigprocmask(SIG_SETMASK, &omask, NULL);
#endif
        return error;
}

pid_t
fork(void)
{
        struct atfork_callback *iter;
        pid_t ret;

        mutex_lock(&atfork_lock);
        SIMPLEQ_FOREACH(iter, &prepareq, next)
                (*iter->fn)();
        _malloc_prefork();

        ret = __locked_fork(&errno);

        if (ret != 0) {
                /*
                 * We are the parent. It doesn't matter here whether
                 * the fork call succeeded or failed.
                 */
                _malloc_postfork();
                SIMPLEQ_FOREACH(iter, &parentq, next)
                        (*iter->fn)();
                mutex_unlock(&atfork_lock);
        } else {
                /* We are the child */
                _malloc_postfork_child();
                SIMPLEQ_FOREACH(iter, &childq, next)
                        (*iter->fn)();
                /*
                 * Note: We are explicitly *not* unlocking
                 * atfork_lock.  Unlocking atfork_lock is problematic,
                 * because if any threads in the parent blocked on it
                 * between the initial lock and the fork() syscall,
                 * unlocking in the child will try to schedule
                 * threads, and either the internal mutex interlock or
                 * the runqueue spinlock could have been held at the
                 * moment of fork(). Since the other threads do not
                 * exist in this process, the spinlock will never be
                 * unlocked, and we would wedge.
                 * Instead, we reinitialize atfork_lock, since we know
                 * that the state of the atfork lists is consistent here,
                 * and that there are no other threads to be affected by
                 * the forcible cleaning of the queue.
                 * This permits double-forking to work, although
                 * it requires knowing that it's "safe" to initialize
                 * a locked mutex in this context.
                 *
                 * The problem exists for users of this interface,
                 * too, since the intended use of pthread_atfork() is
                 * to acquire locks across the fork call to ensure
                 * that the child sees consistent state. There's not
                 * much that can usefully be done in a child handler,
                 * and conventional wisdom discourages using them, but
                 * they're part of the interface, so here we are...
                 */
                mutex_init(&atfork_lock, NULL);
        }

        return ret;
}