xref: /openbsd-src/regress/sys/kern/fork-exit/fork-exit.c (revision 79f7fade5638288ad654be0a84ae59b0956f4313)
1 /*	$OpenBSD: fork-exit.c,v 1.2 2021/04/29 13:39:22 bluhm 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/select.h>
20 #include <sys/wait.h>
21 
22 #include <err.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <pthread.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 
32 int execute = 0;
33 int daemonize = 0;
34 int procs = 1;
35 int threads = 0;
36 int timeout = 30;
37 
38 pthread_barrier_t thread_barrier;
39 char timeoutstr[sizeof("-2147483647")];
40 
41 static void __dead
42 usage(void)
43 {
44 	fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n"
45 	    "    -e          child execs sleep(1), default call sleep(3)\n"
46 	    "    -d          daemonize, use if already process group leader\n"
47 	    "    -p procs    number of processes to fork, default 1\n"
48 	    "    -t threads   number of threads to create, default 0\n"
49 	    "    -T timeout  parent and children will exit, default 30 sec\n");
50 	exit(2);
51 }
52 
53 static void * __dead
54 run_thread(void *arg)
55 {
56 	int error;
57 
58 	error = pthread_barrier_wait(&thread_barrier);
59 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
60 		errc(1, error, "pthread_barrier_wait");
61 
62 	if (sleep(timeout) != 0)
63 		err(1, "sleep %d", timeout);
64 
65 	/* should not happen */
66 	_exit(0);
67 }
68 
69 static void
70 create_threads(void)
71 {
72 	pthread_t *thrs;
73 	int i, error;
74 
75 	error = pthread_barrier_init(&thread_barrier, NULL, threads + 1);
76 	if (error)
77 		errc(1, errno, "pthread_barrier_init");
78 
79 	thrs = reallocarray(NULL, threads, sizeof(pthread_t));
80 	if (thrs == NULL)
81 		err(1, "thrs");
82 
83 	for (i = 0; i < threads; i++) {
84 		error = pthread_create(&thrs[i], NULL, run_thread, NULL);
85 		if (error)
86 			errc(1, error, "pthread_create");
87 	}
88 
89 	error = pthread_barrier_wait(&thread_barrier);
90 	if (error && error != PTHREAD_BARRIER_SERIAL_THREAD)
91 		errc(1, error, "pthread_barrier_wait");
92 
93 	/* return to close child's pipe and sleep */
94 }
95 
96 static void __dead
97 exec_sleep(void)
98 {
99 	execl("/bin/sleep", "sleep", timeoutstr, NULL);
100 	err(1, "exec sleep");
101 }
102 
103 static void __dead
104 run_child(int fd)
105 {
106 	/* close pipe to parent and sleep until killed */
107 	if (execute) {
108 		if (fcntl(fd, F_SETFD, FD_CLOEXEC))
109 			err(1, "fcntl FD_CLOEXEC");
110 		exec_sleep();
111 	} else {
112 		if (threads)
113 			create_threads();
114 		if (close(fd) == -1)
115 			err(1, "close child");
116 		if (sleep(timeout) != 0)
117 			err(1, "sleep %d", timeout);
118 	}
119 
120 	/* should not happen */
121 	_exit(0);
122 }
123 
124 static void
125 sigexit(int sig)
126 {
127 	int i, status;
128 	pid_t pid;
129 
130 	/* all children must terminate in time */
131 	if ((int)alarm(timeout) == -1)
132 		err(1, "alarm");
133 
134 	for (i = 0; i < procs; i++) {
135 		pid = wait(&status);
136 		if (pid == -1)
137 			err(1, "wait");
138 		if (!WIFSIGNALED(status))
139 			errx(1, "child %d not killed", pid);
140 		if(WTERMSIG(status) != SIGTERM)
141 			errx(1, "child %d signal %d", pid, WTERMSIG(status));
142 	}
143 	exit(0);
144 }
145 
146 int
147 main(int argc, char *argv[])
148 {
149 	const char *errstr;
150 	int ch, i, fdmax, fdlen, *rfds, waiting;
151 	fd_set *fdset;
152 	pid_t pgrp;
153 	struct timeval tv;
154 
155 	while ((ch = getopt(argc, argv, "edp:T:t:")) != -1) {
156 	switch (ch) {
157 		case 'e':
158 			execute = 1;
159 			break;
160 		case 'd':
161 			daemonize = 1;
162 			break;
163 		case 'p':
164 			procs = strtonum(optarg, 0, INT_MAX, &errstr);
165 			if (errstr != NULL)
166 				errx(1, "number of procs is %s: %s", errstr,
167 				    optarg);
168 			break;
169 		case 't':
170 			threads = strtonum(optarg, 0, INT_MAX, &errstr);
171 			if (errstr != NULL)
172 				errx(1, "number of threads is %s: %s", errstr,
173 				    optarg);
174 			break;
175 		case 'T':
176 			timeout = strtonum(optarg, 0, INT_MAX, &errstr);
177 			if (errstr != NULL)
178 				errx(1, "timeout is %s: %s", errstr, optarg);
179 			break;
180 		default:
181 			usage();
182 		}
183 	}
184 	if (execute) {
185 		int ret;
186 
187 		if (threads > 0)
188 			errx(1, "execute sleep cannot be used with threads");
189 
190 		ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout);
191 		if (ret < 0 || (size_t)ret >= sizeof(timeoutstr))
192 			err(1, "snprintf");
193 	}
194 
195 	/* become process group leader */
196 	if (daemonize) {
197 		/* get rid of process leadership */
198 		switch (fork()) {
199 		case -1:
200 			err(1, "fork parent");
201 		case 0:
202 			break;
203 		default:
204 			/* parent leaves orphan behind to do the work */
205 			_exit(0);
206 		}
207 	}
208 	pgrp = setsid();
209 	if (pgrp == -1) {
210 		if (!daemonize)
211 			warnx("try -d to become process group leader");
212 		err(1, "setsid");
213 	}
214 
215 	/* create pipes to keep in contact with children */
216 	rfds = reallocarray(NULL, procs, sizeof(int));
217 	if (rfds == NULL)
218 		err(1, "rfds");
219 	fdmax = 0;
220 
221 	/* fork child processes and pass writing end of pipe */
222 	for (i = 0; i < procs; i++) {
223 		int pipefds[2], error;
224 
225 		if (pipe(pipefds) == -1)
226 			err(1, "pipe");
227 		if (fdmax < pipefds[0])
228 			fdmax = pipefds[0];
229 		rfds[i] = pipefds[0];
230 
231 		switch (fork()) {
232 		case -1:
233 			/* resource temporarily unavailable may happen */
234 			error = errno;
235 			/* reap children, but not parent */
236 			signal(SIGTERM, SIG_IGN);
237 			kill(-pgrp, SIGTERM);
238 			errc(1, error, "fork child");
239 		case 0:
240 			/* child closes reading end, read is for the parent */
241 			if (close(pipefds[0]) == -1)
242 				err(1, "close read");
243 			run_child(pipefds[1]);
244 			/* cannot happen */
245 			_exit(0);
246 		default:
247 			/* parent closes writing end, write is for the child */
248 			if (close(pipefds[1]) == -1)
249 				err(1, "close write");
250 			break;
251 		}
252 	}
253 
254 	/* create select mask with all reading ends of child pipes */
255 	fdlen = howmany(fdmax + 1, NFDBITS);
256 	fdset = calloc(fdlen, sizeof(fd_mask));
257 	if (fdset == NULL)
258 		err(1, "fdset");
259 	waiting = 0;
260 	for (i = 0; i < procs; i++) {
261 		FD_SET(rfds[i], fdset);
262 		waiting = 1;
263 	}
264 
265 	/* wait until all child processes are waiting */
266 	while (waiting) {
267 		tv.tv_sec = timeout;
268 		tv.tv_usec = 0;
269 		errno = ETIMEDOUT;
270 		if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0)
271 			err(1, "select");
272 
273 		waiting = 0;
274 		/* remove fd of children that closed their end  */
275 		for (i = 0; i < procs; i++) {
276 			if (rfds[i] >= 0) {
277 				if (FD_ISSET(rfds[i], fdset)) {
278 					if (close(rfds[i]) == -1)
279 						err(1, "close parent");
280 					FD_CLR(rfds[i], fdset);
281 					rfds[i] = -1;
282 				} else {
283 					FD_SET(rfds[i], fdset);
284 					waiting = 1;
285 				}
286 			}
287 		}
288 	}
289 
290 	/* kill all children simultaneously, parent exits in signal handler */
291 	if (signal(SIGTERM, sigexit) == SIG_ERR)
292 		err(1, "signal SIGTERM");
293 	if (kill(-pgrp, SIGTERM) == -1)
294 		err(1, "kill %d", -pgrp);
295 
296 	errx(1, "alive");
297 }
298