1*5a9dda22Sbluhm /* $OpenBSD: fork-exit.c,v 1.4 2021/05/21 20:21:10 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 1985f5c1e8Sbluhm #include <sys/mman.h> 200383853bSbluhm #include <sys/select.h> 210383853bSbluhm #include <sys/wait.h> 220383853bSbluhm 230383853bSbluhm #include <err.h> 240383853bSbluhm #include <errno.h> 250383853bSbluhm #include <fcntl.h> 260383853bSbluhm #include <limits.h> 2779f7fadeSbluhm #include <pthread.h> 280383853bSbluhm #include <signal.h> 290383853bSbluhm #include <stdio.h> 300383853bSbluhm #include <stdlib.h> 3185f5c1e8Sbluhm #include <string.h> 320383853bSbluhm #include <unistd.h> 330383853bSbluhm 340383853bSbluhm int execute = 0; 350383853bSbluhm int daemonize = 0; 3685f5c1e8Sbluhm int heap = 0; 370383853bSbluhm int procs = 1; 3885f5c1e8Sbluhm int stack = 0; 3979f7fadeSbluhm int threads = 0; 400383853bSbluhm int timeout = 30; 410383853bSbluhm 4279f7fadeSbluhm pthread_barrier_t thread_barrier; 4379f7fadeSbluhm char timeoutstr[sizeof("-2147483647")]; 4479f7fadeSbluhm 450383853bSbluhm static void __dead 460383853bSbluhm usage(void) 470383853bSbluhm { 4879f7fadeSbluhm fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n" 490383853bSbluhm " -e child execs sleep(1), default call sleep(3)\n" 5079f7fadeSbluhm " -d daemonize, use if already process group leader\n" 5185f5c1e8Sbluhm " -h heap allocate pages of heap memory, default 0\n" 520383853bSbluhm " -p procs number of processes to fork, default 1\n" 5385f5c1e8Sbluhm " -s stack allocate pages of stack memory, default 0\n" 5479f7fadeSbluhm " -t threads number of threads to create, default 0\n" 5579f7fadeSbluhm " -T timeout parent and children will exit, default 30 sec\n"); 560383853bSbluhm exit(2); 570383853bSbluhm } 580383853bSbluhm 5985f5c1e8Sbluhm static void 6085f5c1e8Sbluhm recurse_page(int depth) 6185f5c1e8Sbluhm { 6285f5c1e8Sbluhm int p[4096 / sizeof(int)]; 6385f5c1e8Sbluhm 6485f5c1e8Sbluhm if (depth == 0) 6585f5c1e8Sbluhm return; 6685f5c1e8Sbluhm p[1] = 0x9abcdef0; 6785f5c1e8Sbluhm explicit_bzero(p, sizeof(int)); 6885f5c1e8Sbluhm recurse_page(depth - 1); 6985f5c1e8Sbluhm } 7085f5c1e8Sbluhm 7185f5c1e8Sbluhm static void 7285f5c1e8Sbluhm alloc_stack(void) 7385f5c1e8Sbluhm { 7485f5c1e8Sbluhm recurse_page(stack); 7585f5c1e8Sbluhm } 7685f5c1e8Sbluhm 7785f5c1e8Sbluhm static void 7885f5c1e8Sbluhm alloc_heap(void) 7985f5c1e8Sbluhm { 8085f5c1e8Sbluhm int *p; 8185f5c1e8Sbluhm int i; 8285f5c1e8Sbluhm 8385f5c1e8Sbluhm for(i = 0; i < heap; i++) { 8485f5c1e8Sbluhm p = mmap(0, 4096, PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); 8585f5c1e8Sbluhm if (p == MAP_FAILED) 8685f5c1e8Sbluhm err(1, "mmap"); 8785f5c1e8Sbluhm p[1] = 0x12345678; 8885f5c1e8Sbluhm explicit_bzero(p, sizeof(int)); 8985f5c1e8Sbluhm } 9085f5c1e8Sbluhm } 9185f5c1e8Sbluhm 9279f7fadeSbluhm static void * __dead 9379f7fadeSbluhm run_thread(void *arg) 9479f7fadeSbluhm { 9579f7fadeSbluhm int error; 9679f7fadeSbluhm 9785f5c1e8Sbluhm if (heap) 9885f5c1e8Sbluhm alloc_heap(); 9985f5c1e8Sbluhm if (stack) 10085f5c1e8Sbluhm alloc_stack(); 10185f5c1e8Sbluhm 10279f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 10379f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 10479f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 10579f7fadeSbluhm 10679f7fadeSbluhm if (sleep(timeout) != 0) 10779f7fadeSbluhm err(1, "sleep %d", timeout); 10879f7fadeSbluhm 10979f7fadeSbluhm /* should not happen */ 11079f7fadeSbluhm _exit(0); 11179f7fadeSbluhm } 11279f7fadeSbluhm 11379f7fadeSbluhm static void 11479f7fadeSbluhm create_threads(void) 11579f7fadeSbluhm { 116*5a9dda22Sbluhm pthread_attr_t tattr; 11779f7fadeSbluhm pthread_t *thrs; 11879f7fadeSbluhm int i, error; 11979f7fadeSbluhm 120*5a9dda22Sbluhm error = pthread_attr_init(&tattr); 121*5a9dda22Sbluhm if (error) 122*5a9dda22Sbluhm errc(1, error, "pthread_attr_init"); 123*5a9dda22Sbluhm if (stack) { 124*5a9dda22Sbluhm /* thread start and function call overhead needs a bit more */ 125*5a9dda22Sbluhm error = pthread_attr_setstacksize(&tattr, 126*5a9dda22Sbluhm (stack + 2) * (4096 + 32)); 127*5a9dda22Sbluhm if (error) 128*5a9dda22Sbluhm errc(1, error, "pthread_attr_setstacksize"); 129*5a9dda22Sbluhm } 130*5a9dda22Sbluhm 13179f7fadeSbluhm error = pthread_barrier_init(&thread_barrier, NULL, threads + 1); 13279f7fadeSbluhm if (error) 133*5a9dda22Sbluhm errc(1, error, "pthread_barrier_init"); 13479f7fadeSbluhm 13579f7fadeSbluhm thrs = reallocarray(NULL, threads, sizeof(pthread_t)); 13679f7fadeSbluhm if (thrs == NULL) 13779f7fadeSbluhm err(1, "thrs"); 13879f7fadeSbluhm 13979f7fadeSbluhm for (i = 0; i < threads; i++) { 140*5a9dda22Sbluhm error = pthread_create(&thrs[i], &tattr, run_thread, NULL); 14179f7fadeSbluhm if (error) 14279f7fadeSbluhm errc(1, error, "pthread_create"); 14379f7fadeSbluhm } 14479f7fadeSbluhm 14579f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 14679f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 14779f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 14879f7fadeSbluhm 14979f7fadeSbluhm /* return to close child's pipe and sleep */ 15079f7fadeSbluhm } 15179f7fadeSbluhm 1520383853bSbluhm static void __dead 1530383853bSbluhm exec_sleep(void) 1540383853bSbluhm { 15579f7fadeSbluhm execl("/bin/sleep", "sleep", timeoutstr, NULL); 1560383853bSbluhm err(1, "exec sleep"); 1570383853bSbluhm } 1580383853bSbluhm 15979f7fadeSbluhm static void __dead 16079f7fadeSbluhm run_child(int fd) 1610383853bSbluhm { 1620383853bSbluhm /* close pipe to parent and sleep until killed */ 1630383853bSbluhm if (execute) { 1640383853bSbluhm if (fcntl(fd, F_SETFD, FD_CLOEXEC)) 1650383853bSbluhm err(1, "fcntl FD_CLOEXEC"); 1660383853bSbluhm exec_sleep(); 1670383853bSbluhm } else { 16885f5c1e8Sbluhm if (threads) { 16979f7fadeSbluhm create_threads(); 17085f5c1e8Sbluhm } else { 17185f5c1e8Sbluhm if (heap) 17285f5c1e8Sbluhm alloc_heap(); 17385f5c1e8Sbluhm if (stack) 17485f5c1e8Sbluhm alloc_stack(); 17585f5c1e8Sbluhm } 1760383853bSbluhm if (close(fd) == -1) 17779f7fadeSbluhm err(1, "close child"); 1780383853bSbluhm if (sleep(timeout) != 0) 1790383853bSbluhm err(1, "sleep %d", timeout); 1800383853bSbluhm } 18179f7fadeSbluhm 18279f7fadeSbluhm /* should not happen */ 1830383853bSbluhm _exit(0); 1840383853bSbluhm } 1850383853bSbluhm 1860383853bSbluhm static void 1870383853bSbluhm sigexit(int sig) 1880383853bSbluhm { 1890383853bSbluhm int i, status; 1900383853bSbluhm pid_t pid; 1910383853bSbluhm 1920383853bSbluhm /* all children must terminate in time */ 1930383853bSbluhm if ((int)alarm(timeout) == -1) 1940383853bSbluhm err(1, "alarm"); 1950383853bSbluhm 1960383853bSbluhm for (i = 0; i < procs; i++) { 1970383853bSbluhm pid = wait(&status); 1980383853bSbluhm if (pid == -1) 1990383853bSbluhm err(1, "wait"); 2000383853bSbluhm if (!WIFSIGNALED(status)) 2010383853bSbluhm errx(1, "child %d not killed", pid); 2020383853bSbluhm if(WTERMSIG(status) != SIGTERM) 2030383853bSbluhm errx(1, "child %d signal %d", pid, WTERMSIG(status)); 2040383853bSbluhm } 2050383853bSbluhm exit(0); 2060383853bSbluhm } 2070383853bSbluhm 2080383853bSbluhm int 2090383853bSbluhm main(int argc, char *argv[]) 2100383853bSbluhm { 2110383853bSbluhm const char *errstr; 2120383853bSbluhm int ch, i, fdmax, fdlen, *rfds, waiting; 2130383853bSbluhm fd_set *fdset; 2140383853bSbluhm pid_t pgrp; 2150383853bSbluhm struct timeval tv; 2160383853bSbluhm 21785f5c1e8Sbluhm while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) { 2180383853bSbluhm switch (ch) { 2190383853bSbluhm case 'e': 2200383853bSbluhm execute = 1; 2210383853bSbluhm break; 2220383853bSbluhm case 'd': 2230383853bSbluhm daemonize = 1; 2240383853bSbluhm break; 22585f5c1e8Sbluhm case 'h': 22685f5c1e8Sbluhm heap = strtonum(optarg, 0, INT_MAX, &errstr); 22785f5c1e8Sbluhm if (errstr != NULL) 22885f5c1e8Sbluhm errx(1, "number of heap allocations is %s: %s", 22985f5c1e8Sbluhm errstr, optarg); 23085f5c1e8Sbluhm break; 2310383853bSbluhm case 'p': 232*5a9dda22Sbluhm procs = strtonum(optarg, 0, INT_MAX / 4096, &errstr); 2330383853bSbluhm if (errstr != NULL) 2340383853bSbluhm errx(1, "number of procs is %s: %s", errstr, 2350383853bSbluhm optarg); 2360383853bSbluhm break; 23785f5c1e8Sbluhm case 's': 238*5a9dda22Sbluhm stack = strtonum(optarg, 0, 239*5a9dda22Sbluhm (INT_MAX - 2) / (4096 + 32), &errstr); 24085f5c1e8Sbluhm if (errstr != NULL) 24185f5c1e8Sbluhm errx(1, "number of stack allocations is %s: %s", 24285f5c1e8Sbluhm errstr, optarg); 24385f5c1e8Sbluhm break; 2440383853bSbluhm case 't': 24579f7fadeSbluhm threads = strtonum(optarg, 0, INT_MAX, &errstr); 24679f7fadeSbluhm if (errstr != NULL) 24779f7fadeSbluhm errx(1, "number of threads is %s: %s", errstr, 24879f7fadeSbluhm optarg); 24979f7fadeSbluhm break; 25079f7fadeSbluhm case 'T': 2510383853bSbluhm timeout = strtonum(optarg, 0, INT_MAX, &errstr); 2520383853bSbluhm if (errstr != NULL) 2530383853bSbluhm errx(1, "timeout is %s: %s", errstr, optarg); 25479f7fadeSbluhm break; 2550383853bSbluhm default: 2560383853bSbluhm usage(); 2570383853bSbluhm } 2580383853bSbluhm } 25979f7fadeSbluhm if (execute) { 26079f7fadeSbluhm int ret; 26179f7fadeSbluhm 26279f7fadeSbluhm if (threads > 0) 26379f7fadeSbluhm errx(1, "execute sleep cannot be used with threads"); 26479f7fadeSbluhm 26579f7fadeSbluhm ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout); 26679f7fadeSbluhm if (ret < 0 || (size_t)ret >= sizeof(timeoutstr)) 26779f7fadeSbluhm err(1, "snprintf"); 26879f7fadeSbluhm } 2690383853bSbluhm 2700383853bSbluhm /* become process group leader */ 27179f7fadeSbluhm if (daemonize) { 27279f7fadeSbluhm /* get rid of process leadership */ 2730383853bSbluhm switch (fork()) { 2740383853bSbluhm case -1: 2750383853bSbluhm err(1, "fork parent"); 2760383853bSbluhm case 0: 2770383853bSbluhm break; 2780383853bSbluhm default: 27979f7fadeSbluhm /* parent leaves orphan behind to do the work */ 2800383853bSbluhm _exit(0); 2810383853bSbluhm } 2820383853bSbluhm } 28379f7fadeSbluhm pgrp = setsid(); 28479f7fadeSbluhm if (pgrp == -1) { 2850383853bSbluhm if (!daemonize) 2860383853bSbluhm warnx("try -d to become process group leader"); 2870383853bSbluhm err(1, "setsid"); 2880383853bSbluhm } 2890383853bSbluhm 2900383853bSbluhm /* create pipes to keep in contact with children */ 2910383853bSbluhm rfds = reallocarray(NULL, procs, sizeof(int)); 2920383853bSbluhm if (rfds == NULL) 2930383853bSbluhm err(1, "rfds"); 2940383853bSbluhm fdmax = 0; 2950383853bSbluhm 2960383853bSbluhm /* fork child processes and pass writing end of pipe */ 2970383853bSbluhm for (i = 0; i < procs; i++) { 29879f7fadeSbluhm int pipefds[2], error; 2990383853bSbluhm 3000383853bSbluhm if (pipe(pipefds) == -1) 3010383853bSbluhm err(1, "pipe"); 3020383853bSbluhm if (fdmax < pipefds[0]) 3030383853bSbluhm fdmax = pipefds[0]; 3040383853bSbluhm rfds[i] = pipefds[0]; 30579f7fadeSbluhm 30679f7fadeSbluhm switch (fork()) { 30779f7fadeSbluhm case -1: 30879f7fadeSbluhm /* resource temporarily unavailable may happen */ 30979f7fadeSbluhm error = errno; 31079f7fadeSbluhm /* reap children, but not parent */ 31179f7fadeSbluhm signal(SIGTERM, SIG_IGN); 31279f7fadeSbluhm kill(-pgrp, SIGTERM); 31379f7fadeSbluhm errc(1, error, "fork child"); 31479f7fadeSbluhm case 0: 31579f7fadeSbluhm /* child closes reading end, read is for the parent */ 31679f7fadeSbluhm if (close(pipefds[0]) == -1) 31779f7fadeSbluhm err(1, "close read"); 31879f7fadeSbluhm run_child(pipefds[1]); 31979f7fadeSbluhm /* cannot happen */ 32079f7fadeSbluhm _exit(0); 32179f7fadeSbluhm default: 32279f7fadeSbluhm /* parent closes writing end, write is for the child */ 3230383853bSbluhm if (close(pipefds[1]) == -1) 32479f7fadeSbluhm err(1, "close write"); 32579f7fadeSbluhm break; 32679f7fadeSbluhm } 3270383853bSbluhm } 3280383853bSbluhm 3290383853bSbluhm /* create select mask with all reading ends of child pipes */ 3300383853bSbluhm fdlen = howmany(fdmax + 1, NFDBITS); 3310383853bSbluhm fdset = calloc(fdlen, sizeof(fd_mask)); 3320383853bSbluhm if (fdset == NULL) 3330383853bSbluhm err(1, "fdset"); 33479f7fadeSbluhm waiting = 0; 3350383853bSbluhm for (i = 0; i < procs; i++) { 3360383853bSbluhm FD_SET(rfds[i], fdset); 33779f7fadeSbluhm waiting = 1; 3380383853bSbluhm } 3390383853bSbluhm 3400383853bSbluhm /* wait until all child processes are waiting */ 34179f7fadeSbluhm while (waiting) { 3420383853bSbluhm tv.tv_sec = timeout; 3430383853bSbluhm tv.tv_usec = 0; 3440383853bSbluhm errno = ETIMEDOUT; 3450383853bSbluhm if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 3460383853bSbluhm err(1, "select"); 3470383853bSbluhm 34879f7fadeSbluhm waiting = 0; 3490383853bSbluhm /* remove fd of children that closed their end */ 3500383853bSbluhm for (i = 0; i < procs; i++) { 3510383853bSbluhm if (rfds[i] >= 0) { 3520383853bSbluhm if (FD_ISSET(rfds[i], fdset)) { 3530383853bSbluhm if (close(rfds[i]) == -1) 35479f7fadeSbluhm err(1, "close parent"); 3550383853bSbluhm FD_CLR(rfds[i], fdset); 3560383853bSbluhm rfds[i] = -1; 3570383853bSbluhm } else { 3580383853bSbluhm FD_SET(rfds[i], fdset); 3590383853bSbluhm waiting = 1; 3600383853bSbluhm } 3610383853bSbluhm } 3620383853bSbluhm } 36379f7fadeSbluhm } 3640383853bSbluhm 3650383853bSbluhm /* kill all children simultaneously, parent exits in signal handler */ 3660383853bSbluhm if (signal(SIGTERM, sigexit) == SIG_ERR) 3670383853bSbluhm err(1, "signal SIGTERM"); 3680383853bSbluhm if (kill(-pgrp, SIGTERM) == -1) 3690383853bSbluhm err(1, "kill %d", -pgrp); 3700383853bSbluhm 3710383853bSbluhm errx(1, "alive"); 3720383853bSbluhm } 373