1*881e535eSclaudio /* $OpenBSD: fork-exit.c,v 1.8 2024/08/07 18:25:39 claudio 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 42*881e535eSclaudio int pagesize; 43*881e535eSclaudio 4479f7fadeSbluhm pthread_barrier_t thread_barrier; 4579f7fadeSbluhm char timeoutstr[sizeof("-2147483647")]; 4679f7fadeSbluhm 470383853bSbluhm static void __dead 480383853bSbluhm usage(void) 490383853bSbluhm { 5079f7fadeSbluhm fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n" 510383853bSbluhm " -e child execs sleep(1), default call sleep(3)\n" 5279f7fadeSbluhm " -d daemonize, use if already process group leader\n" 53*881e535eSclaudio " -h heap allocate number of kB of heap memory, default 0\n" 540383853bSbluhm " -p procs number of processes to fork, default 1\n" 55*881e535eSclaudio " -s stack allocate number of kB of stack memory, default 0\n" 5679f7fadeSbluhm " -t threads number of threads to create, default 0\n" 5779f7fadeSbluhm " -T timeout parent and children will exit, default 30 sec\n"); 580383853bSbluhm exit(2); 590383853bSbluhm } 600383853bSbluhm 6185f5c1e8Sbluhm static void 6285f5c1e8Sbluhm recurse_page(int depth) 6385f5c1e8Sbluhm { 6485f5c1e8Sbluhm int p[4096 / sizeof(int)]; 6585f5c1e8Sbluhm 6685f5c1e8Sbluhm if (depth == 0) 6785f5c1e8Sbluhm return; 6885f5c1e8Sbluhm p[1] = 0x9abcdef0; 6985f5c1e8Sbluhm explicit_bzero(p, sizeof(int)); 7085f5c1e8Sbluhm recurse_page(depth - 1); 7185f5c1e8Sbluhm } 7285f5c1e8Sbluhm 7385f5c1e8Sbluhm static void 7485f5c1e8Sbluhm alloc_stack(void) 7585f5c1e8Sbluhm { 76*881e535eSclaudio recurse_page((stack * 1024) / (4096 + 200)); 7785f5c1e8Sbluhm } 7885f5c1e8Sbluhm 7985f5c1e8Sbluhm static void 8085f5c1e8Sbluhm alloc_heap(void) 8185f5c1e8Sbluhm { 8285f5c1e8Sbluhm int *p; 8385f5c1e8Sbluhm int i; 8485f5c1e8Sbluhm 85*881e535eSclaudio for (i = 0; i < heap / (pagesize / 1024); i++) { 86*881e535eSclaudio p = mmap(0, pagesize, PROT_WRITE|PROT_READ, 87*881e535eSclaudio MAP_SHARED|MAP_ANON, -1, 0); 8885f5c1e8Sbluhm if (p == MAP_FAILED) 8985f5c1e8Sbluhm err(1, "mmap"); 9085f5c1e8Sbluhm p[1] = 0x12345678; 9185f5c1e8Sbluhm explicit_bzero(p, sizeof(int)); 9285f5c1e8Sbluhm } 9385f5c1e8Sbluhm } 9485f5c1e8Sbluhm 9579f7fadeSbluhm static void * __dead 9679f7fadeSbluhm run_thread(void *arg) 9779f7fadeSbluhm { 9879f7fadeSbluhm int error; 9979f7fadeSbluhm 10085f5c1e8Sbluhm if (heap) 10185f5c1e8Sbluhm alloc_heap(); 10285f5c1e8Sbluhm if (stack) 10385f5c1e8Sbluhm alloc_stack(); 10485f5c1e8Sbluhm 10579f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 10679f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 10779f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 10879f7fadeSbluhm 10979f7fadeSbluhm if (sleep(timeout) != 0) 11079f7fadeSbluhm err(1, "sleep %d", timeout); 11179f7fadeSbluhm 11279f7fadeSbluhm /* should not happen */ 11379f7fadeSbluhm _exit(0); 11479f7fadeSbluhm } 11579f7fadeSbluhm 11679f7fadeSbluhm static void 11779f7fadeSbluhm create_threads(void) 11879f7fadeSbluhm { 1195a9dda22Sbluhm pthread_attr_t tattr; 12079f7fadeSbluhm pthread_t *thrs; 12179f7fadeSbluhm int i, error; 12279f7fadeSbluhm 1235a9dda22Sbluhm error = pthread_attr_init(&tattr); 1245a9dda22Sbluhm if (error) 1255a9dda22Sbluhm errc(1, error, "pthread_attr_init"); 1265a9dda22Sbluhm if (stack) { 1275a9dda22Sbluhm /* thread start and function call overhead needs a bit more */ 1285a9dda22Sbluhm error = pthread_attr_setstacksize(&tattr, 129*881e535eSclaudio (stack + 2) * (1024ULL + 50)); 1305a9dda22Sbluhm if (error) 1315a9dda22Sbluhm errc(1, error, "pthread_attr_setstacksize"); 1325a9dda22Sbluhm } 1335a9dda22Sbluhm 13479f7fadeSbluhm error = pthread_barrier_init(&thread_barrier, NULL, threads + 1); 13579f7fadeSbluhm if (error) 1365a9dda22Sbluhm errc(1, error, "pthread_barrier_init"); 13779f7fadeSbluhm 13879f7fadeSbluhm thrs = reallocarray(NULL, threads, sizeof(pthread_t)); 13979f7fadeSbluhm if (thrs == NULL) 14079f7fadeSbluhm err(1, "thrs"); 14179f7fadeSbluhm 14279f7fadeSbluhm for (i = 0; i < threads; i++) { 1435a9dda22Sbluhm error = pthread_create(&thrs[i], &tattr, run_thread, NULL); 14479f7fadeSbluhm if (error) 14579f7fadeSbluhm errc(1, error, "pthread_create"); 14679f7fadeSbluhm } 14779f7fadeSbluhm 14879f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 14979f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 15079f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 15179f7fadeSbluhm 15279f7fadeSbluhm /* return to close child's pipe and sleep */ 15379f7fadeSbluhm } 15479f7fadeSbluhm 1550383853bSbluhm static void __dead 1560383853bSbluhm exec_sleep(void) 1570383853bSbluhm { 15879f7fadeSbluhm execl("/bin/sleep", "sleep", timeoutstr, NULL); 1590383853bSbluhm err(1, "exec sleep"); 1600383853bSbluhm } 1610383853bSbluhm 16279f7fadeSbluhm static void __dead 16379f7fadeSbluhm run_child(int fd) 1640383853bSbluhm { 1650383853bSbluhm /* close pipe to parent and sleep until killed */ 1660383853bSbluhm if (execute) { 1670383853bSbluhm if (fcntl(fd, F_SETFD, FD_CLOEXEC)) 1680383853bSbluhm err(1, "fcntl FD_CLOEXEC"); 1690383853bSbluhm exec_sleep(); 1700383853bSbluhm } else { 17185f5c1e8Sbluhm if (threads) { 17279f7fadeSbluhm create_threads(); 17385f5c1e8Sbluhm } else { 17485f5c1e8Sbluhm if (heap) 17585f5c1e8Sbluhm alloc_heap(); 17685f5c1e8Sbluhm if (stack) 17785f5c1e8Sbluhm alloc_stack(); 17885f5c1e8Sbluhm } 1790383853bSbluhm if (close(fd) == -1) 18079f7fadeSbluhm err(1, "close child"); 1810383853bSbluhm if (sleep(timeout) != 0) 1820383853bSbluhm err(1, "sleep %d", timeout); 1830383853bSbluhm } 18479f7fadeSbluhm 18579f7fadeSbluhm /* should not happen */ 1860383853bSbluhm _exit(0); 1870383853bSbluhm } 1880383853bSbluhm 1890383853bSbluhm static void 1900383853bSbluhm sigexit(int sig) 1910383853bSbluhm { 1920383853bSbluhm int i, status; 1930383853bSbluhm pid_t pid; 1940383853bSbluhm 1950383853bSbluhm /* all children must terminate in time */ 1965a9255e7Sbluhm alarm(timeout); 1970383853bSbluhm 1980383853bSbluhm for (i = 0; i < procs; i++) { 1990383853bSbluhm pid = wait(&status); 2000383853bSbluhm if (pid == -1) 2010383853bSbluhm err(1, "wait"); 2020383853bSbluhm if (!WIFSIGNALED(status)) 2030383853bSbluhm errx(1, "child %d not killed", pid); 2040383853bSbluhm if(WTERMSIG(status) != SIGTERM) 2050383853bSbluhm errx(1, "child %d signal %d", pid, WTERMSIG(status)); 2060383853bSbluhm } 2070383853bSbluhm exit(0); 2080383853bSbluhm } 2090383853bSbluhm 2100383853bSbluhm int 2110383853bSbluhm main(int argc, char *argv[]) 2120383853bSbluhm { 2130383853bSbluhm const char *errstr; 2140383853bSbluhm int ch, i, fdmax, fdlen, *rfds, waiting; 2150383853bSbluhm fd_set *fdset; 2160383853bSbluhm pid_t pgrp; 2170383853bSbluhm struct timeval tv; 2180383853bSbluhm 219*881e535eSclaudio pagesize = sysconf(_SC_PAGESIZE); 220*881e535eSclaudio 22185f5c1e8Sbluhm while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) { 2220383853bSbluhm switch (ch) { 2230383853bSbluhm case 'e': 2240383853bSbluhm execute = 1; 2250383853bSbluhm break; 2260383853bSbluhm case 'd': 2270383853bSbluhm daemonize = 1; 2280383853bSbluhm break; 22985f5c1e8Sbluhm case 'h': 23085f5c1e8Sbluhm heap = strtonum(optarg, 0, INT_MAX, &errstr); 23185f5c1e8Sbluhm if (errstr != NULL) 23285f5c1e8Sbluhm errx(1, "number of heap allocations is %s: %s", 23385f5c1e8Sbluhm errstr, optarg); 23485f5c1e8Sbluhm break; 2350383853bSbluhm case 'p': 236*881e535eSclaudio procs = strtonum(optarg, 0, INT_MAX / pagesize, 237*881e535eSclaudio &errstr); 2380383853bSbluhm if (errstr != NULL) 2390383853bSbluhm errx(1, "number of procs is %s: %s", errstr, 2400383853bSbluhm optarg); 2410383853bSbluhm break; 24285f5c1e8Sbluhm case 's': 2435a9dda22Sbluhm stack = strtonum(optarg, 0, 244*881e535eSclaudio (INT_MAX / (1024 + 50)) - 2, &errstr); 24585f5c1e8Sbluhm if (errstr != NULL) 24685f5c1e8Sbluhm errx(1, "number of stack allocations is %s: %s", 24785f5c1e8Sbluhm errstr, optarg); 24885f5c1e8Sbluhm break; 2490383853bSbluhm case 't': 25079f7fadeSbluhm threads = strtonum(optarg, 0, INT_MAX, &errstr); 25179f7fadeSbluhm if (errstr != NULL) 25279f7fadeSbluhm errx(1, "number of threads is %s: %s", errstr, 25379f7fadeSbluhm optarg); 25479f7fadeSbluhm break; 25579f7fadeSbluhm case 'T': 2560383853bSbluhm timeout = strtonum(optarg, 0, INT_MAX, &errstr); 2570383853bSbluhm if (errstr != NULL) 2580383853bSbluhm errx(1, "timeout is %s: %s", errstr, optarg); 25979f7fadeSbluhm break; 2600383853bSbluhm default: 2610383853bSbluhm usage(); 2620383853bSbluhm } 2630383853bSbluhm } 26479f7fadeSbluhm if (execute) { 26579f7fadeSbluhm int ret; 26679f7fadeSbluhm 26779f7fadeSbluhm if (threads > 0) 26879f7fadeSbluhm errx(1, "execute sleep cannot be used with threads"); 26979f7fadeSbluhm 27079f7fadeSbluhm ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout); 27179f7fadeSbluhm if (ret < 0 || (size_t)ret >= sizeof(timeoutstr)) 27279f7fadeSbluhm err(1, "snprintf"); 27379f7fadeSbluhm } 2740383853bSbluhm 2750383853bSbluhm /* become process group leader */ 27679f7fadeSbluhm if (daemonize) { 27779f7fadeSbluhm /* get rid of process leadership */ 2780383853bSbluhm switch (fork()) { 2790383853bSbluhm case -1: 2800383853bSbluhm err(1, "fork parent"); 2810383853bSbluhm case 0: 2820383853bSbluhm break; 2830383853bSbluhm default: 28479f7fadeSbluhm /* parent leaves orphan behind to do the work */ 2850383853bSbluhm _exit(0); 2860383853bSbluhm } 2870383853bSbluhm } 28879f7fadeSbluhm pgrp = setsid(); 28979f7fadeSbluhm if (pgrp == -1) { 2900383853bSbluhm if (!daemonize) 2910383853bSbluhm warnx("try -d to become process group leader"); 2920383853bSbluhm err(1, "setsid"); 2930383853bSbluhm } 2940383853bSbluhm 2950383853bSbluhm /* create pipes to keep in contact with children */ 2960383853bSbluhm rfds = reallocarray(NULL, procs, sizeof(int)); 2970383853bSbluhm if (rfds == NULL) 2980383853bSbluhm err(1, "rfds"); 2990383853bSbluhm fdmax = 0; 3000383853bSbluhm 3010383853bSbluhm /* fork child processes and pass writing end of pipe */ 3020383853bSbluhm for (i = 0; i < procs; i++) { 30379f7fadeSbluhm int pipefds[2], error; 3040383853bSbluhm 3050383853bSbluhm if (pipe(pipefds) == -1) 3060383853bSbluhm err(1, "pipe"); 3070383853bSbluhm if (fdmax < pipefds[0]) 3080383853bSbluhm fdmax = pipefds[0]; 3090383853bSbluhm rfds[i] = pipefds[0]; 31079f7fadeSbluhm 31179f7fadeSbluhm switch (fork()) { 31279f7fadeSbluhm case -1: 31379f7fadeSbluhm /* resource temporarily unavailable may happen */ 31479f7fadeSbluhm error = errno; 31579f7fadeSbluhm /* reap children, but not parent */ 31679f7fadeSbluhm signal(SIGTERM, SIG_IGN); 31779f7fadeSbluhm kill(-pgrp, SIGTERM); 31879f7fadeSbluhm errc(1, error, "fork child"); 31979f7fadeSbluhm case 0: 32079f7fadeSbluhm /* child closes reading end, read is for the parent */ 32179f7fadeSbluhm if (close(pipefds[0]) == -1) 32279f7fadeSbluhm err(1, "close read"); 32379f7fadeSbluhm run_child(pipefds[1]); 32479f7fadeSbluhm /* cannot happen */ 32579f7fadeSbluhm _exit(0); 32679f7fadeSbluhm default: 32779f7fadeSbluhm /* parent closes writing end, write is for the child */ 3280383853bSbluhm if (close(pipefds[1]) == -1) 32979f7fadeSbluhm err(1, "close write"); 33079f7fadeSbluhm break; 33179f7fadeSbluhm } 3320383853bSbluhm } 3330383853bSbluhm 3340383853bSbluhm /* create select mask with all reading ends of child pipes */ 3350383853bSbluhm fdlen = howmany(fdmax + 1, NFDBITS); 3360383853bSbluhm fdset = calloc(fdlen, sizeof(fd_mask)); 3370383853bSbluhm if (fdset == NULL) 3380383853bSbluhm err(1, "fdset"); 33979f7fadeSbluhm waiting = 0; 3400383853bSbluhm for (i = 0; i < procs; i++) { 3410383853bSbluhm FD_SET(rfds[i], fdset); 34279f7fadeSbluhm waiting = 1; 3430383853bSbluhm } 3440383853bSbluhm 3450383853bSbluhm /* wait until all child processes are waiting */ 34679f7fadeSbluhm while (waiting) { 3470383853bSbluhm tv.tv_sec = timeout; 3480383853bSbluhm tv.tv_usec = 0; 3490383853bSbluhm errno = ETIMEDOUT; 3500383853bSbluhm if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 3510383853bSbluhm err(1, "select"); 3520383853bSbluhm 35379f7fadeSbluhm waiting = 0; 3540383853bSbluhm /* remove fd of children that closed their end */ 3550383853bSbluhm for (i = 0; i < procs; i++) { 3560383853bSbluhm if (rfds[i] >= 0) { 3570383853bSbluhm if (FD_ISSET(rfds[i], fdset)) { 3580383853bSbluhm if (close(rfds[i]) == -1) 35979f7fadeSbluhm err(1, "close parent"); 3600383853bSbluhm FD_CLR(rfds[i], fdset); 3610383853bSbluhm rfds[i] = -1; 3620383853bSbluhm } else { 3630383853bSbluhm FD_SET(rfds[i], fdset); 3640383853bSbluhm waiting = 1; 3650383853bSbluhm } 3660383853bSbluhm } 3670383853bSbluhm } 36879f7fadeSbluhm } 3690383853bSbluhm 3700383853bSbluhm /* kill all children simultaneously, parent exits in signal handler */ 3710383853bSbluhm if (signal(SIGTERM, sigexit) == SIG_ERR) 3720383853bSbluhm err(1, "signal SIGTERM"); 3730383853bSbluhm if (kill(-pgrp, SIGTERM) == -1) 3740383853bSbluhm err(1, "kill %d", -pgrp); 3750383853bSbluhm 3760383853bSbluhm errx(1, "alive"); 3770383853bSbluhm } 378