1 /* $OpenBSD: fork-exit.c,v 1.4 2021/05/21 20:21:10 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 + 32)); 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 if ((int)alarm(timeout) == -1) 194 err(1, "alarm"); 195 196 for (i = 0; i < procs; i++) { 197 pid = wait(&status); 198 if (pid == -1) 199 err(1, "wait"); 200 if (!WIFSIGNALED(status)) 201 errx(1, "child %d not killed", pid); 202 if(WTERMSIG(status) != SIGTERM) 203 errx(1, "child %d signal %d", pid, WTERMSIG(status)); 204 } 205 exit(0); 206 } 207 208 int 209 main(int argc, char *argv[]) 210 { 211 const char *errstr; 212 int ch, i, fdmax, fdlen, *rfds, waiting; 213 fd_set *fdset; 214 pid_t pgrp; 215 struct timeval tv; 216 217 while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) { 218 switch (ch) { 219 case 'e': 220 execute = 1; 221 break; 222 case 'd': 223 daemonize = 1; 224 break; 225 case 'h': 226 heap = strtonum(optarg, 0, INT_MAX, &errstr); 227 if (errstr != NULL) 228 errx(1, "number of heap allocations is %s: %s", 229 errstr, optarg); 230 break; 231 case 'p': 232 procs = strtonum(optarg, 0, INT_MAX / 4096, &errstr); 233 if (errstr != NULL) 234 errx(1, "number of procs is %s: %s", errstr, 235 optarg); 236 break; 237 case 's': 238 stack = strtonum(optarg, 0, 239 (INT_MAX - 2) / (4096 + 32), &errstr); 240 if (errstr != NULL) 241 errx(1, "number of stack allocations is %s: %s", 242 errstr, optarg); 243 break; 244 case 't': 245 threads = strtonum(optarg, 0, INT_MAX, &errstr); 246 if (errstr != NULL) 247 errx(1, "number of threads is %s: %s", errstr, 248 optarg); 249 break; 250 case 'T': 251 timeout = strtonum(optarg, 0, INT_MAX, &errstr); 252 if (errstr != NULL) 253 errx(1, "timeout is %s: %s", errstr, optarg); 254 break; 255 default: 256 usage(); 257 } 258 } 259 if (execute) { 260 int ret; 261 262 if (threads > 0) 263 errx(1, "execute sleep cannot be used with threads"); 264 265 ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout); 266 if (ret < 0 || (size_t)ret >= sizeof(timeoutstr)) 267 err(1, "snprintf"); 268 } 269 270 /* become process group leader */ 271 if (daemonize) { 272 /* get rid of process leadership */ 273 switch (fork()) { 274 case -1: 275 err(1, "fork parent"); 276 case 0: 277 break; 278 default: 279 /* parent leaves orphan behind to do the work */ 280 _exit(0); 281 } 282 } 283 pgrp = setsid(); 284 if (pgrp == -1) { 285 if (!daemonize) 286 warnx("try -d to become process group leader"); 287 err(1, "setsid"); 288 } 289 290 /* create pipes to keep in contact with children */ 291 rfds = reallocarray(NULL, procs, sizeof(int)); 292 if (rfds == NULL) 293 err(1, "rfds"); 294 fdmax = 0; 295 296 /* fork child processes and pass writing end of pipe */ 297 for (i = 0; i < procs; i++) { 298 int pipefds[2], error; 299 300 if (pipe(pipefds) == -1) 301 err(1, "pipe"); 302 if (fdmax < pipefds[0]) 303 fdmax = pipefds[0]; 304 rfds[i] = pipefds[0]; 305 306 switch (fork()) { 307 case -1: 308 /* resource temporarily unavailable may happen */ 309 error = errno; 310 /* reap children, but not parent */ 311 signal(SIGTERM, SIG_IGN); 312 kill(-pgrp, SIGTERM); 313 errc(1, error, "fork child"); 314 case 0: 315 /* child closes reading end, read is for the parent */ 316 if (close(pipefds[0]) == -1) 317 err(1, "close read"); 318 run_child(pipefds[1]); 319 /* cannot happen */ 320 _exit(0); 321 default: 322 /* parent closes writing end, write is for the child */ 323 if (close(pipefds[1]) == -1) 324 err(1, "close write"); 325 break; 326 } 327 } 328 329 /* create select mask with all reading ends of child pipes */ 330 fdlen = howmany(fdmax + 1, NFDBITS); 331 fdset = calloc(fdlen, sizeof(fd_mask)); 332 if (fdset == NULL) 333 err(1, "fdset"); 334 waiting = 0; 335 for (i = 0; i < procs; i++) { 336 FD_SET(rfds[i], fdset); 337 waiting = 1; 338 } 339 340 /* wait until all child processes are waiting */ 341 while (waiting) { 342 tv.tv_sec = timeout; 343 tv.tv_usec = 0; 344 errno = ETIMEDOUT; 345 if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 346 err(1, "select"); 347 348 waiting = 0; 349 /* remove fd of children that closed their end */ 350 for (i = 0; i < procs; i++) { 351 if (rfds[i] >= 0) { 352 if (FD_ISSET(rfds[i], fdset)) { 353 if (close(rfds[i]) == -1) 354 err(1, "close parent"); 355 FD_CLR(rfds[i], fdset); 356 rfds[i] = -1; 357 } else { 358 FD_SET(rfds[i], fdset); 359 waiting = 1; 360 } 361 } 362 } 363 } 364 365 /* kill all children simultaneously, parent exits in signal handler */ 366 if (signal(SIGTERM, sigexit) == SIG_ERR) 367 err(1, "signal SIGTERM"); 368 if (kill(-pgrp, SIGTERM) == -1) 369 err(1, "kill %d", -pgrp); 370 371 errx(1, "alive"); 372 } 373