xref: /openbsd/regress/sys/kern/fork-exit/fork-exit.c (revision 881e535e)
1 /*	$OpenBSD: fork-exit.c,v 1.8 2024/08/07 18:25:39 claudio Exp $	*/
2 
3 /*
4  * Copyright (c) 2021 Alexander Bluhm <bluhm@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/mman.h>
20 #include <sys/select.h>
21 #include <sys/wait.h>
22 
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <pthread.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 int execute = 0;
35 int daemonize = 0;
36 int heap = 0;
37 int procs = 1;
38 int stack = 0;
39 int threads = 0;
40 int timeout = 30;
41 
42 int pagesize;
43 
44 pthread_barrier_t thread_barrier;
45 char timeoutstr[sizeof("-2147483647")];
46 
47 static void __dead
usage(void)48 usage(void)
49 {
50 	fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n"
51 	    "    -e          child execs sleep(1), default call sleep(3)\n"
52 	    "    -d          daemonize, use if already process group leader\n"
53 	    "    -h heap     allocate number of kB of heap memory, default 0\n"
54 	    "    -p procs    number of processes to fork, default 1\n"
55 	    "    -s stack    allocate number of kB of stack memory, default 0\n"
56 	    "    -t threads  number of threads to create, default 0\n"
57 	    "    -T timeout  parent and children will exit, default 30 sec\n");
58 	exit(2);
59 }
60 
61 static void
recurse_page(int depth)62 recurse_page(int depth)
63 {
64 	int p[4096 / sizeof(int)];
65 
66 	if (depth == 0)
67 		return;
68 	p[1] = 0x9abcdef0;
69 	explicit_bzero(p, sizeof(int));
70 	recurse_page(depth - 1);
71 }
72 
73 static void
alloc_stack(void)74 alloc_stack(void)
75 {
76 	recurse_page((stack * 1024) / (4096 + 200));
77 }
78 
79 static void
alloc_heap(void)80 alloc_heap(void)
81 {
82 	int *p;
83 	int i;
84 
85 	for (i = 0; i < heap / (pagesize / 1024); i++) {
86 		p = mmap(0, pagesize, PROT_WRITE|PROT_READ,
87 		    MAP_SHARED|MAP_ANON, -1, 0);
88 		if (p == MAP_FAILED)
89 			err(1, "mmap");
90 		p[1] = 0x12345678;
91 		explicit_bzero(p, sizeof(int));
92 	}
93 }
94 
95 static void * __dead
run_thread(void * arg)96 run_thread(void *arg)
97 {
98 	int error;
99 
100 	if (heap)
101 		alloc_heap();
102 	if (stack)
103 		alloc_stack();
104 
105 	error = pthread_barrier_wait(&thread_barrier);
106 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
107 		errc(1, error, "pthread_barrier_wait");
108 
109 	if (sleep(timeout) != 0)
110 		err(1, "sleep %d", timeout);
111 
112 	/* should not happen */
113 	_exit(0);
114 }
115 
116 static void
create_threads(void)117 create_threads(void)
118 {
119 	pthread_attr_t tattr;
120 	pthread_t *thrs;
121 	int i, error;
122 
123 	error = pthread_attr_init(&tattr);
124 	if (error)
125 		errc(1, error, "pthread_attr_init");
126 	if (stack) {
127 		/* thread start and function call overhead needs a bit more */
128 		error = pthread_attr_setstacksize(&tattr,
129 		    (stack + 2) * (1024ULL + 50));
130 		if (error)
131 			errc(1, error, "pthread_attr_setstacksize");
132 	}
133 
134 	error = pthread_barrier_init(&thread_barrier, NULL, threads + 1);
135 	if (error)
136 		errc(1, error, "pthread_barrier_init");
137 
138 	thrs = reallocarray(NULL, threads, sizeof(pthread_t));
139 	if (thrs == NULL)
140 		err(1, "thrs");
141 
142 	for (i = 0; i < threads; i++) {
143 		error = pthread_create(&thrs[i], &tattr, run_thread, NULL);
144 		if (error)
145 			errc(1, error, "pthread_create");
146 	}
147 
148 	error = pthread_barrier_wait(&thread_barrier);
149 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
150 		errc(1, error, "pthread_barrier_wait");
151 
152 	/* return to close child's pipe and sleep */
153 }
154 
155 static void __dead
exec_sleep(void)156 exec_sleep(void)
157 {
158 	execl("/bin/sleep", "sleep", timeoutstr, NULL);
159 	err(1, "exec sleep");
160 }
161 
162 static void __dead
run_child(int fd)163 run_child(int fd)
164 {
165 	/* close pipe to parent and sleep until killed */
166 	if (execute) {
167 		if (fcntl(fd, F_SETFD, FD_CLOEXEC))
168 			err(1, "fcntl FD_CLOEXEC");
169 		exec_sleep();
170 	} else {
171 		if (threads) {
172 			create_threads();
173 		} else {
174 			if (heap)
175 				alloc_heap();
176 			if (stack)
177 				alloc_stack();
178 		}
179 		if (close(fd) == -1)
180 			err(1, "close child");
181 		if (sleep(timeout) != 0)
182 			err(1, "sleep %d", timeout);
183 	}
184 
185 	/* should not happen */
186 	_exit(0);
187 }
188 
189 static void
sigexit(int sig)190 sigexit(int sig)
191 {
192 	int i, status;
193 	pid_t pid;
194 
195 	/* all children must terminate in time */
196 	alarm(timeout);
197 
198 	for (i = 0; i < procs; i++) {
199 		pid = wait(&status);
200 		if (pid == -1)
201 			err(1, "wait");
202 		if (!WIFSIGNALED(status))
203 			errx(1, "child %d not killed", pid);
204 		if(WTERMSIG(status) != SIGTERM)
205 			errx(1, "child %d signal %d", pid, WTERMSIG(status));
206 	}
207 	exit(0);
208 }
209 
210 int
main(int argc,char * argv[])211 main(int argc, char *argv[])
212 {
213 	const char *errstr;
214 	int ch, i, fdmax, fdlen, *rfds, waiting;
215 	fd_set *fdset;
216 	pid_t pgrp;
217 	struct timeval tv;
218 
219 	pagesize = sysconf(_SC_PAGESIZE);
220 
221 	while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) {
222 	switch (ch) {
223 		case 'e':
224 			execute = 1;
225 			break;
226 		case 'd':
227 			daemonize = 1;
228 			break;
229 		case 'h':
230 			heap = strtonum(optarg, 0, INT_MAX, &errstr);
231 			if (errstr != NULL)
232 				errx(1, "number of heap allocations is %s: %s",
233 				    errstr, optarg);
234 			break;
235 		case 'p':
236 			procs = strtonum(optarg, 0, INT_MAX / pagesize,
237 			    &errstr);
238 			if (errstr != NULL)
239 				errx(1, "number of procs is %s: %s", errstr,
240 				    optarg);
241 			break;
242 		case 's':
243 			stack = strtonum(optarg, 0,
244 			    (INT_MAX / (1024 + 50)) - 2, &errstr);
245 			if (errstr != NULL)
246 				errx(1, "number of stack allocations is %s: %s",
247 				    errstr, optarg);
248 			break;
249 		case 't':
250 			threads = strtonum(optarg, 0, INT_MAX, &errstr);
251 			if (errstr != NULL)
252 				errx(1, "number of threads is %s: %s", errstr,
253 				    optarg);
254 			break;
255 		case 'T':
256 			timeout = strtonum(optarg, 0, INT_MAX, &errstr);
257 			if (errstr != NULL)
258 				errx(1, "timeout is %s: %s", errstr, optarg);
259 			break;
260 		default:
261 			usage();
262 		}
263 	}
264 	if (execute) {
265 		int ret;
266 
267 		if (threads > 0)
268 			errx(1, "execute sleep cannot be used with threads");
269 
270 		ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout);
271 		if (ret < 0 || (size_t)ret >= sizeof(timeoutstr))
272 			err(1, "snprintf");
273 	}
274 
275 	/* become process group leader */
276 	if (daemonize) {
277 		/* get rid of process leadership */
278 		switch (fork()) {
279 		case -1:
280 			err(1, "fork parent");
281 		case 0:
282 			break;
283 		default:
284 			/* parent leaves orphan behind to do the work */
285 			_exit(0);
286 		}
287 	}
288 	pgrp = setsid();
289 	if (pgrp == -1) {
290 		if (!daemonize)
291 			warnx("try -d to become process group leader");
292 		err(1, "setsid");
293 	}
294 
295 	/* create pipes to keep in contact with children */
296 	rfds = reallocarray(NULL, procs, sizeof(int));
297 	if (rfds == NULL)
298 		err(1, "rfds");
299 	fdmax = 0;
300 
301 	/* fork child processes and pass writing end of pipe */
302 	for (i = 0; i < procs; i++) {
303 		int pipefds[2], error;
304 
305 		if (pipe(pipefds) == -1)
306 			err(1, "pipe");
307 		if (fdmax < pipefds[0])
308 			fdmax = pipefds[0];
309 		rfds[i] = pipefds[0];
310 
311 		switch (fork()) {
312 		case -1:
313 			/* resource temporarily unavailable may happen */
314 			error = errno;
315 			/* reap children, but not parent */
316 			signal(SIGTERM, SIG_IGN);
317 			kill(-pgrp, SIGTERM);
318 			errc(1, error, "fork child");
319 		case 0:
320 			/* child closes reading end, read is for the parent */
321 			if (close(pipefds[0]) == -1)
322 				err(1, "close read");
323 			run_child(pipefds[1]);
324 			/* cannot happen */
325 			_exit(0);
326 		default:
327 			/* parent closes writing end, write is for the child */
328 			if (close(pipefds[1]) == -1)
329 				err(1, "close write");
330 			break;
331 		}
332 	}
333 
334 	/* create select mask with all reading ends of child pipes */
335 	fdlen = howmany(fdmax + 1, NFDBITS);
336 	fdset = calloc(fdlen, sizeof(fd_mask));
337 	if (fdset == NULL)
338 		err(1, "fdset");
339 	waiting = 0;
340 	for (i = 0; i < procs; i++) {
341 		FD_SET(rfds[i], fdset);
342 		waiting = 1;
343 	}
344 
345 	/* wait until all child processes are waiting */
346 	while (waiting) {
347 		tv.tv_sec = timeout;
348 		tv.tv_usec = 0;
349 		errno = ETIMEDOUT;
350 		if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0)
351 			err(1, "select");
352 
353 		waiting = 0;
354 		/* remove fd of children that closed their end  */
355 		for (i = 0; i < procs; i++) {
356 			if (rfds[i] >= 0) {
357 				if (FD_ISSET(rfds[i], fdset)) {
358 					if (close(rfds[i]) == -1)
359 						err(1, "close parent");
360 					FD_CLR(rfds[i], fdset);
361 					rfds[i] = -1;
362 				} else {
363 					FD_SET(rfds[i], fdset);
364 					waiting = 1;
365 				}
366 			}
367 		}
368 	}
369 
370 	/* kill all children simultaneously, parent exits in signal handler */
371 	if (signal(SIGTERM, sigexit) == SIG_ERR)
372 		err(1, "signal SIGTERM");
373 	if (kill(-pgrp, SIGTERM) == -1)
374 		err(1, "kill %d", -pgrp);
375 
376 	errx(1, "alive");
377 }
378