1*7e6ac503SKyle Evans /*- 2*7e6ac503SKyle Evans * 3*7e6ac503SKyle Evans * Copyright (C) 2024 Kyle Evans <kevans@FreeBSD.org> 4*7e6ac503SKyle Evans * 5*7e6ac503SKyle Evans * SPDX-License-Identifier: BSD-2-Clause 6*7e6ac503SKyle Evans * 7*7e6ac503SKyle Evans */ 8*7e6ac503SKyle Evans 9*7e6ac503SKyle Evans #include <sys/wait.h> 10*7e6ac503SKyle Evans #include <errno.h> 11*7e6ac503SKyle Evans #include <pthread.h> 12*7e6ac503SKyle Evans #include <signal.h> 13*7e6ac503SKyle Evans #include <stdbool.h> 14*7e6ac503SKyle Evans #include <stdio.h> 15*7e6ac503SKyle Evans #include <unistd.h> 16*7e6ac503SKyle Evans 17*7e6ac503SKyle Evans #include <atf-c.h> 18*7e6ac503SKyle Evans 19*7e6ac503SKyle Evans #define EXIT_NOPREPARE 1 20*7e6ac503SKyle Evans #define EXIT_CALLEDPARENT 2 21*7e6ac503SKyle Evans #define EXIT_NOCHILD 3 22*7e6ac503SKyle Evans #define EXIT_BADORDER 4 23*7e6ac503SKyle Evans 24*7e6ac503SKyle Evans static int child; 25*7e6ac503SKyle Evans static int forked; 26*7e6ac503SKyle Evans static int parent; 27*7e6ac503SKyle Evans 28*7e6ac503SKyle Evans static void 29*7e6ac503SKyle Evans basic_prepare(void) 30*7e6ac503SKyle Evans { 31*7e6ac503SKyle Evans ATF_REQUIRE(parent == 0); 32*7e6ac503SKyle Evans forked++; 33*7e6ac503SKyle Evans } 34*7e6ac503SKyle Evans 35*7e6ac503SKyle Evans static void 36*7e6ac503SKyle Evans basic_parent(void) 37*7e6ac503SKyle Evans { 38*7e6ac503SKyle Evans ATF_REQUIRE(forked != 0); 39*7e6ac503SKyle Evans parent++; 40*7e6ac503SKyle Evans } 41*7e6ac503SKyle Evans 42*7e6ac503SKyle Evans static void 43*7e6ac503SKyle Evans basic_child(void) 44*7e6ac503SKyle Evans { 45*7e6ac503SKyle Evans if (!forked) 46*7e6ac503SKyle Evans _exit(EXIT_NOPREPARE); 47*7e6ac503SKyle Evans if (parent != 0) 48*7e6ac503SKyle Evans _exit(EXIT_CALLEDPARENT); 49*7e6ac503SKyle Evans child++; 50*7e6ac503SKyle Evans } 51*7e6ac503SKyle Evans 52*7e6ac503SKyle Evans /* 53*7e6ac503SKyle Evans * In the basic test, we'll register just once and set some globals to confirm 54*7e6ac503SKyle Evans * that the prepare/parent callbacks were executed as expected. The child will 55*7e6ac503SKyle Evans * use its exit status to communicate to us if the callback was not executed 56*7e6ac503SKyle Evans * properly since we cannot assert there. This is a subset of the 57*7e6ac503SKyle Evans * multi-callback test, but separated out so that it's more obvious from running 58*7e6ac503SKyle Evans * the atfork_test if pthread_atfork() is completely broken or just 59*7e6ac503SKyle Evans * out-of-order. 60*7e6ac503SKyle Evans */ 61*7e6ac503SKyle Evans ATF_TC(basic_atfork); 62*7e6ac503SKyle Evans ATF_TC_HEAD(basic_atfork, tc) 63*7e6ac503SKyle Evans { 64*7e6ac503SKyle Evans atf_tc_set_md_var(tc, "descr", 65*7e6ac503SKyle Evans "Checks invocation of all three atfork callbacks"); 66*7e6ac503SKyle Evans } 67*7e6ac503SKyle Evans ATF_TC_BODY(basic_atfork, tc) 68*7e6ac503SKyle Evans { 69*7e6ac503SKyle Evans pid_t p, wpid; 70*7e6ac503SKyle Evans int status; 71*7e6ac503SKyle Evans 72*7e6ac503SKyle Evans pthread_atfork(basic_prepare, basic_parent, basic_child); 73*7e6ac503SKyle Evans 74*7e6ac503SKyle Evans p = fork(); 75*7e6ac503SKyle Evans 76*7e6ac503SKyle Evans ATF_REQUIRE(p >= 0); 77*7e6ac503SKyle Evans if (p == 0) 78*7e6ac503SKyle Evans _exit(child != 0 ? 0 : EXIT_NOCHILD); 79*7e6ac503SKyle Evans 80*7e6ac503SKyle Evans /* 81*7e6ac503SKyle Evans * The child can't use any of our standard atf-c(3) macros, so we have 82*7e6ac503SKyle Evans * to rely on the exit status to convey any shenanigans. 83*7e6ac503SKyle Evans */ 84*7e6ac503SKyle Evans while ((wpid = waitpid(p, &status, 0)) != p) { 85*7e6ac503SKyle Evans ATF_REQUIRE_ERRNO(EINTR, wpid == -1); 86*7e6ac503SKyle Evans if (wpid == -1) 87*7e6ac503SKyle Evans continue; 88*7e6ac503SKyle Evans } 89*7e6ac503SKyle Evans 90*7e6ac503SKyle Evans ATF_REQUIRE_MSG(WIFEXITED(status), 91*7e6ac503SKyle Evans "child did not exit cleanly, status %x", status); 92*7e6ac503SKyle Evans 93*7e6ac503SKyle Evans status = WEXITSTATUS(status); 94*7e6ac503SKyle Evans ATF_REQUIRE_MSG(status == 0, "atfork in child %s", 95*7e6ac503SKyle Evans status == EXIT_NOPREPARE ? "did not see `prepare` execute" : 96*7e6ac503SKyle Evans (status == EXIT_CALLEDPARENT ? "observed `parent` executing" : 97*7e6ac503SKyle Evans (status == EXIT_NOCHILD ? "did not see `child` execute" : 98*7e6ac503SKyle Evans "mystery"))); 99*7e6ac503SKyle Evans 100*7e6ac503SKyle Evans ATF_REQUIRE(forked != 0); 101*7e6ac503SKyle Evans ATF_REQUIRE(parent != 0); 102*7e6ac503SKyle Evans ATF_REQUIRE(child == 0); 103*7e6ac503SKyle Evans } 104*7e6ac503SKyle Evans 105*7e6ac503SKyle Evans static void 106*7e6ac503SKyle Evans multi_assert(bool cond, bool can_assert) 107*7e6ac503SKyle Evans { 108*7e6ac503SKyle Evans if (can_assert) 109*7e6ac503SKyle Evans ATF_REQUIRE((cond)); 110*7e6ac503SKyle Evans else if (!(cond)) 111*7e6ac503SKyle Evans _exit(EXIT_BADORDER); 112*7e6ac503SKyle Evans } 113*7e6ac503SKyle Evans 114*7e6ac503SKyle Evans static void 115*7e6ac503SKyle Evans multi_bump(int *var, int bit, bool can_assert) 116*7e6ac503SKyle Evans { 117*7e6ac503SKyle Evans int mask, val; 118*7e6ac503SKyle Evans 119*7e6ac503SKyle Evans mask = (1 << (bit - 1)); 120*7e6ac503SKyle Evans val = *var; 121*7e6ac503SKyle Evans 122*7e6ac503SKyle Evans /* 123*7e6ac503SKyle Evans * Every bit below this one must be set, and none of the upper bits 124*7e6ac503SKyle Evans * should be set. 125*7e6ac503SKyle Evans */ 126*7e6ac503SKyle Evans multi_assert((val & mask) == 0, can_assert); 127*7e6ac503SKyle Evans if (bit == 1) 128*7e6ac503SKyle Evans multi_assert(val == 0, can_assert); 129*7e6ac503SKyle Evans else 130*7e6ac503SKyle Evans multi_assert((val & ~mask) == (mask - 1), can_assert); 131*7e6ac503SKyle Evans 132*7e6ac503SKyle Evans *var |= mask; 133*7e6ac503SKyle Evans } 134*7e6ac503SKyle Evans 135*7e6ac503SKyle Evans static void 136*7e6ac503SKyle Evans multi_prepare1(void) 137*7e6ac503SKyle Evans { 138*7e6ac503SKyle Evans /* 139*7e6ac503SKyle Evans * The bits are flipped for prepare because it's supposed to be called 140*7e6ac503SKyle Evans * in the reverse order of registration. 141*7e6ac503SKyle Evans */ 142*7e6ac503SKyle Evans multi_bump(&forked, 2, true); 143*7e6ac503SKyle Evans } 144*7e6ac503SKyle Evans static void 145*7e6ac503SKyle Evans multi_prepare2(void) 146*7e6ac503SKyle Evans { 147*7e6ac503SKyle Evans multi_bump(&forked, 1, true); 148*7e6ac503SKyle Evans } 149*7e6ac503SKyle Evans 150*7e6ac503SKyle Evans static void 151*7e6ac503SKyle Evans multi_parent1(void) 152*7e6ac503SKyle Evans { 153*7e6ac503SKyle Evans multi_bump(&parent, 1, true); 154*7e6ac503SKyle Evans } 155*7e6ac503SKyle Evans static void 156*7e6ac503SKyle Evans multi_parent2(void) 157*7e6ac503SKyle Evans { 158*7e6ac503SKyle Evans multi_bump(&parent, 2, true); 159*7e6ac503SKyle Evans } 160*7e6ac503SKyle Evans 161*7e6ac503SKyle Evans static void 162*7e6ac503SKyle Evans multi_child1(void) 163*7e6ac503SKyle Evans { 164*7e6ac503SKyle Evans multi_bump(&child, 1, false); 165*7e6ac503SKyle Evans } 166*7e6ac503SKyle Evans static void 167*7e6ac503SKyle Evans multi_child2(void) 168*7e6ac503SKyle Evans { 169*7e6ac503SKyle Evans multi_bump(&child, 2, false); 170*7e6ac503SKyle Evans } 171*7e6ac503SKyle Evans 172*7e6ac503SKyle Evans /* 173*7e6ac503SKyle Evans * The multi-atfork test works much like the basic one, but it registers 174*7e6ac503SKyle Evans * multiple times and enforces an order. The child still does just as strict 175*7e6ac503SKyle Evans * of tests as the parent and continues to communicate the results of those 176*7e6ac503SKyle Evans * tests back via its exit status. 177*7e6ac503SKyle Evans */ 178*7e6ac503SKyle Evans ATF_TC(multi_atfork); 179*7e6ac503SKyle Evans ATF_TC_HEAD(multi_atfork, tc) 180*7e6ac503SKyle Evans { 181*7e6ac503SKyle Evans atf_tc_set_md_var(tc, "descr", 182*7e6ac503SKyle Evans "Checks that multiple callbacks are called in the documented order"); 183*7e6ac503SKyle Evans } 184*7e6ac503SKyle Evans ATF_TC_BODY(multi_atfork, tc) 185*7e6ac503SKyle Evans { 186*7e6ac503SKyle Evans pid_t p, wpid; 187*7e6ac503SKyle Evans int status; 188*7e6ac503SKyle Evans 189*7e6ac503SKyle Evans pthread_atfork(multi_prepare1, multi_parent1, multi_child1); 190*7e6ac503SKyle Evans pthread_atfork(multi_prepare2, multi_parent2, multi_child2); 191*7e6ac503SKyle Evans 192*7e6ac503SKyle Evans p = fork(); 193*7e6ac503SKyle Evans 194*7e6ac503SKyle Evans ATF_REQUIRE(p >= 0); 195*7e6ac503SKyle Evans if (p == 0) 196*7e6ac503SKyle Evans _exit(child != 0 ? 0 : EXIT_NOCHILD); 197*7e6ac503SKyle Evans 198*7e6ac503SKyle Evans /* 199*7e6ac503SKyle Evans * The child can't use any of our standard atf-c(3) macros, so we have 200*7e6ac503SKyle Evans * to rely on the exit status to convey any shenanigans. 201*7e6ac503SKyle Evans */ 202*7e6ac503SKyle Evans while ((wpid = waitpid(p, &status, 0)) != p) { 203*7e6ac503SKyle Evans ATF_REQUIRE_ERRNO(EINTR, wpid == -1); 204*7e6ac503SKyle Evans if (wpid == -1) 205*7e6ac503SKyle Evans continue; 206*7e6ac503SKyle Evans } 207*7e6ac503SKyle Evans 208*7e6ac503SKyle Evans ATF_REQUIRE_MSG(WIFEXITED(status), 209*7e6ac503SKyle Evans "child did not exit cleanly, status %x", status); 210*7e6ac503SKyle Evans 211*7e6ac503SKyle Evans status = WEXITSTATUS(status); 212*7e6ac503SKyle Evans ATF_REQUIRE_MSG(status == 0, "atfork in child %s", 213*7e6ac503SKyle Evans status == EXIT_BADORDER ? "called in wrong order" : 214*7e6ac503SKyle Evans (status == EXIT_NOCHILD ? "did not see `child` execute" : 215*7e6ac503SKyle Evans "mystery")); 216*7e6ac503SKyle Evans 217*7e6ac503SKyle Evans ATF_REQUIRE(forked != 0); 218*7e6ac503SKyle Evans ATF_REQUIRE(parent != 0); 219*7e6ac503SKyle Evans ATF_REQUIRE(child == 0); 220*7e6ac503SKyle Evans } 221*7e6ac503SKyle Evans 222*7e6ac503SKyle Evans ATF_TP_ADD_TCS(tp) 223*7e6ac503SKyle Evans { 224*7e6ac503SKyle Evans ATF_TP_ADD_TC(tp, basic_atfork); 225*7e6ac503SKyle Evans ATF_TP_ADD_TC(tp, multi_atfork); 226*7e6ac503SKyle Evans return (atf_no_error()); 227*7e6ac503SKyle Evans } 228