xref: /openbsd-src/regress/sys/kern/fork-exit/fork-exit.c (revision 79f7fade5638288ad654be0a84ae59b0956f4313)
1*79f7fadeSbluhm /*	$OpenBSD: fork-exit.c,v 1.2 2021/04/29 13:39:22 bluhm 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 
190383853bSbluhm #include <sys/select.h>
200383853bSbluhm #include <sys/wait.h>
210383853bSbluhm 
220383853bSbluhm #include <err.h>
230383853bSbluhm #include <errno.h>
240383853bSbluhm #include <fcntl.h>
250383853bSbluhm #include <limits.h>
26*79f7fadeSbluhm #include <pthread.h>
270383853bSbluhm #include <signal.h>
280383853bSbluhm #include <stdio.h>
290383853bSbluhm #include <stdlib.h>
300383853bSbluhm #include <unistd.h>
310383853bSbluhm 
320383853bSbluhm int execute = 0;
330383853bSbluhm int daemonize = 0;
340383853bSbluhm int procs = 1;
35*79f7fadeSbluhm int threads = 0;
360383853bSbluhm int timeout = 30;
370383853bSbluhm 
38*79f7fadeSbluhm pthread_barrier_t thread_barrier;
39*79f7fadeSbluhm char timeoutstr[sizeof("-2147483647")];
40*79f7fadeSbluhm 
410383853bSbluhm static void __dead
420383853bSbluhm usage(void)
430383853bSbluhm {
44*79f7fadeSbluhm 	fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n"
450383853bSbluhm 	    "    -e          child execs sleep(1), default call sleep(3)\n"
46*79f7fadeSbluhm 	    "    -d          daemonize, use if already process group leader\n"
470383853bSbluhm 	    "    -p procs    number of processes to fork, default 1\n"
48*79f7fadeSbluhm 	    "    -t threads   number of threads to create, default 0\n"
49*79f7fadeSbluhm 	    "    -T timeout  parent and children will exit, default 30 sec\n");
500383853bSbluhm 	exit(2);
510383853bSbluhm }
520383853bSbluhm 
53*79f7fadeSbluhm static void * __dead
54*79f7fadeSbluhm run_thread(void *arg)
55*79f7fadeSbluhm {
56*79f7fadeSbluhm 	int error;
57*79f7fadeSbluhm 
58*79f7fadeSbluhm 	error = pthread_barrier_wait(&thread_barrier);
59*79f7fadeSbluhm 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
60*79f7fadeSbluhm 		errc(1, error, "pthread_barrier_wait");
61*79f7fadeSbluhm 
62*79f7fadeSbluhm 	if (sleep(timeout) != 0)
63*79f7fadeSbluhm 		err(1, "sleep %d", timeout);
64*79f7fadeSbluhm 
65*79f7fadeSbluhm 	/* should not happen */
66*79f7fadeSbluhm 	_exit(0);
67*79f7fadeSbluhm }
68*79f7fadeSbluhm 
69*79f7fadeSbluhm static void
70*79f7fadeSbluhm create_threads(void)
71*79f7fadeSbluhm {
72*79f7fadeSbluhm 	pthread_t *thrs;
73*79f7fadeSbluhm 	int i, error;
74*79f7fadeSbluhm 
75*79f7fadeSbluhm 	error = pthread_barrier_init(&thread_barrier, NULL, threads + 1);
76*79f7fadeSbluhm 	if (error)
77*79f7fadeSbluhm 		errc(1, errno, "pthread_barrier_init");
78*79f7fadeSbluhm 
79*79f7fadeSbluhm 	thrs = reallocarray(NULL, threads, sizeof(pthread_t));
80*79f7fadeSbluhm 	if (thrs == NULL)
81*79f7fadeSbluhm 		err(1, "thrs");
82*79f7fadeSbluhm 
83*79f7fadeSbluhm 	for (i = 0; i < threads; i++) {
84*79f7fadeSbluhm 		error = pthread_create(&thrs[i], NULL, run_thread, NULL);
85*79f7fadeSbluhm 		if (error)
86*79f7fadeSbluhm 			errc(1, error, "pthread_create");
87*79f7fadeSbluhm 	}
88*79f7fadeSbluhm 
89*79f7fadeSbluhm 	error = pthread_barrier_wait(&thread_barrier);
90*79f7fadeSbluhm 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
91*79f7fadeSbluhm 		errc(1, error, "pthread_barrier_wait");
92*79f7fadeSbluhm 
93*79f7fadeSbluhm 	/* return to close child's pipe and sleep */
94*79f7fadeSbluhm }
95*79f7fadeSbluhm 
960383853bSbluhm static void __dead
970383853bSbluhm exec_sleep(void)
980383853bSbluhm {
99*79f7fadeSbluhm 	execl("/bin/sleep", "sleep", timeoutstr, NULL);
1000383853bSbluhm 	err(1, "exec sleep");
1010383853bSbluhm }
1020383853bSbluhm 
103*79f7fadeSbluhm static void __dead
104*79f7fadeSbluhm run_child(int fd)
1050383853bSbluhm {
1060383853bSbluhm 	/* close pipe to parent and sleep until killed */
1070383853bSbluhm 	if (execute) {
1080383853bSbluhm 		if (fcntl(fd, F_SETFD, FD_CLOEXEC))
1090383853bSbluhm 			err(1, "fcntl FD_CLOEXEC");
1100383853bSbluhm 		exec_sleep();
1110383853bSbluhm 	} else {
112*79f7fadeSbluhm 		if (threads)
113*79f7fadeSbluhm 			create_threads();
1140383853bSbluhm 		if (close(fd) == -1)
115*79f7fadeSbluhm 			err(1, "close child");
1160383853bSbluhm 		if (sleep(timeout) != 0)
1170383853bSbluhm 			err(1, "sleep %d", timeout);
1180383853bSbluhm 	}
119*79f7fadeSbluhm 
120*79f7fadeSbluhm 	/* should not happen */
1210383853bSbluhm 	_exit(0);
1220383853bSbluhm }
1230383853bSbluhm 
1240383853bSbluhm static void
1250383853bSbluhm sigexit(int sig)
1260383853bSbluhm {
1270383853bSbluhm 	int i, status;
1280383853bSbluhm 	pid_t pid;
1290383853bSbluhm 
1300383853bSbluhm 	/* all children must terminate in time */
1310383853bSbluhm 	if ((int)alarm(timeout) == -1)
1320383853bSbluhm 		err(1, "alarm");
1330383853bSbluhm 
1340383853bSbluhm 	for (i = 0; i < procs; i++) {
1350383853bSbluhm 		pid = wait(&status);
1360383853bSbluhm 		if (pid == -1)
1370383853bSbluhm 			err(1, "wait");
1380383853bSbluhm 		if (!WIFSIGNALED(status))
1390383853bSbluhm 			errx(1, "child %d not killed", pid);
1400383853bSbluhm 		if(WTERMSIG(status) != SIGTERM)
1410383853bSbluhm 			errx(1, "child %d signal %d", pid, WTERMSIG(status));
1420383853bSbluhm 	}
1430383853bSbluhm 	exit(0);
1440383853bSbluhm }
1450383853bSbluhm 
1460383853bSbluhm int
1470383853bSbluhm main(int argc, char *argv[])
1480383853bSbluhm {
1490383853bSbluhm 	const char *errstr;
1500383853bSbluhm 	int ch, i, fdmax, fdlen, *rfds, waiting;
1510383853bSbluhm 	fd_set *fdset;
1520383853bSbluhm 	pid_t pgrp;
1530383853bSbluhm 	struct timeval tv;
1540383853bSbluhm 
155*79f7fadeSbluhm 	while ((ch = getopt(argc, argv, "edp:T:t:")) != -1) {
1560383853bSbluhm 	switch (ch) {
1570383853bSbluhm 		case 'e':
1580383853bSbluhm 			execute = 1;
1590383853bSbluhm 			break;
1600383853bSbluhm 		case 'd':
1610383853bSbluhm 			daemonize = 1;
1620383853bSbluhm 			break;
1630383853bSbluhm 		case 'p':
1640383853bSbluhm 			procs = strtonum(optarg, 0, INT_MAX, &errstr);
1650383853bSbluhm 			if (errstr != NULL)
1660383853bSbluhm 				errx(1, "number of procs is %s: %s", errstr,
1670383853bSbluhm 				    optarg);
1680383853bSbluhm 			break;
1690383853bSbluhm 		case 't':
170*79f7fadeSbluhm 			threads = strtonum(optarg, 0, INT_MAX, &errstr);
171*79f7fadeSbluhm 			if (errstr != NULL)
172*79f7fadeSbluhm 				errx(1, "number of threads is %s: %s", errstr,
173*79f7fadeSbluhm 				    optarg);
174*79f7fadeSbluhm 			break;
175*79f7fadeSbluhm 		case 'T':
1760383853bSbluhm 			timeout = strtonum(optarg, 0, INT_MAX, &errstr);
1770383853bSbluhm 			if (errstr != NULL)
1780383853bSbluhm 				errx(1, "timeout is %s: %s", errstr, optarg);
179*79f7fadeSbluhm 			break;
1800383853bSbluhm 		default:
1810383853bSbluhm 			usage();
1820383853bSbluhm 		}
1830383853bSbluhm 	}
184*79f7fadeSbluhm 	if (execute) {
185*79f7fadeSbluhm 		int ret;
186*79f7fadeSbluhm 
187*79f7fadeSbluhm 		if (threads > 0)
188*79f7fadeSbluhm 			errx(1, "execute sleep cannot be used with threads");
189*79f7fadeSbluhm 
190*79f7fadeSbluhm 		ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout);
191*79f7fadeSbluhm 		if (ret < 0 || (size_t)ret >= sizeof(timeoutstr))
192*79f7fadeSbluhm 			err(1, "snprintf");
193*79f7fadeSbluhm 	}
1940383853bSbluhm 
1950383853bSbluhm 	/* become process group leader */
196*79f7fadeSbluhm 	if (daemonize) {
197*79f7fadeSbluhm 		/* get rid of process leadership */
1980383853bSbluhm 		switch (fork()) {
1990383853bSbluhm 		case -1:
2000383853bSbluhm 			err(1, "fork parent");
2010383853bSbluhm 		case 0:
2020383853bSbluhm 			break;
2030383853bSbluhm 		default:
204*79f7fadeSbluhm 			/* parent leaves orphan behind to do the work */
2050383853bSbluhm 			_exit(0);
2060383853bSbluhm 		}
2070383853bSbluhm 	}
208*79f7fadeSbluhm 	pgrp = setsid();
209*79f7fadeSbluhm 	if (pgrp == -1) {
2100383853bSbluhm 		if (!daemonize)
2110383853bSbluhm 			warnx("try -d to become process group leader");
2120383853bSbluhm 		err(1, "setsid");
2130383853bSbluhm 	}
2140383853bSbluhm 
2150383853bSbluhm 	/* create pipes to keep in contact with children */
2160383853bSbluhm 	rfds = reallocarray(NULL, procs, sizeof(int));
2170383853bSbluhm 	if (rfds == NULL)
2180383853bSbluhm 		err(1, "rfds");
2190383853bSbluhm 	fdmax = 0;
2200383853bSbluhm 
2210383853bSbluhm 	/* fork child processes and pass writing end of pipe */
2220383853bSbluhm 	for (i = 0; i < procs; i++) {
223*79f7fadeSbluhm 		int pipefds[2], error;
2240383853bSbluhm 
2250383853bSbluhm 		if (pipe(pipefds) == -1)
2260383853bSbluhm 			err(1, "pipe");
2270383853bSbluhm 		if (fdmax < pipefds[0])
2280383853bSbluhm 			fdmax = pipefds[0];
2290383853bSbluhm 		rfds[i] = pipefds[0];
230*79f7fadeSbluhm 
231*79f7fadeSbluhm 		switch (fork()) {
232*79f7fadeSbluhm 		case -1:
233*79f7fadeSbluhm 			/* resource temporarily unavailable may happen */
234*79f7fadeSbluhm 			error = errno;
235*79f7fadeSbluhm 			/* reap children, but not parent */
236*79f7fadeSbluhm 			signal(SIGTERM, SIG_IGN);
237*79f7fadeSbluhm 			kill(-pgrp, SIGTERM);
238*79f7fadeSbluhm 			errc(1, error, "fork child");
239*79f7fadeSbluhm 		case 0:
240*79f7fadeSbluhm 			/* child closes reading end, read is for the parent */
241*79f7fadeSbluhm 			if (close(pipefds[0]) == -1)
242*79f7fadeSbluhm 				err(1, "close read");
243*79f7fadeSbluhm 			run_child(pipefds[1]);
244*79f7fadeSbluhm 			/* cannot happen */
245*79f7fadeSbluhm 			_exit(0);
246*79f7fadeSbluhm 		default:
247*79f7fadeSbluhm 			/* parent closes writing end, write is for the child */
2480383853bSbluhm 			if (close(pipefds[1]) == -1)
249*79f7fadeSbluhm 				err(1, "close write");
250*79f7fadeSbluhm 			break;
251*79f7fadeSbluhm 		}
2520383853bSbluhm 	}
2530383853bSbluhm 
2540383853bSbluhm 	/* create select mask with all reading ends of child pipes */
2550383853bSbluhm 	fdlen = howmany(fdmax + 1, NFDBITS);
2560383853bSbluhm 	fdset = calloc(fdlen, sizeof(fd_mask));
2570383853bSbluhm 	if (fdset == NULL)
2580383853bSbluhm 		err(1, "fdset");
259*79f7fadeSbluhm 	waiting = 0;
2600383853bSbluhm 	for (i = 0; i < procs; i++) {
2610383853bSbluhm 		FD_SET(rfds[i], fdset);
262*79f7fadeSbluhm 		waiting = 1;
2630383853bSbluhm 	}
2640383853bSbluhm 
2650383853bSbluhm 	/* wait until all child processes are waiting */
266*79f7fadeSbluhm 	while (waiting) {
2670383853bSbluhm 		tv.tv_sec = timeout;
2680383853bSbluhm 		tv.tv_usec = 0;
2690383853bSbluhm 		errno = ETIMEDOUT;
2700383853bSbluhm 		if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0)
2710383853bSbluhm 			err(1, "select");
2720383853bSbluhm 
273*79f7fadeSbluhm 		waiting = 0;
2740383853bSbluhm 		/* remove fd of children that closed their end  */
2750383853bSbluhm 		for (i = 0; i < procs; i++) {
2760383853bSbluhm 			if (rfds[i] >= 0) {
2770383853bSbluhm 				if (FD_ISSET(rfds[i], fdset)) {
2780383853bSbluhm 					if (close(rfds[i]) == -1)
279*79f7fadeSbluhm 						err(1, "close parent");
2800383853bSbluhm 					FD_CLR(rfds[i], fdset);
2810383853bSbluhm 					rfds[i] = -1;
2820383853bSbluhm 				} else {
2830383853bSbluhm 					FD_SET(rfds[i], fdset);
2840383853bSbluhm 					waiting = 1;
2850383853bSbluhm 				}
2860383853bSbluhm 			}
2870383853bSbluhm 		}
288*79f7fadeSbluhm 	}
2890383853bSbluhm 
2900383853bSbluhm 	/* kill all children simultaneously, parent exits in signal handler */
2910383853bSbluhm 	if (signal(SIGTERM, sigexit) == SIG_ERR)
2920383853bSbluhm 		err(1, "signal SIGTERM");
2930383853bSbluhm 	if (kill(-pgrp, SIGTERM) == -1)
2940383853bSbluhm 		err(1, "kill %d", -pgrp);
2950383853bSbluhm 
2960383853bSbluhm 	errx(1, "alive");
2970383853bSbluhm }
298