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