17e6ac503SKyle Evans /*-
27e6ac503SKyle Evans *
37e6ac503SKyle Evans * Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org>
47e6ac503SKyle Evans *
57e6ac503SKyle Evans * SPDX-License-Identifier: BSD-2-Clause
67e6ac503SKyle Evans *
77e6ac503SKyle Evans */
87e6ac503SKyle Evans
97e6ac503SKyle Evans #include <sys/wait.h>
107e6ac503SKyle Evans #include <errno.h>
117e6ac503SKyle Evans #include <pthread.h>
127e6ac503SKyle Evans #include <signal.h>
137e6ac503SKyle Evans #include <stdbool.h>
147e6ac503SKyle Evans #include <stdio.h>
157e6ac503SKyle Evans #include <unistd.h>
167e6ac503SKyle Evans
177e6ac503SKyle Evans #include <atf-c.h>
187e6ac503SKyle Evans
197e6ac503SKyle Evans #define EXIT_NOPREPARE 1
207e6ac503SKyle Evans #define EXIT_CALLEDPARENT 2
217e6ac503SKyle Evans #define EXIT_NOCHILD 3
227e6ac503SKyle Evans #define EXIT_BADORDER 4
237e6ac503SKyle Evans
247e6ac503SKyle Evans static int child;
257e6ac503SKyle Evans static int forked;
267e6ac503SKyle Evans static int parent;
277e6ac503SKyle Evans
28*4b202f4fSKyle Evans /*
29*4b202f4fSKyle Evans * We'll disable prefork unless we're specifically running the preinit test to
30*4b202f4fSKyle Evans * be sure that we don't mess up any other tests' results.
31*4b202f4fSKyle Evans */
32*4b202f4fSKyle Evans static bool prefork_enabled;
33*4b202f4fSKyle Evans
34*4b202f4fSKyle Evans static void
prefork(void)35*4b202f4fSKyle Evans prefork(void)
36*4b202f4fSKyle Evans {
37*4b202f4fSKyle Evans if (prefork_enabled)
38*4b202f4fSKyle Evans forked++;
39*4b202f4fSKyle Evans }
40*4b202f4fSKyle Evans
41*4b202f4fSKyle Evans static void
registrar(void)42*4b202f4fSKyle Evans registrar(void)
43*4b202f4fSKyle Evans {
44*4b202f4fSKyle Evans pthread_atfork(prefork, NULL, NULL);
45*4b202f4fSKyle Evans }
46*4b202f4fSKyle Evans
47*4b202f4fSKyle Evans static __attribute__((section(".preinit_array"), used))
48*4b202f4fSKyle Evans void (*preinitfn)(void) = ®istrar;
49*4b202f4fSKyle Evans
50*4b202f4fSKyle Evans /*
51*4b202f4fSKyle Evans * preinit_atfork() just enables the prepare handler that we registered in a
52*4b202f4fSKyle Evans * .preinit_array entry and checks that forking actually invoked that callback.
53*4b202f4fSKyle Evans * We don't bother testing all three callbacks here because the implementation
54*4b202f4fSKyle Evans * doesn't really lend itself to the kind of error where we only have a partial
55*4b202f4fSKyle Evans * set of callbacks registered.
56*4b202f4fSKyle Evans */
57*4b202f4fSKyle Evans ATF_TC(preinit_atfork);
ATF_TC_HEAD(preinit_atfork,tc)58*4b202f4fSKyle Evans ATF_TC_HEAD(preinit_atfork, tc)
59*4b202f4fSKyle Evans {
60*4b202f4fSKyle Evans atf_tc_set_md_var(tc, "descr",
61*4b202f4fSKyle Evans "Checks that atfork callbacks may be registered in .preinit_array functions");
62*4b202f4fSKyle Evans }
ATF_TC_BODY(preinit_atfork,tc)63*4b202f4fSKyle Evans ATF_TC_BODY(preinit_atfork, tc)
64*4b202f4fSKyle Evans {
65*4b202f4fSKyle Evans pid_t p;
66*4b202f4fSKyle Evans
67*4b202f4fSKyle Evans (void)signal(SIGCHLD, SIG_IGN);
68*4b202f4fSKyle Evans prefork_enabled = true;
69*4b202f4fSKyle Evans p = fork();
70*4b202f4fSKyle Evans
71*4b202f4fSKyle Evans ATF_REQUIRE(p >= 0);
72*4b202f4fSKyle Evans if (p == 0)
73*4b202f4fSKyle Evans _exit(0);
74*4b202f4fSKyle Evans
75*4b202f4fSKyle Evans prefork_enabled = false;
76*4b202f4fSKyle Evans
77*4b202f4fSKyle Evans ATF_REQUIRE(forked != 0);
78*4b202f4fSKyle Evans }
79*4b202f4fSKyle Evans
807e6ac503SKyle Evans static void
basic_prepare(void)817e6ac503SKyle Evans basic_prepare(void)
827e6ac503SKyle Evans {
837e6ac503SKyle Evans ATF_REQUIRE(parent == 0);
847e6ac503SKyle Evans forked++;
857e6ac503SKyle Evans }
867e6ac503SKyle Evans
877e6ac503SKyle Evans static void
basic_parent(void)887e6ac503SKyle Evans basic_parent(void)
897e6ac503SKyle Evans {
907e6ac503SKyle Evans ATF_REQUIRE(forked != 0);
917e6ac503SKyle Evans parent++;
927e6ac503SKyle Evans }
937e6ac503SKyle Evans
947e6ac503SKyle Evans static void
basic_child(void)957e6ac503SKyle Evans basic_child(void)
967e6ac503SKyle Evans {
977e6ac503SKyle Evans if (!forked)
987e6ac503SKyle Evans _exit(EXIT_NOPREPARE);
997e6ac503SKyle Evans if (parent != 0)
1007e6ac503SKyle Evans _exit(EXIT_CALLEDPARENT);
1017e6ac503SKyle Evans child++;
1027e6ac503SKyle Evans }
1037e6ac503SKyle Evans
1047e6ac503SKyle Evans /*
1057e6ac503SKyle Evans * In the basic test, we'll register just once and set some globals to confirm
1067e6ac503SKyle Evans * that the prepare/parent callbacks were executed as expected. The child will
1077e6ac503SKyle Evans * use its exit status to communicate to us if the callback was not executed
1087e6ac503SKyle Evans * properly since we cannot assert there. This is a subset of the
1097e6ac503SKyle Evans * multi-callback test, but separated out so that it's more obvious from running
1107e6ac503SKyle Evans * the atfork_test if pthread_atfork() is completely broken or just
1117e6ac503SKyle Evans * out-of-order.
1127e6ac503SKyle Evans */
1137e6ac503SKyle Evans ATF_TC(basic_atfork);
ATF_TC_HEAD(basic_atfork,tc)1147e6ac503SKyle Evans ATF_TC_HEAD(basic_atfork, tc)
1157e6ac503SKyle Evans {
1167e6ac503SKyle Evans atf_tc_set_md_var(tc, "descr",
1177e6ac503SKyle Evans "Checks invocation of all three atfork callbacks");
1187e6ac503SKyle Evans }
ATF_TC_BODY(basic_atfork,tc)1197e6ac503SKyle Evans ATF_TC_BODY(basic_atfork, tc)
1207e6ac503SKyle Evans {
1217e6ac503SKyle Evans pid_t p, wpid;
1227e6ac503SKyle Evans int status;
1237e6ac503SKyle Evans
1247e6ac503SKyle Evans pthread_atfork(basic_prepare, basic_parent, basic_child);
1257e6ac503SKyle Evans
1267e6ac503SKyle Evans p = fork();
1277e6ac503SKyle Evans
1287e6ac503SKyle Evans ATF_REQUIRE(p >= 0);
1297e6ac503SKyle Evans if (p == 0)
1307e6ac503SKyle Evans _exit(child != 0 ? 0 : EXIT_NOCHILD);
1317e6ac503SKyle Evans
1327e6ac503SKyle Evans /*
1337e6ac503SKyle Evans * The child can't use any of our standard atf-c(3) macros, so we have
1347e6ac503SKyle Evans * to rely on the exit status to convey any shenanigans.
1357e6ac503SKyle Evans */
1367e6ac503SKyle Evans while ((wpid = waitpid(p, &status, 0)) != p) {
1377e6ac503SKyle Evans ATF_REQUIRE_ERRNO(EINTR, wpid == -1);
1387e6ac503SKyle Evans if (wpid == -1)
1397e6ac503SKyle Evans continue;
1407e6ac503SKyle Evans }
1417e6ac503SKyle Evans
1427e6ac503SKyle Evans ATF_REQUIRE_MSG(WIFEXITED(status),
1437e6ac503SKyle Evans "child did not exit cleanly, status %x", status);
1447e6ac503SKyle Evans
1457e6ac503SKyle Evans status = WEXITSTATUS(status);
1467e6ac503SKyle Evans ATF_REQUIRE_MSG(status == 0, "atfork in child %s",
1477e6ac503SKyle Evans status == EXIT_NOPREPARE ? "did not see `prepare` execute" :
1487e6ac503SKyle Evans (status == EXIT_CALLEDPARENT ? "observed `parent` executing" :
1497e6ac503SKyle Evans (status == EXIT_NOCHILD ? "did not see `child` execute" :
1507e6ac503SKyle Evans "mystery")));
1517e6ac503SKyle Evans
1527e6ac503SKyle Evans ATF_REQUIRE(forked != 0);
1537e6ac503SKyle Evans ATF_REQUIRE(parent != 0);
1547e6ac503SKyle Evans ATF_REQUIRE(child == 0);
1557e6ac503SKyle Evans }
1567e6ac503SKyle Evans
1577e6ac503SKyle Evans static void
multi_assert(bool cond,bool can_assert)1587e6ac503SKyle Evans multi_assert(bool cond, bool can_assert)
1597e6ac503SKyle Evans {
1607e6ac503SKyle Evans if (can_assert)
1617e6ac503SKyle Evans ATF_REQUIRE((cond));
1627e6ac503SKyle Evans else if (!(cond))
1637e6ac503SKyle Evans _exit(EXIT_BADORDER);
1647e6ac503SKyle Evans }
1657e6ac503SKyle Evans
1667e6ac503SKyle Evans static void
multi_bump(int * var,int bit,bool can_assert)1677e6ac503SKyle Evans multi_bump(int *var, int bit, bool can_assert)
1687e6ac503SKyle Evans {
1697e6ac503SKyle Evans int mask, val;
1707e6ac503SKyle Evans
1717e6ac503SKyle Evans mask = (1 << (bit - 1));
1727e6ac503SKyle Evans val = *var;
1737e6ac503SKyle Evans
1747e6ac503SKyle Evans /*
1757e6ac503SKyle Evans * Every bit below this one must be set, and none of the upper bits
1767e6ac503SKyle Evans * should be set.
1777e6ac503SKyle Evans */
1787e6ac503SKyle Evans multi_assert((val & mask) == 0, can_assert);
1797e6ac503SKyle Evans if (bit == 1)
1807e6ac503SKyle Evans multi_assert(val == 0, can_assert);
1817e6ac503SKyle Evans else
1827e6ac503SKyle Evans multi_assert((val & ~mask) == (mask - 1), can_assert);
1837e6ac503SKyle Evans
1847e6ac503SKyle Evans *var |= mask;
1857e6ac503SKyle Evans }
1867e6ac503SKyle Evans
1877e6ac503SKyle Evans static void
multi_prepare1(void)1887e6ac503SKyle Evans multi_prepare1(void)
1897e6ac503SKyle Evans {
1907e6ac503SKyle Evans /*
1917e6ac503SKyle Evans * The bits are flipped for prepare because it's supposed to be called
1927e6ac503SKyle Evans * in the reverse order of registration.
1937e6ac503SKyle Evans */
1947e6ac503SKyle Evans multi_bump(&forked, 2, true);
1957e6ac503SKyle Evans }
1967e6ac503SKyle Evans static void
multi_prepare2(void)1977e6ac503SKyle Evans multi_prepare2(void)
1987e6ac503SKyle Evans {
1997e6ac503SKyle Evans multi_bump(&forked, 1, true);
2007e6ac503SKyle Evans }
2017e6ac503SKyle Evans
2027e6ac503SKyle Evans static void
multi_parent1(void)2037e6ac503SKyle Evans multi_parent1(void)
2047e6ac503SKyle Evans {
2057e6ac503SKyle Evans multi_bump(&parent, 1, true);
2067e6ac503SKyle Evans }
2077e6ac503SKyle Evans static void
multi_parent2(void)2087e6ac503SKyle Evans multi_parent2(void)
2097e6ac503SKyle Evans {
2107e6ac503SKyle Evans multi_bump(&parent, 2, true);
2117e6ac503SKyle Evans }
2127e6ac503SKyle Evans
2137e6ac503SKyle Evans static void
multi_child1(void)2147e6ac503SKyle Evans multi_child1(void)
2157e6ac503SKyle Evans {
2167e6ac503SKyle Evans multi_bump(&child, 1, false);
2177e6ac503SKyle Evans }
2187e6ac503SKyle Evans static void
multi_child2(void)2197e6ac503SKyle Evans multi_child2(void)
2207e6ac503SKyle Evans {
2217e6ac503SKyle Evans multi_bump(&child, 2, false);
2227e6ac503SKyle Evans }
2237e6ac503SKyle Evans
2247e6ac503SKyle Evans /*
2257e6ac503SKyle Evans * The multi-atfork test works much like the basic one, but it registers
2267e6ac503SKyle Evans * multiple times and enforces an order. The child still does just as strict
2277e6ac503SKyle Evans * of tests as the parent and continues to communicate the results of those
2287e6ac503SKyle Evans * tests back via its exit status.
2297e6ac503SKyle Evans */
2307e6ac503SKyle Evans ATF_TC(multi_atfork);
ATF_TC_HEAD(multi_atfork,tc)2317e6ac503SKyle Evans ATF_TC_HEAD(multi_atfork, tc)
2327e6ac503SKyle Evans {
2337e6ac503SKyle Evans atf_tc_set_md_var(tc, "descr",
2347e6ac503SKyle Evans "Checks that multiple callbacks are called in the documented order");
2357e6ac503SKyle Evans }
ATF_TC_BODY(multi_atfork,tc)2367e6ac503SKyle Evans ATF_TC_BODY(multi_atfork, tc)
2377e6ac503SKyle Evans {
2387e6ac503SKyle Evans pid_t p, wpid;
2397e6ac503SKyle Evans int status;
2407e6ac503SKyle Evans
2417e6ac503SKyle Evans pthread_atfork(multi_prepare1, multi_parent1, multi_child1);
2427e6ac503SKyle Evans pthread_atfork(multi_prepare2, multi_parent2, multi_child2);
2437e6ac503SKyle Evans
2447e6ac503SKyle Evans p = fork();
2457e6ac503SKyle Evans
2467e6ac503SKyle Evans ATF_REQUIRE(p >= 0);
2477e6ac503SKyle Evans if (p == 0)
2487e6ac503SKyle Evans _exit(child != 0 ? 0 : EXIT_NOCHILD);
2497e6ac503SKyle Evans
2507e6ac503SKyle Evans /*
2517e6ac503SKyle Evans * The child can't use any of our standard atf-c(3) macros, so we have
2527e6ac503SKyle Evans * to rely on the exit status to convey any shenanigans.
2537e6ac503SKyle Evans */
2547e6ac503SKyle Evans while ((wpid = waitpid(p, &status, 0)) != p) {
2557e6ac503SKyle Evans ATF_REQUIRE_ERRNO(EINTR, wpid == -1);
2567e6ac503SKyle Evans if (wpid == -1)
2577e6ac503SKyle Evans continue;
2587e6ac503SKyle Evans }
2597e6ac503SKyle Evans
2607e6ac503SKyle Evans ATF_REQUIRE_MSG(WIFEXITED(status),
2617e6ac503SKyle Evans "child did not exit cleanly, status %x", status);
2627e6ac503SKyle Evans
2637e6ac503SKyle Evans status = WEXITSTATUS(status);
2647e6ac503SKyle Evans ATF_REQUIRE_MSG(status == 0, "atfork in child %s",
2657e6ac503SKyle Evans status == EXIT_BADORDER ? "called in wrong order" :
2667e6ac503SKyle Evans (status == EXIT_NOCHILD ? "did not see `child` execute" :
2677e6ac503SKyle Evans "mystery"));
2687e6ac503SKyle Evans
2697e6ac503SKyle Evans ATF_REQUIRE(forked != 0);
2707e6ac503SKyle Evans ATF_REQUIRE(parent != 0);
2717e6ac503SKyle Evans ATF_REQUIRE(child == 0);
2727e6ac503SKyle Evans }
2737e6ac503SKyle Evans
ATF_TP_ADD_TCS(tp)2747e6ac503SKyle Evans ATF_TP_ADD_TCS(tp)
2757e6ac503SKyle Evans {
276*4b202f4fSKyle Evans ATF_TP_ADD_TC(tp, preinit_atfork);
2777e6ac503SKyle Evans ATF_TP_ADD_TC(tp, basic_atfork);
2787e6ac503SKyle Evans ATF_TP_ADD_TC(tp, multi_atfork);
2797e6ac503SKyle Evans return (atf_no_error());
2807e6ac503SKyle Evans }
281