xref: /freebsd/lib/libthr/tests/atfork_test.c (revision b64c5a0ace59af62eff52bfe110a521dc73c937b)
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) = &registrar;
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