1*79f7fadeSbluhm /* $OpenBSD: fork-exit.c,v 1.2 2021/04/29 13:39:22 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 190383853bSbluhm #include <sys/select.h> 200383853bSbluhm #include <sys/wait.h> 210383853bSbluhm 220383853bSbluhm #include <err.h> 230383853bSbluhm #include <errno.h> 240383853bSbluhm #include <fcntl.h> 250383853bSbluhm #include <limits.h> 26*79f7fadeSbluhm #include <pthread.h> 270383853bSbluhm #include <signal.h> 280383853bSbluhm #include <stdio.h> 290383853bSbluhm #include <stdlib.h> 300383853bSbluhm #include <unistd.h> 310383853bSbluhm 320383853bSbluhm int execute = 0; 330383853bSbluhm int daemonize = 0; 340383853bSbluhm int procs = 1; 35*79f7fadeSbluhm int threads = 0; 360383853bSbluhm int timeout = 30; 370383853bSbluhm 38*79f7fadeSbluhm pthread_barrier_t thread_barrier; 39*79f7fadeSbluhm char timeoutstr[sizeof("-2147483647")]; 40*79f7fadeSbluhm 410383853bSbluhm static void __dead 420383853bSbluhm usage(void) 430383853bSbluhm { 44*79f7fadeSbluhm fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n" 450383853bSbluhm " -e child execs sleep(1), default call sleep(3)\n" 46*79f7fadeSbluhm " -d daemonize, use if already process group leader\n" 470383853bSbluhm " -p procs number of processes to fork, default 1\n" 48*79f7fadeSbluhm " -t threads number of threads to create, default 0\n" 49*79f7fadeSbluhm " -T timeout parent and children will exit, default 30 sec\n"); 500383853bSbluhm exit(2); 510383853bSbluhm } 520383853bSbluhm 53*79f7fadeSbluhm static void * __dead 54*79f7fadeSbluhm run_thread(void *arg) 55*79f7fadeSbluhm { 56*79f7fadeSbluhm int error; 57*79f7fadeSbluhm 58*79f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 59*79f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 60*79f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 61*79f7fadeSbluhm 62*79f7fadeSbluhm if (sleep(timeout) != 0) 63*79f7fadeSbluhm err(1, "sleep %d", timeout); 64*79f7fadeSbluhm 65*79f7fadeSbluhm /* should not happen */ 66*79f7fadeSbluhm _exit(0); 67*79f7fadeSbluhm } 68*79f7fadeSbluhm 69*79f7fadeSbluhm static void 70*79f7fadeSbluhm create_threads(void) 71*79f7fadeSbluhm { 72*79f7fadeSbluhm pthread_t *thrs; 73*79f7fadeSbluhm int i, error; 74*79f7fadeSbluhm 75*79f7fadeSbluhm error = pthread_barrier_init(&thread_barrier, NULL, threads + 1); 76*79f7fadeSbluhm if (error) 77*79f7fadeSbluhm errc(1, errno, "pthread_barrier_init"); 78*79f7fadeSbluhm 79*79f7fadeSbluhm thrs = reallocarray(NULL, threads, sizeof(pthread_t)); 80*79f7fadeSbluhm if (thrs == NULL) 81*79f7fadeSbluhm err(1, "thrs"); 82*79f7fadeSbluhm 83*79f7fadeSbluhm for (i = 0; i < threads; i++) { 84*79f7fadeSbluhm error = pthread_create(&thrs[i], NULL, run_thread, NULL); 85*79f7fadeSbluhm if (error) 86*79f7fadeSbluhm errc(1, error, "pthread_create"); 87*79f7fadeSbluhm } 88*79f7fadeSbluhm 89*79f7fadeSbluhm error = pthread_barrier_wait(&thread_barrier); 90*79f7fadeSbluhm if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 91*79f7fadeSbluhm errc(1, error, "pthread_barrier_wait"); 92*79f7fadeSbluhm 93*79f7fadeSbluhm /* return to close child's pipe and sleep */ 94*79f7fadeSbluhm } 95*79f7fadeSbluhm 960383853bSbluhm static void __dead 970383853bSbluhm exec_sleep(void) 980383853bSbluhm { 99*79f7fadeSbluhm execl("/bin/sleep", "sleep", timeoutstr, NULL); 1000383853bSbluhm err(1, "exec sleep"); 1010383853bSbluhm } 1020383853bSbluhm 103*79f7fadeSbluhm static void __dead 104*79f7fadeSbluhm run_child(int fd) 1050383853bSbluhm { 1060383853bSbluhm /* close pipe to parent and sleep until killed */ 1070383853bSbluhm if (execute) { 1080383853bSbluhm if (fcntl(fd, F_SETFD, FD_CLOEXEC)) 1090383853bSbluhm err(1, "fcntl FD_CLOEXEC"); 1100383853bSbluhm exec_sleep(); 1110383853bSbluhm } else { 112*79f7fadeSbluhm if (threads) 113*79f7fadeSbluhm create_threads(); 1140383853bSbluhm if (close(fd) == -1) 115*79f7fadeSbluhm err(1, "close child"); 1160383853bSbluhm if (sleep(timeout) != 0) 1170383853bSbluhm err(1, "sleep %d", timeout); 1180383853bSbluhm } 119*79f7fadeSbluhm 120*79f7fadeSbluhm /* should not happen */ 1210383853bSbluhm _exit(0); 1220383853bSbluhm } 1230383853bSbluhm 1240383853bSbluhm static void 1250383853bSbluhm sigexit(int sig) 1260383853bSbluhm { 1270383853bSbluhm int i, status; 1280383853bSbluhm pid_t pid; 1290383853bSbluhm 1300383853bSbluhm /* all children must terminate in time */ 1310383853bSbluhm if ((int)alarm(timeout) == -1) 1320383853bSbluhm err(1, "alarm"); 1330383853bSbluhm 1340383853bSbluhm for (i = 0; i < procs; i++) { 1350383853bSbluhm pid = wait(&status); 1360383853bSbluhm if (pid == -1) 1370383853bSbluhm err(1, "wait"); 1380383853bSbluhm if (!WIFSIGNALED(status)) 1390383853bSbluhm errx(1, "child %d not killed", pid); 1400383853bSbluhm if(WTERMSIG(status) != SIGTERM) 1410383853bSbluhm errx(1, "child %d signal %d", pid, WTERMSIG(status)); 1420383853bSbluhm } 1430383853bSbluhm exit(0); 1440383853bSbluhm } 1450383853bSbluhm 1460383853bSbluhm int 1470383853bSbluhm main(int argc, char *argv[]) 1480383853bSbluhm { 1490383853bSbluhm const char *errstr; 1500383853bSbluhm int ch, i, fdmax, fdlen, *rfds, waiting; 1510383853bSbluhm fd_set *fdset; 1520383853bSbluhm pid_t pgrp; 1530383853bSbluhm struct timeval tv; 1540383853bSbluhm 155*79f7fadeSbluhm while ((ch = getopt(argc, argv, "edp:T:t:")) != -1) { 1560383853bSbluhm switch (ch) { 1570383853bSbluhm case 'e': 1580383853bSbluhm execute = 1; 1590383853bSbluhm break; 1600383853bSbluhm case 'd': 1610383853bSbluhm daemonize = 1; 1620383853bSbluhm break; 1630383853bSbluhm case 'p': 1640383853bSbluhm procs = strtonum(optarg, 0, INT_MAX, &errstr); 1650383853bSbluhm if (errstr != NULL) 1660383853bSbluhm errx(1, "number of procs is %s: %s", errstr, 1670383853bSbluhm optarg); 1680383853bSbluhm break; 1690383853bSbluhm case 't': 170*79f7fadeSbluhm threads = strtonum(optarg, 0, INT_MAX, &errstr); 171*79f7fadeSbluhm if (errstr != NULL) 172*79f7fadeSbluhm errx(1, "number of threads is %s: %s", errstr, 173*79f7fadeSbluhm optarg); 174*79f7fadeSbluhm break; 175*79f7fadeSbluhm case 'T': 1760383853bSbluhm timeout = strtonum(optarg, 0, INT_MAX, &errstr); 1770383853bSbluhm if (errstr != NULL) 1780383853bSbluhm errx(1, "timeout is %s: %s", errstr, optarg); 179*79f7fadeSbluhm break; 1800383853bSbluhm default: 1810383853bSbluhm usage(); 1820383853bSbluhm } 1830383853bSbluhm } 184*79f7fadeSbluhm if (execute) { 185*79f7fadeSbluhm int ret; 186*79f7fadeSbluhm 187*79f7fadeSbluhm if (threads > 0) 188*79f7fadeSbluhm errx(1, "execute sleep cannot be used with threads"); 189*79f7fadeSbluhm 190*79f7fadeSbluhm ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout); 191*79f7fadeSbluhm if (ret < 0 || (size_t)ret >= sizeof(timeoutstr)) 192*79f7fadeSbluhm err(1, "snprintf"); 193*79f7fadeSbluhm } 1940383853bSbluhm 1950383853bSbluhm /* become process group leader */ 196*79f7fadeSbluhm if (daemonize) { 197*79f7fadeSbluhm /* get rid of process leadership */ 1980383853bSbluhm switch (fork()) { 1990383853bSbluhm case -1: 2000383853bSbluhm err(1, "fork parent"); 2010383853bSbluhm case 0: 2020383853bSbluhm break; 2030383853bSbluhm default: 204*79f7fadeSbluhm /* parent leaves orphan behind to do the work */ 2050383853bSbluhm _exit(0); 2060383853bSbluhm } 2070383853bSbluhm } 208*79f7fadeSbluhm pgrp = setsid(); 209*79f7fadeSbluhm if (pgrp == -1) { 2100383853bSbluhm if (!daemonize) 2110383853bSbluhm warnx("try -d to become process group leader"); 2120383853bSbluhm err(1, "setsid"); 2130383853bSbluhm } 2140383853bSbluhm 2150383853bSbluhm /* create pipes to keep in contact with children */ 2160383853bSbluhm rfds = reallocarray(NULL, procs, sizeof(int)); 2170383853bSbluhm if (rfds == NULL) 2180383853bSbluhm err(1, "rfds"); 2190383853bSbluhm fdmax = 0; 2200383853bSbluhm 2210383853bSbluhm /* fork child processes and pass writing end of pipe */ 2220383853bSbluhm for (i = 0; i < procs; i++) { 223*79f7fadeSbluhm int pipefds[2], error; 2240383853bSbluhm 2250383853bSbluhm if (pipe(pipefds) == -1) 2260383853bSbluhm err(1, "pipe"); 2270383853bSbluhm if (fdmax < pipefds[0]) 2280383853bSbluhm fdmax = pipefds[0]; 2290383853bSbluhm rfds[i] = pipefds[0]; 230*79f7fadeSbluhm 231*79f7fadeSbluhm switch (fork()) { 232*79f7fadeSbluhm case -1: 233*79f7fadeSbluhm /* resource temporarily unavailable may happen */ 234*79f7fadeSbluhm error = errno; 235*79f7fadeSbluhm /* reap children, but not parent */ 236*79f7fadeSbluhm signal(SIGTERM, SIG_IGN); 237*79f7fadeSbluhm kill(-pgrp, SIGTERM); 238*79f7fadeSbluhm errc(1, error, "fork child"); 239*79f7fadeSbluhm case 0: 240*79f7fadeSbluhm /* child closes reading end, read is for the parent */ 241*79f7fadeSbluhm if (close(pipefds[0]) == -1) 242*79f7fadeSbluhm err(1, "close read"); 243*79f7fadeSbluhm run_child(pipefds[1]); 244*79f7fadeSbluhm /* cannot happen */ 245*79f7fadeSbluhm _exit(0); 246*79f7fadeSbluhm default: 247*79f7fadeSbluhm /* parent closes writing end, write is for the child */ 2480383853bSbluhm if (close(pipefds[1]) == -1) 249*79f7fadeSbluhm err(1, "close write"); 250*79f7fadeSbluhm break; 251*79f7fadeSbluhm } 2520383853bSbluhm } 2530383853bSbluhm 2540383853bSbluhm /* create select mask with all reading ends of child pipes */ 2550383853bSbluhm fdlen = howmany(fdmax + 1, NFDBITS); 2560383853bSbluhm fdset = calloc(fdlen, sizeof(fd_mask)); 2570383853bSbluhm if (fdset == NULL) 2580383853bSbluhm err(1, "fdset"); 259*79f7fadeSbluhm waiting = 0; 2600383853bSbluhm for (i = 0; i < procs; i++) { 2610383853bSbluhm FD_SET(rfds[i], fdset); 262*79f7fadeSbluhm waiting = 1; 2630383853bSbluhm } 2640383853bSbluhm 2650383853bSbluhm /* wait until all child processes are waiting */ 266*79f7fadeSbluhm while (waiting) { 2670383853bSbluhm tv.tv_sec = timeout; 2680383853bSbluhm tv.tv_usec = 0; 2690383853bSbluhm errno = ETIMEDOUT; 2700383853bSbluhm if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 2710383853bSbluhm err(1, "select"); 2720383853bSbluhm 273*79f7fadeSbluhm waiting = 0; 2740383853bSbluhm /* remove fd of children that closed their end */ 2750383853bSbluhm for (i = 0; i < procs; i++) { 2760383853bSbluhm if (rfds[i] >= 0) { 2770383853bSbluhm if (FD_ISSET(rfds[i], fdset)) { 2780383853bSbluhm if (close(rfds[i]) == -1) 279*79f7fadeSbluhm err(1, "close parent"); 2800383853bSbluhm FD_CLR(rfds[i], fdset); 2810383853bSbluhm rfds[i] = -1; 2820383853bSbluhm } else { 2830383853bSbluhm FD_SET(rfds[i], fdset); 2840383853bSbluhm waiting = 1; 2850383853bSbluhm } 2860383853bSbluhm } 2870383853bSbluhm } 288*79f7fadeSbluhm } 2890383853bSbluhm 2900383853bSbluhm /* kill all children simultaneously, parent exits in signal handler */ 2910383853bSbluhm if (signal(SIGTERM, sigexit) == SIG_ERR) 2920383853bSbluhm err(1, "signal SIGTERM"); 2930383853bSbluhm if (kill(-pgrp, SIGTERM) == -1) 2940383853bSbluhm err(1, "kill %d", -pgrp); 2950383853bSbluhm 2960383853bSbluhm errx(1, "alive"); 2970383853bSbluhm } 298