xref: /openbsd-src/regress/sys/kern/fork-exit/fork-exit.c (revision 0383853b786e4046dad8884064c8759a7ac49184)
1 /*	$OpenBSD: fork-exit.c,v 1.1 2021/04/28 17:59:53 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 <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 
31 int execute = 0;
32 int daemonize = 0;
33 int procs = 1;
34 int timeout = 30;
35 
36 static void __dead
37 usage(void)
38 {
39 	fprintf(stderr, "fork-exit [-ed] [-p procs] [-t timeout]\n"
40 	    "    -e          child execs sleep(1), default call sleep(3)\n"
41 	    "    -d          daemonize, if already process group leader\n"
42 	    "    -p procs    number of processes to fork, default 1\n"
43 	    "    -t timeout  parent and children will exit, default 30 sec\n");
44 	exit(2);
45 }
46 
47 static void __dead
48 exec_sleep(void)
49 {
50 	execl("/bin/sleep", "sleep", "30", NULL);
51 	err(1, "exec sleep");
52 }
53 
54 static void
55 fork_sleep(int fd)
56 {
57 	switch (fork()) {
58 	case -1:
59 		err(1, "fork");
60 	case 0:
61 		break;
62 	default:
63 		return;
64 	}
65 	/* close pipe to parent and sleep until killed */
66 	if (execute) {
67 		if (fcntl(fd, F_SETFD, FD_CLOEXEC))
68 			err(1, "fcntl FD_CLOEXEC");
69 		exec_sleep();
70 	} else {
71 		if (close(fd) == -1)
72 			err(1, "close write");
73 		if (sleep(timeout) != 0)
74 			err(1, "sleep %d", timeout);
75 	}
76 	_exit(0);
77 }
78 
79 static void
80 sigexit(int sig)
81 {
82 	int i, status;
83 	pid_t pid;
84 
85 	/* all children must terminate in time */
86 	if ((int)alarm(timeout) == -1)
87 		err(1, "alarm");
88 
89 	for (i = 0; i < procs; i++) {
90 		pid = wait(&status);
91 		if (pid == -1)
92 			err(1, "wait");
93 		if (!WIFSIGNALED(status))
94 			errx(1, "child %d not killed", pid);
95 		if(WTERMSIG(status) != SIGTERM)
96 			errx(1, "child %d signal %d", pid, WTERMSIG(status));
97 	}
98 	exit(0);
99 }
100 
101 int
102 main(int argc, char *argv[])
103 {
104 	const char *errstr;
105 	int ch, i, fdmax, fdlen, *rfds, waiting;
106 	fd_set *fdset;
107 	pid_t pgrp;
108 	struct timeval tv;
109 
110 	while ((ch = getopt(argc, argv, "edp:t:")) != -1) {
111 	switch (ch) {
112 		case 'e':
113 			execute = 1;
114 			break;
115 		case 'd':
116 			daemonize = 1;
117 			break;
118 		case 'p':
119 			procs = strtonum(optarg, 0, INT_MAX, &errstr);
120 			if (errstr != NULL)
121 				errx(1, "number of procs is %s: %s", errstr,
122 				    optarg);
123 			break;
124 		case 't':
125 			timeout = strtonum(optarg, 0, INT_MAX, &errstr);
126 			if (errstr != NULL)
127 				errx(1, "timeout is %s: %s", errstr, optarg);
128 		default:
129 			usage();
130 		}
131 	}
132 
133 	/* become process group leader */
134 	pgrp = setsid();
135 	if (pgrp == -1) {
136 		if (errno == EPERM && daemonize) {
137 			/* get rid of leadership */
138 			switch (fork()) {
139 			case -1:
140 				err(1, "fork parent");
141 			case 0:
142 				/* try again */
143 				pgrp = setsid();
144 				break;
145 			default:
146 				_exit(0);
147 			}
148 		}
149 		if (!daemonize)
150 			warnx("try -d to become process group leader");
151 		if (pgrp == -1)
152 			err(1, "setsid");
153 	}
154 
155 	/* create pipes to keep in contact with children */
156 	rfds = reallocarray(NULL, procs, sizeof(int));
157 	if (rfds == NULL)
158 		err(1, "rfds");
159 	fdmax = 0;
160 
161 	/* fork child processes and pass writing end of pipe */
162 	for (i = 0; i < procs; i++) {
163 		int pipefds[2];
164 
165 		if (pipe(pipefds) == -1)
166 			err(1, "pipe");
167 		if (fdmax < pipefds[0])
168 			fdmax = pipefds[0];
169 		rfds[i] = pipefds[0];
170 		fork_sleep(pipefds[1]);
171 		if (close(pipefds[1]) == -1)
172 			err(1, "close parent");
173 	}
174 
175 	/* create select mask with all reading ends of child pipes */
176 	fdlen = howmany(fdmax + 1, NFDBITS);
177 	fdset = calloc(fdlen, sizeof(fd_mask));
178 	if (fdset == NULL)
179 		err(1, "fdset");
180 	for (i = 0; i < procs; i++) {
181 		FD_SET(rfds[i], fdset);
182 	}
183 
184 	/* wait until all child processes are waiting */
185 	do  {
186 		waiting = 0;
187 		tv.tv_sec = timeout;
188 		tv.tv_usec = 0;
189 		errno = ETIMEDOUT;
190 		if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0)
191 			err(1, "select");
192 
193 		/* remove fd of children that closed their end  */
194 		for (i = 0; i < procs; i++) {
195 			if (rfds[i] >= 0) {
196 				if (FD_ISSET(rfds[i], fdset)) {
197 					if (close(rfds[i]) == -1)
198 						err(1, "close read");
199 					FD_CLR(rfds[i], fdset);
200 					rfds[i] = -1;
201 				} else {
202 					FD_SET(rfds[i], fdset);
203 					waiting = 1;
204 				}
205 			}
206 		}
207 	} while (waiting);
208 
209 	/* kill all children simultaneously, parent exits in signal handler */
210 	if (signal(SIGTERM, sigexit) == SIG_ERR)
211 		err(1, "signal SIGTERM");
212 	if (kill(-pgrp, SIGTERM) == -1)
213 		err(1, "kill %d", -pgrp);
214 
215 	errx(1, "alive");
216 }
217