xref: /openbsd-src/regress/sys/kern/fork-exit/fork-exit.c (revision 881e535e63ea900b2d4c44780d1203771ce283dc)
1*881e535eSclaudio /*	$OpenBSD: fork-exit.c,v 1.8 2024/08/07 18:25:39 claudio Exp $	*/
20383853bSbluhm 
30383853bSbluhm /*
40383853bSbluhm  * Copyright (c) 2021 Alexander Bluhm <bluhm@openbsd.org>
50383853bSbluhm  *
60383853bSbluhm  * Permission to use, copy, modify, and distribute this software for any
70383853bSbluhm  * purpose with or without fee is hereby granted, provided that the above
80383853bSbluhm  * copyright notice and this permission notice appear in all copies.
90383853bSbluhm  *
100383853bSbluhm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
110383853bSbluhm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
120383853bSbluhm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
130383853bSbluhm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
140383853bSbluhm  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
150383853bSbluhm  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
160383853bSbluhm  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
170383853bSbluhm  */
180383853bSbluhm 
1985f5c1e8Sbluhm #include <sys/mman.h>
200383853bSbluhm #include <sys/select.h>
210383853bSbluhm #include <sys/wait.h>
220383853bSbluhm 
230383853bSbluhm #include <err.h>
240383853bSbluhm #include <errno.h>
250383853bSbluhm #include <fcntl.h>
260383853bSbluhm #include <limits.h>
2779f7fadeSbluhm #include <pthread.h>
280383853bSbluhm #include <signal.h>
290383853bSbluhm #include <stdio.h>
300383853bSbluhm #include <stdlib.h>
3185f5c1e8Sbluhm #include <string.h>
320383853bSbluhm #include <unistd.h>
330383853bSbluhm 
340383853bSbluhm int execute = 0;
350383853bSbluhm int daemonize = 0;
3685f5c1e8Sbluhm int heap = 0;
370383853bSbluhm int procs = 1;
3885f5c1e8Sbluhm int stack = 0;
3979f7fadeSbluhm int threads = 0;
400383853bSbluhm int timeout = 30;
410383853bSbluhm 
42*881e535eSclaudio int pagesize;
43*881e535eSclaudio 
4479f7fadeSbluhm pthread_barrier_t thread_barrier;
4579f7fadeSbluhm char timeoutstr[sizeof("-2147483647")];
4679f7fadeSbluhm 
470383853bSbluhm static void __dead
480383853bSbluhm usage(void)
490383853bSbluhm {
5079f7fadeSbluhm 	fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n"
510383853bSbluhm 	    "    -e          child execs sleep(1), default call sleep(3)\n"
5279f7fadeSbluhm 	    "    -d          daemonize, use if already process group leader\n"
53*881e535eSclaudio 	    "    -h heap     allocate number of kB of heap memory, default 0\n"
540383853bSbluhm 	    "    -p procs    number of processes to fork, default 1\n"
55*881e535eSclaudio 	    "    -s stack    allocate number of kB of stack memory, default 0\n"
5679f7fadeSbluhm 	    "    -t threads  number of threads to create, default 0\n"
5779f7fadeSbluhm 	    "    -T timeout  parent and children will exit, default 30 sec\n");
580383853bSbluhm 	exit(2);
590383853bSbluhm }
600383853bSbluhm 
6185f5c1e8Sbluhm static void
6285f5c1e8Sbluhm recurse_page(int depth)
6385f5c1e8Sbluhm {
6485f5c1e8Sbluhm 	int p[4096 / sizeof(int)];
6585f5c1e8Sbluhm 
6685f5c1e8Sbluhm 	if (depth == 0)
6785f5c1e8Sbluhm 		return;
6885f5c1e8Sbluhm 	p[1] = 0x9abcdef0;
6985f5c1e8Sbluhm 	explicit_bzero(p, sizeof(int));
7085f5c1e8Sbluhm 	recurse_page(depth - 1);
7185f5c1e8Sbluhm }
7285f5c1e8Sbluhm 
7385f5c1e8Sbluhm static void
7485f5c1e8Sbluhm alloc_stack(void)
7585f5c1e8Sbluhm {
76*881e535eSclaudio 	recurse_page((stack * 1024) / (4096 + 200));
7785f5c1e8Sbluhm }
7885f5c1e8Sbluhm 
7985f5c1e8Sbluhm static void
8085f5c1e8Sbluhm alloc_heap(void)
8185f5c1e8Sbluhm {
8285f5c1e8Sbluhm 	int *p;
8385f5c1e8Sbluhm 	int i;
8485f5c1e8Sbluhm 
85*881e535eSclaudio 	for (i = 0; i < heap / (pagesize / 1024); i++) {
86*881e535eSclaudio 		p = mmap(0, pagesize, PROT_WRITE|PROT_READ,
87*881e535eSclaudio 		    MAP_SHARED|MAP_ANON, -1, 0);
8885f5c1e8Sbluhm 		if (p == MAP_FAILED)
8985f5c1e8Sbluhm 			err(1, "mmap");
9085f5c1e8Sbluhm 		p[1] = 0x12345678;
9185f5c1e8Sbluhm 		explicit_bzero(p, sizeof(int));
9285f5c1e8Sbluhm 	}
9385f5c1e8Sbluhm }
9485f5c1e8Sbluhm 
9579f7fadeSbluhm static void * __dead
9679f7fadeSbluhm run_thread(void *arg)
9779f7fadeSbluhm {
9879f7fadeSbluhm 	int error;
9979f7fadeSbluhm 
10085f5c1e8Sbluhm 	if (heap)
10185f5c1e8Sbluhm 		alloc_heap();
10285f5c1e8Sbluhm 	if (stack)
10385f5c1e8Sbluhm 		alloc_stack();
10485f5c1e8Sbluhm 
10579f7fadeSbluhm 	error = pthread_barrier_wait(&thread_barrier);
10679f7fadeSbluhm 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
10779f7fadeSbluhm 		errc(1, error, "pthread_barrier_wait");
10879f7fadeSbluhm 
10979f7fadeSbluhm 	if (sleep(timeout) != 0)
11079f7fadeSbluhm 		err(1, "sleep %d", timeout);
11179f7fadeSbluhm 
11279f7fadeSbluhm 	/* should not happen */
11379f7fadeSbluhm 	_exit(0);
11479f7fadeSbluhm }
11579f7fadeSbluhm 
11679f7fadeSbluhm static void
11779f7fadeSbluhm create_threads(void)
11879f7fadeSbluhm {
1195a9dda22Sbluhm 	pthread_attr_t tattr;
12079f7fadeSbluhm 	pthread_t *thrs;
12179f7fadeSbluhm 	int i, error;
12279f7fadeSbluhm 
1235a9dda22Sbluhm 	error = pthread_attr_init(&tattr);
1245a9dda22Sbluhm 	if (error)
1255a9dda22Sbluhm 		errc(1, error, "pthread_attr_init");
1265a9dda22Sbluhm 	if (stack) {
1275a9dda22Sbluhm 		/* thread start and function call overhead needs a bit more */
1285a9dda22Sbluhm 		error = pthread_attr_setstacksize(&tattr,
129*881e535eSclaudio 		    (stack + 2) * (1024ULL + 50));
1305a9dda22Sbluhm 		if (error)
1315a9dda22Sbluhm 			errc(1, error, "pthread_attr_setstacksize");
1325a9dda22Sbluhm 	}
1335a9dda22Sbluhm 
13479f7fadeSbluhm 	error = pthread_barrier_init(&thread_barrier, NULL, threads + 1);
13579f7fadeSbluhm 	if (error)
1365a9dda22Sbluhm 		errc(1, error, "pthread_barrier_init");
13779f7fadeSbluhm 
13879f7fadeSbluhm 	thrs = reallocarray(NULL, threads, sizeof(pthread_t));
13979f7fadeSbluhm 	if (thrs == NULL)
14079f7fadeSbluhm 		err(1, "thrs");
14179f7fadeSbluhm 
14279f7fadeSbluhm 	for (i = 0; i < threads; i++) {
1435a9dda22Sbluhm 		error = pthread_create(&thrs[i], &tattr, run_thread, NULL);
14479f7fadeSbluhm 		if (error)
14579f7fadeSbluhm 			errc(1, error, "pthread_create");
14679f7fadeSbluhm 	}
14779f7fadeSbluhm 
14879f7fadeSbluhm 	error = pthread_barrier_wait(&thread_barrier);
14979f7fadeSbluhm 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
15079f7fadeSbluhm 		errc(1, error, "pthread_barrier_wait");
15179f7fadeSbluhm 
15279f7fadeSbluhm 	/* return to close child's pipe and sleep */
15379f7fadeSbluhm }
15479f7fadeSbluhm 
1550383853bSbluhm static void __dead
1560383853bSbluhm exec_sleep(void)
1570383853bSbluhm {
15879f7fadeSbluhm 	execl("/bin/sleep", "sleep", timeoutstr, NULL);
1590383853bSbluhm 	err(1, "exec sleep");
1600383853bSbluhm }
1610383853bSbluhm 
16279f7fadeSbluhm static void __dead
16379f7fadeSbluhm run_child(int fd)
1640383853bSbluhm {
1650383853bSbluhm 	/* close pipe to parent and sleep until killed */
1660383853bSbluhm 	if (execute) {
1670383853bSbluhm 		if (fcntl(fd, F_SETFD, FD_CLOEXEC))
1680383853bSbluhm 			err(1, "fcntl FD_CLOEXEC");
1690383853bSbluhm 		exec_sleep();
1700383853bSbluhm 	} else {
17185f5c1e8Sbluhm 		if (threads) {
17279f7fadeSbluhm 			create_threads();
17385f5c1e8Sbluhm 		} else {
17485f5c1e8Sbluhm 			if (heap)
17585f5c1e8Sbluhm 				alloc_heap();
17685f5c1e8Sbluhm 			if (stack)
17785f5c1e8Sbluhm 				alloc_stack();
17885f5c1e8Sbluhm 		}
1790383853bSbluhm 		if (close(fd) == -1)
18079f7fadeSbluhm 			err(1, "close child");
1810383853bSbluhm 		if (sleep(timeout) != 0)
1820383853bSbluhm 			err(1, "sleep %d", timeout);
1830383853bSbluhm 	}
18479f7fadeSbluhm 
18579f7fadeSbluhm 	/* should not happen */
1860383853bSbluhm 	_exit(0);
1870383853bSbluhm }
1880383853bSbluhm 
1890383853bSbluhm static void
1900383853bSbluhm sigexit(int sig)
1910383853bSbluhm {
1920383853bSbluhm 	int i, status;
1930383853bSbluhm 	pid_t pid;
1940383853bSbluhm 
1950383853bSbluhm 	/* all children must terminate in time */
1965a9255e7Sbluhm 	alarm(timeout);
1970383853bSbluhm 
1980383853bSbluhm 	for (i = 0; i < procs; i++) {
1990383853bSbluhm 		pid = wait(&status);
2000383853bSbluhm 		if (pid == -1)
2010383853bSbluhm 			err(1, "wait");
2020383853bSbluhm 		if (!WIFSIGNALED(status))
2030383853bSbluhm 			errx(1, "child %d not killed", pid);
2040383853bSbluhm 		if(WTERMSIG(status) != SIGTERM)
2050383853bSbluhm 			errx(1, "child %d signal %d", pid, WTERMSIG(status));
2060383853bSbluhm 	}
2070383853bSbluhm 	exit(0);
2080383853bSbluhm }
2090383853bSbluhm 
2100383853bSbluhm int
2110383853bSbluhm main(int argc, char *argv[])
2120383853bSbluhm {
2130383853bSbluhm 	const char *errstr;
2140383853bSbluhm 	int ch, i, fdmax, fdlen, *rfds, waiting;
2150383853bSbluhm 	fd_set *fdset;
2160383853bSbluhm 	pid_t pgrp;
2170383853bSbluhm 	struct timeval tv;
2180383853bSbluhm 
219*881e535eSclaudio 	pagesize = sysconf(_SC_PAGESIZE);
220*881e535eSclaudio 
22185f5c1e8Sbluhm 	while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) {
2220383853bSbluhm 	switch (ch) {
2230383853bSbluhm 		case 'e':
2240383853bSbluhm 			execute = 1;
2250383853bSbluhm 			break;
2260383853bSbluhm 		case 'd':
2270383853bSbluhm 			daemonize = 1;
2280383853bSbluhm 			break;
22985f5c1e8Sbluhm 		case 'h':
23085f5c1e8Sbluhm 			heap = strtonum(optarg, 0, INT_MAX, &errstr);
23185f5c1e8Sbluhm 			if (errstr != NULL)
23285f5c1e8Sbluhm 				errx(1, "number of heap allocations is %s: %s",
23385f5c1e8Sbluhm 				    errstr, optarg);
23485f5c1e8Sbluhm 			break;
2350383853bSbluhm 		case 'p':
236*881e535eSclaudio 			procs = strtonum(optarg, 0, INT_MAX / pagesize,
237*881e535eSclaudio 			    &errstr);
2380383853bSbluhm 			if (errstr != NULL)
2390383853bSbluhm 				errx(1, "number of procs is %s: %s", errstr,
2400383853bSbluhm 				    optarg);
2410383853bSbluhm 			break;
24285f5c1e8Sbluhm 		case 's':
2435a9dda22Sbluhm 			stack = strtonum(optarg, 0,
244*881e535eSclaudio 			    (INT_MAX / (1024 + 50)) - 2, &errstr);
24585f5c1e8Sbluhm 			if (errstr != NULL)
24685f5c1e8Sbluhm 				errx(1, "number of stack allocations is %s: %s",
24785f5c1e8Sbluhm 				    errstr, optarg);
24885f5c1e8Sbluhm 			break;
2490383853bSbluhm 		case 't':
25079f7fadeSbluhm 			threads = strtonum(optarg, 0, INT_MAX, &errstr);
25179f7fadeSbluhm 			if (errstr != NULL)
25279f7fadeSbluhm 				errx(1, "number of threads is %s: %s", errstr,
25379f7fadeSbluhm 				    optarg);
25479f7fadeSbluhm 			break;
25579f7fadeSbluhm 		case 'T':
2560383853bSbluhm 			timeout = strtonum(optarg, 0, INT_MAX, &errstr);
2570383853bSbluhm 			if (errstr != NULL)
2580383853bSbluhm 				errx(1, "timeout is %s: %s", errstr, optarg);
25979f7fadeSbluhm 			break;
2600383853bSbluhm 		default:
2610383853bSbluhm 			usage();
2620383853bSbluhm 		}
2630383853bSbluhm 	}
26479f7fadeSbluhm 	if (execute) {
26579f7fadeSbluhm 		int ret;
26679f7fadeSbluhm 
26779f7fadeSbluhm 		if (threads > 0)
26879f7fadeSbluhm 			errx(1, "execute sleep cannot be used with threads");
26979f7fadeSbluhm 
27079f7fadeSbluhm 		ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout);
27179f7fadeSbluhm 		if (ret < 0 || (size_t)ret >= sizeof(timeoutstr))
27279f7fadeSbluhm 			err(1, "snprintf");
27379f7fadeSbluhm 	}
2740383853bSbluhm 
2750383853bSbluhm 	/* become process group leader */
27679f7fadeSbluhm 	if (daemonize) {
27779f7fadeSbluhm 		/* get rid of process leadership */
2780383853bSbluhm 		switch (fork()) {
2790383853bSbluhm 		case -1:
2800383853bSbluhm 			err(1, "fork parent");
2810383853bSbluhm 		case 0:
2820383853bSbluhm 			break;
2830383853bSbluhm 		default:
28479f7fadeSbluhm 			/* parent leaves orphan behind to do the work */
2850383853bSbluhm 			_exit(0);
2860383853bSbluhm 		}
2870383853bSbluhm 	}
28879f7fadeSbluhm 	pgrp = setsid();
28979f7fadeSbluhm 	if (pgrp == -1) {
2900383853bSbluhm 		if (!daemonize)
2910383853bSbluhm 			warnx("try -d to become process group leader");
2920383853bSbluhm 		err(1, "setsid");
2930383853bSbluhm 	}
2940383853bSbluhm 
2950383853bSbluhm 	/* create pipes to keep in contact with children */
2960383853bSbluhm 	rfds = reallocarray(NULL, procs, sizeof(int));
2970383853bSbluhm 	if (rfds == NULL)
2980383853bSbluhm 		err(1, "rfds");
2990383853bSbluhm 	fdmax = 0;
3000383853bSbluhm 
3010383853bSbluhm 	/* fork child processes and pass writing end of pipe */
3020383853bSbluhm 	for (i = 0; i < procs; i++) {
30379f7fadeSbluhm 		int pipefds[2], error;
3040383853bSbluhm 
3050383853bSbluhm 		if (pipe(pipefds) == -1)
3060383853bSbluhm 			err(1, "pipe");
3070383853bSbluhm 		if (fdmax < pipefds[0])
3080383853bSbluhm 			fdmax = pipefds[0];
3090383853bSbluhm 		rfds[i] = pipefds[0];
31079f7fadeSbluhm 
31179f7fadeSbluhm 		switch (fork()) {
31279f7fadeSbluhm 		case -1:
31379f7fadeSbluhm 			/* resource temporarily unavailable may happen */
31479f7fadeSbluhm 			error = errno;
31579f7fadeSbluhm 			/* reap children, but not parent */
31679f7fadeSbluhm 			signal(SIGTERM, SIG_IGN);
31779f7fadeSbluhm 			kill(-pgrp, SIGTERM);
31879f7fadeSbluhm 			errc(1, error, "fork child");
31979f7fadeSbluhm 		case 0:
32079f7fadeSbluhm 			/* child closes reading end, read is for the parent */
32179f7fadeSbluhm 			if (close(pipefds[0]) == -1)
32279f7fadeSbluhm 				err(1, "close read");
32379f7fadeSbluhm 			run_child(pipefds[1]);
32479f7fadeSbluhm 			/* cannot happen */
32579f7fadeSbluhm 			_exit(0);
32679f7fadeSbluhm 		default:
32779f7fadeSbluhm 			/* parent closes writing end, write is for the child */
3280383853bSbluhm 			if (close(pipefds[1]) == -1)
32979f7fadeSbluhm 				err(1, "close write");
33079f7fadeSbluhm 			break;
33179f7fadeSbluhm 		}
3320383853bSbluhm 	}
3330383853bSbluhm 
3340383853bSbluhm 	/* create select mask with all reading ends of child pipes */
3350383853bSbluhm 	fdlen = howmany(fdmax + 1, NFDBITS);
3360383853bSbluhm 	fdset = calloc(fdlen, sizeof(fd_mask));
3370383853bSbluhm 	if (fdset == NULL)
3380383853bSbluhm 		err(1, "fdset");
33979f7fadeSbluhm 	waiting = 0;
3400383853bSbluhm 	for (i = 0; i < procs; i++) {
3410383853bSbluhm 		FD_SET(rfds[i], fdset);
34279f7fadeSbluhm 		waiting = 1;
3430383853bSbluhm 	}
3440383853bSbluhm 
3450383853bSbluhm 	/* wait until all child processes are waiting */
34679f7fadeSbluhm 	while (waiting) {
3470383853bSbluhm 		tv.tv_sec = timeout;
3480383853bSbluhm 		tv.tv_usec = 0;
3490383853bSbluhm 		errno = ETIMEDOUT;
3500383853bSbluhm 		if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0)
3510383853bSbluhm 			err(1, "select");
3520383853bSbluhm 
35379f7fadeSbluhm 		waiting = 0;
3540383853bSbluhm 		/* remove fd of children that closed their end  */
3550383853bSbluhm 		for (i = 0; i < procs; i++) {
3560383853bSbluhm 			if (rfds[i] >= 0) {
3570383853bSbluhm 				if (FD_ISSET(rfds[i], fdset)) {
3580383853bSbluhm 					if (close(rfds[i]) == -1)
35979f7fadeSbluhm 						err(1, "close parent");
3600383853bSbluhm 					FD_CLR(rfds[i], fdset);
3610383853bSbluhm 					rfds[i] = -1;
3620383853bSbluhm 				} else {
3630383853bSbluhm 					FD_SET(rfds[i], fdset);
3640383853bSbluhm 					waiting = 1;
3650383853bSbluhm 				}
3660383853bSbluhm 			}
3670383853bSbluhm 		}
36879f7fadeSbluhm 	}
3690383853bSbluhm 
3700383853bSbluhm 	/* kill all children simultaneously, parent exits in signal handler */
3710383853bSbluhm 	if (signal(SIGTERM, sigexit) == SIG_ERR)
3720383853bSbluhm 		err(1, "signal SIGTERM");
3730383853bSbluhm 	if (kill(-pgrp, SIGTERM) == -1)
3740383853bSbluhm 		err(1, "kill %d", -pgrp);
3750383853bSbluhm 
3760383853bSbluhm 	errx(1, "alive");
3770383853bSbluhm }
378