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