1*85f5c1e8Sbluhm /* $OpenBSD: fork-exit.c,v 1.3 2021/05/04 13:24:49 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 19*85f5c1e8Sbluhm #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> 31*85f5c1e8Sbluhm #include <string.h> 320383853bSbluhm #include <unistd.h> 330383853bSbluhm 340383853bSbluhm int execute = 0; 350383853bSbluhm int daemonize = 0; 36*85f5c1e8Sbluhm int heap = 0; 370383853bSbluhm int procs = 1; 38*85f5c1e8Sbluhm 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" 51*85f5c1e8Sbluhm " -h heap allocate pages of heap memory, default 0\n" 520383853bSbluhm " -p procs number of processes to fork, default 1\n" 53*85f5c1e8Sbluhm " -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 59*85f5c1e8Sbluhm static void 60*85f5c1e8Sbluhm recurse_page(int depth) 61*85f5c1e8Sbluhm { 62*85f5c1e8Sbluhm int p[4096 / sizeof(int)]; 63*85f5c1e8Sbluhm 64*85f5c1e8Sbluhm if (depth == 0) 65*85f5c1e8Sbluhm return; 66*85f5c1e8Sbluhm p[1] = 0x9abcdef0; 67*85f5c1e8Sbluhm explicit_bzero(p, sizeof(int)); 68*85f5c1e8Sbluhm recurse_page(depth - 1); 69*85f5c1e8Sbluhm } 70*85f5c1e8Sbluhm 71*85f5c1e8Sbluhm static void 72*85f5c1e8Sbluhm alloc_stack(void) 73*85f5c1e8Sbluhm { 74*85f5c1e8Sbluhm recurse_page(stack); 75*85f5c1e8Sbluhm } 76*85f5c1e8Sbluhm 77*85f5c1e8Sbluhm static void 78*85f5c1e8Sbluhm alloc_heap(void) 79*85f5c1e8Sbluhm { 80*85f5c1e8Sbluhm int *p; 81*85f5c1e8Sbluhm int i; 82*85f5c1e8Sbluhm 83*85f5c1e8Sbluhm for(i = 0; i < heap; i++) { 84*85f5c1e8Sbluhm p = mmap(0, 4096, PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); 85*85f5c1e8Sbluhm if (p == MAP_FAILED) 86*85f5c1e8Sbluhm err(1, "mmap"); 87*85f5c1e8Sbluhm p[1] = 0x12345678; 88*85f5c1e8Sbluhm explicit_bzero(p, sizeof(int)); 89*85f5c1e8Sbluhm } 90*85f5c1e8Sbluhm } 91*85f5c1e8Sbluhm 9279f7fadeSbluhm static void * __dead 9379f7fadeSbluhm run_thread(void *arg) 9479f7fadeSbluhm { 9579f7fadeSbluhm int error; 9679f7fadeSbluhm 97*85f5c1e8Sbluhm if (heap) 98*85f5c1e8Sbluhm alloc_heap(); 99*85f5c1e8Sbluhm if (stack) 100*85f5c1e8Sbluhm alloc_stack(); 101*85f5c1e8Sbluhm 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 { 11679f7fadeSbluhm pthread_t *thrs; 11779f7fadeSbluhm int i, error; 11879f7fadeSbluhm 11979f7fadeSbluhm error = pthread_barrier_init(&thread_barrier, NULL, threads + 1); 12079f7fadeSbluhm if (error) 12179f7fadeSbluhm errc(1, errno, "pthread_barrier_init"); 12279f7fadeSbluhm 12379f7fadeSbluhm thrs = reallocarray(NULL, threads, sizeof(pthread_t)); 12479f7fadeSbluhm if (thrs == NULL) 12579f7fadeSbluhm err(1, "thrs"); 12679f7fadeSbluhm 12779f7fadeSbluhm for (i = 0; i < threads; i++) { 12879f7fadeSbluhm error = pthread_create(&thrs[i], NULL, run_thread, NULL); 12979f7fadeSbluhm if (error) 13079f7fadeSbluhm errc(1, error, "pthread_create"); 13179f7fadeSbluhm } 13279f7fadeSbluhm 13379f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 13479f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 13579f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 13679f7fadeSbluhm 13779f7fadeSbluhm /* return to close child's pipe and sleep */ 13879f7fadeSbluhm } 13979f7fadeSbluhm 1400383853bSbluhm static void __dead 1410383853bSbluhm exec_sleep(void) 1420383853bSbluhm { 14379f7fadeSbluhm execl("/bin/sleep", "sleep", timeoutstr, NULL); 1440383853bSbluhm err(1, "exec sleep"); 1450383853bSbluhm } 1460383853bSbluhm 14779f7fadeSbluhm static void __dead 14879f7fadeSbluhm run_child(int fd) 1490383853bSbluhm { 1500383853bSbluhm /* close pipe to parent and sleep until killed */ 1510383853bSbluhm if (execute) { 1520383853bSbluhm if (fcntl(fd, F_SETFD, FD_CLOEXEC)) 1530383853bSbluhm err(1, "fcntl FD_CLOEXEC"); 1540383853bSbluhm exec_sleep(); 1550383853bSbluhm } else { 156*85f5c1e8Sbluhm if (threads) { 15779f7fadeSbluhm create_threads(); 158*85f5c1e8Sbluhm } else { 159*85f5c1e8Sbluhm if (heap) 160*85f5c1e8Sbluhm alloc_heap(); 161*85f5c1e8Sbluhm if (stack) 162*85f5c1e8Sbluhm alloc_stack(); 163*85f5c1e8Sbluhm } 1640383853bSbluhm if (close(fd) == -1) 16579f7fadeSbluhm err(1, "close child"); 1660383853bSbluhm if (sleep(timeout) != 0) 1670383853bSbluhm err(1, "sleep %d", timeout); 1680383853bSbluhm } 16979f7fadeSbluhm 17079f7fadeSbluhm /* should not happen */ 1710383853bSbluhm _exit(0); 1720383853bSbluhm } 1730383853bSbluhm 1740383853bSbluhm static void 1750383853bSbluhm sigexit(int sig) 1760383853bSbluhm { 1770383853bSbluhm int i, status; 1780383853bSbluhm pid_t pid; 1790383853bSbluhm 1800383853bSbluhm /* all children must terminate in time */ 1810383853bSbluhm if ((int)alarm(timeout) == -1) 1820383853bSbluhm err(1, "alarm"); 1830383853bSbluhm 1840383853bSbluhm for (i = 0; i < procs; i++) { 1850383853bSbluhm pid = wait(&status); 1860383853bSbluhm if (pid == -1) 1870383853bSbluhm err(1, "wait"); 1880383853bSbluhm if (!WIFSIGNALED(status)) 1890383853bSbluhm errx(1, "child %d not killed", pid); 1900383853bSbluhm if(WTERMSIG(status) != SIGTERM) 1910383853bSbluhm errx(1, "child %d signal %d", pid, WTERMSIG(status)); 1920383853bSbluhm } 1930383853bSbluhm exit(0); 1940383853bSbluhm } 1950383853bSbluhm 1960383853bSbluhm int 1970383853bSbluhm main(int argc, char *argv[]) 1980383853bSbluhm { 1990383853bSbluhm const char *errstr; 2000383853bSbluhm int ch, i, fdmax, fdlen, *rfds, waiting; 2010383853bSbluhm fd_set *fdset; 2020383853bSbluhm pid_t pgrp; 2030383853bSbluhm struct timeval tv; 2040383853bSbluhm 205*85f5c1e8Sbluhm while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) { 2060383853bSbluhm switch (ch) { 2070383853bSbluhm case 'e': 2080383853bSbluhm execute = 1; 2090383853bSbluhm break; 2100383853bSbluhm case 'd': 2110383853bSbluhm daemonize = 1; 2120383853bSbluhm break; 213*85f5c1e8Sbluhm case 'h': 214*85f5c1e8Sbluhm heap = strtonum(optarg, 0, INT_MAX, &errstr); 215*85f5c1e8Sbluhm if (errstr != NULL) 216*85f5c1e8Sbluhm errx(1, "number of heap allocations is %s: %s", 217*85f5c1e8Sbluhm errstr, optarg); 218*85f5c1e8Sbluhm break; 2190383853bSbluhm case 'p': 2200383853bSbluhm procs = strtonum(optarg, 0, INT_MAX, &errstr); 2210383853bSbluhm if (errstr != NULL) 2220383853bSbluhm errx(1, "number of procs is %s: %s", errstr, 2230383853bSbluhm optarg); 2240383853bSbluhm break; 225*85f5c1e8Sbluhm case 's': 226*85f5c1e8Sbluhm stack = strtonum(optarg, 0, INT_MAX, &errstr); 227*85f5c1e8Sbluhm if (errstr != NULL) 228*85f5c1e8Sbluhm errx(1, "number of stack allocations is %s: %s", 229*85f5c1e8Sbluhm errstr, optarg); 230*85f5c1e8Sbluhm break; 2310383853bSbluhm case 't': 23279f7fadeSbluhm threads = strtonum(optarg, 0, INT_MAX, &errstr); 23379f7fadeSbluhm if (errstr != NULL) 23479f7fadeSbluhm errx(1, "number of threads is %s: %s", errstr, 23579f7fadeSbluhm optarg); 23679f7fadeSbluhm break; 23779f7fadeSbluhm case 'T': 2380383853bSbluhm timeout = strtonum(optarg, 0, INT_MAX, &errstr); 2390383853bSbluhm if (errstr != NULL) 2400383853bSbluhm errx(1, "timeout is %s: %s", errstr, optarg); 24179f7fadeSbluhm break; 2420383853bSbluhm default: 2430383853bSbluhm usage(); 2440383853bSbluhm } 2450383853bSbluhm } 24679f7fadeSbluhm if (execute) { 24779f7fadeSbluhm int ret; 24879f7fadeSbluhm 24979f7fadeSbluhm if (threads > 0) 25079f7fadeSbluhm errx(1, "execute sleep cannot be used with threads"); 25179f7fadeSbluhm 25279f7fadeSbluhm ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout); 25379f7fadeSbluhm if (ret < 0 || (size_t)ret >= sizeof(timeoutstr)) 25479f7fadeSbluhm err(1, "snprintf"); 25579f7fadeSbluhm } 2560383853bSbluhm 2570383853bSbluhm /* become process group leader */ 25879f7fadeSbluhm if (daemonize) { 25979f7fadeSbluhm /* get rid of process leadership */ 2600383853bSbluhm switch (fork()) { 2610383853bSbluhm case -1: 2620383853bSbluhm err(1, "fork parent"); 2630383853bSbluhm case 0: 2640383853bSbluhm break; 2650383853bSbluhm default: 26679f7fadeSbluhm /* parent leaves orphan behind to do the work */ 2670383853bSbluhm _exit(0); 2680383853bSbluhm } 2690383853bSbluhm } 27079f7fadeSbluhm pgrp = setsid(); 27179f7fadeSbluhm if (pgrp == -1) { 2720383853bSbluhm if (!daemonize) 2730383853bSbluhm warnx("try -d to become process group leader"); 2740383853bSbluhm err(1, "setsid"); 2750383853bSbluhm } 2760383853bSbluhm 2770383853bSbluhm /* create pipes to keep in contact with children */ 2780383853bSbluhm rfds = reallocarray(NULL, procs, sizeof(int)); 2790383853bSbluhm if (rfds == NULL) 2800383853bSbluhm err(1, "rfds"); 2810383853bSbluhm fdmax = 0; 2820383853bSbluhm 2830383853bSbluhm /* fork child processes and pass writing end of pipe */ 2840383853bSbluhm for (i = 0; i < procs; i++) { 28579f7fadeSbluhm int pipefds[2], error; 2860383853bSbluhm 2870383853bSbluhm if (pipe(pipefds) == -1) 2880383853bSbluhm err(1, "pipe"); 2890383853bSbluhm if (fdmax < pipefds[0]) 2900383853bSbluhm fdmax = pipefds[0]; 2910383853bSbluhm rfds[i] = pipefds[0]; 29279f7fadeSbluhm 29379f7fadeSbluhm switch (fork()) { 29479f7fadeSbluhm case -1: 29579f7fadeSbluhm /* resource temporarily unavailable may happen */ 29679f7fadeSbluhm error = errno; 29779f7fadeSbluhm /* reap children, but not parent */ 29879f7fadeSbluhm signal(SIGTERM, SIG_IGN); 29979f7fadeSbluhm kill(-pgrp, SIGTERM); 30079f7fadeSbluhm errc(1, error, "fork child"); 30179f7fadeSbluhm case 0: 30279f7fadeSbluhm /* child closes reading end, read is for the parent */ 30379f7fadeSbluhm if (close(pipefds[0]) == -1) 30479f7fadeSbluhm err(1, "close read"); 30579f7fadeSbluhm run_child(pipefds[1]); 30679f7fadeSbluhm /* cannot happen */ 30779f7fadeSbluhm _exit(0); 30879f7fadeSbluhm default: 30979f7fadeSbluhm /* parent closes writing end, write is for the child */ 3100383853bSbluhm if (close(pipefds[1]) == -1) 31179f7fadeSbluhm err(1, "close write"); 31279f7fadeSbluhm break; 31379f7fadeSbluhm } 3140383853bSbluhm } 3150383853bSbluhm 3160383853bSbluhm /* create select mask with all reading ends of child pipes */ 3170383853bSbluhm fdlen = howmany(fdmax + 1, NFDBITS); 3180383853bSbluhm fdset = calloc(fdlen, sizeof(fd_mask)); 3190383853bSbluhm if (fdset == NULL) 3200383853bSbluhm err(1, "fdset"); 32179f7fadeSbluhm waiting = 0; 3220383853bSbluhm for (i = 0; i < procs; i++) { 3230383853bSbluhm FD_SET(rfds[i], fdset); 32479f7fadeSbluhm waiting = 1; 3250383853bSbluhm } 3260383853bSbluhm 3270383853bSbluhm /* wait until all child processes are waiting */ 32879f7fadeSbluhm while (waiting) { 3290383853bSbluhm tv.tv_sec = timeout; 3300383853bSbluhm tv.tv_usec = 0; 3310383853bSbluhm errno = ETIMEDOUT; 3320383853bSbluhm if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 3330383853bSbluhm err(1, "select"); 3340383853bSbluhm 33579f7fadeSbluhm waiting = 0; 3360383853bSbluhm /* remove fd of children that closed their end */ 3370383853bSbluhm for (i = 0; i < procs; i++) { 3380383853bSbluhm if (rfds[i] >= 0) { 3390383853bSbluhm if (FD_ISSET(rfds[i], fdset)) { 3400383853bSbluhm if (close(rfds[i]) == -1) 34179f7fadeSbluhm err(1, "close parent"); 3420383853bSbluhm FD_CLR(rfds[i], fdset); 3430383853bSbluhm rfds[i] = -1; 3440383853bSbluhm } else { 3450383853bSbluhm FD_SET(rfds[i], fdset); 3460383853bSbluhm waiting = 1; 3470383853bSbluhm } 3480383853bSbluhm } 3490383853bSbluhm } 35079f7fadeSbluhm } 3510383853bSbluhm 3520383853bSbluhm /* kill all children simultaneously, parent exits in signal handler */ 3530383853bSbluhm if (signal(SIGTERM, sigexit) == SIG_ERR) 3540383853bSbluhm err(1, "signal SIGTERM"); 3550383853bSbluhm if (kill(-pgrp, SIGTERM) == -1) 3560383853bSbluhm err(1, "kill %d", -pgrp); 3570383853bSbluhm 3580383853bSbluhm errx(1, "alive"); 3590383853bSbluhm } 360