1 /* $OpenBSD: fork-exit.c,v 1.1 2021/04/28 17:59:53 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/select.h> 20 #include <sys/wait.h> 21 22 #include <err.h> 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <limits.h> 26 #include <signal.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <unistd.h> 30 31 int execute = 0; 32 int daemonize = 0; 33 int procs = 1; 34 int timeout = 30; 35 36 static void __dead 37 usage(void) 38 { 39 fprintf(stderr, "fork-exit [-ed] [-p procs] [-t timeout]\n" 40 " -e child execs sleep(1), default call sleep(3)\n" 41 " -d daemonize, if already process group leader\n" 42 " -p procs number of processes to fork, default 1\n" 43 " -t timeout parent and children will exit, default 30 sec\n"); 44 exit(2); 45 } 46 47 static void __dead 48 exec_sleep(void) 49 { 50 execl("/bin/sleep", "sleep", "30", NULL); 51 err(1, "exec sleep"); 52 } 53 54 static void 55 fork_sleep(int fd) 56 { 57 switch (fork()) { 58 case -1: 59 err(1, "fork"); 60 case 0: 61 break; 62 default: 63 return; 64 } 65 /* close pipe to parent and sleep until killed */ 66 if (execute) { 67 if (fcntl(fd, F_SETFD, FD_CLOEXEC)) 68 err(1, "fcntl FD_CLOEXEC"); 69 exec_sleep(); 70 } else { 71 if (close(fd) == -1) 72 err(1, "close write"); 73 if (sleep(timeout) != 0) 74 err(1, "sleep %d", timeout); 75 } 76 _exit(0); 77 } 78 79 static void 80 sigexit(int sig) 81 { 82 int i, status; 83 pid_t pid; 84 85 /* all children must terminate in time */ 86 if ((int)alarm(timeout) == -1) 87 err(1, "alarm"); 88 89 for (i = 0; i < procs; i++) { 90 pid = wait(&status); 91 if (pid == -1) 92 err(1, "wait"); 93 if (!WIFSIGNALED(status)) 94 errx(1, "child %d not killed", pid); 95 if(WTERMSIG(status) != SIGTERM) 96 errx(1, "child %d signal %d", pid, WTERMSIG(status)); 97 } 98 exit(0); 99 } 100 101 int 102 main(int argc, char *argv[]) 103 { 104 const char *errstr; 105 int ch, i, fdmax, fdlen, *rfds, waiting; 106 fd_set *fdset; 107 pid_t pgrp; 108 struct timeval tv; 109 110 while ((ch = getopt(argc, argv, "edp:t:")) != -1) { 111 switch (ch) { 112 case 'e': 113 execute = 1; 114 break; 115 case 'd': 116 daemonize = 1; 117 break; 118 case 'p': 119 procs = strtonum(optarg, 0, INT_MAX, &errstr); 120 if (errstr != NULL) 121 errx(1, "number of procs is %s: %s", errstr, 122 optarg); 123 break; 124 case 't': 125 timeout = strtonum(optarg, 0, INT_MAX, &errstr); 126 if (errstr != NULL) 127 errx(1, "timeout is %s: %s", errstr, optarg); 128 default: 129 usage(); 130 } 131 } 132 133 /* become process group leader */ 134 pgrp = setsid(); 135 if (pgrp == -1) { 136 if (errno == EPERM && daemonize) { 137 /* get rid of leadership */ 138 switch (fork()) { 139 case -1: 140 err(1, "fork parent"); 141 case 0: 142 /* try again */ 143 pgrp = setsid(); 144 break; 145 default: 146 _exit(0); 147 } 148 } 149 if (!daemonize) 150 warnx("try -d to become process group leader"); 151 if (pgrp == -1) 152 err(1, "setsid"); 153 } 154 155 /* create pipes to keep in contact with children */ 156 rfds = reallocarray(NULL, procs, sizeof(int)); 157 if (rfds == NULL) 158 err(1, "rfds"); 159 fdmax = 0; 160 161 /* fork child processes and pass writing end of pipe */ 162 for (i = 0; i < procs; i++) { 163 int pipefds[2]; 164 165 if (pipe(pipefds) == -1) 166 err(1, "pipe"); 167 if (fdmax < pipefds[0]) 168 fdmax = pipefds[0]; 169 rfds[i] = pipefds[0]; 170 fork_sleep(pipefds[1]); 171 if (close(pipefds[1]) == -1) 172 err(1, "close parent"); 173 } 174 175 /* create select mask with all reading ends of child pipes */ 176 fdlen = howmany(fdmax + 1, NFDBITS); 177 fdset = calloc(fdlen, sizeof(fd_mask)); 178 if (fdset == NULL) 179 err(1, "fdset"); 180 for (i = 0; i < procs; i++) { 181 FD_SET(rfds[i], fdset); 182 } 183 184 /* wait until all child processes are waiting */ 185 do { 186 waiting = 0; 187 tv.tv_sec = timeout; 188 tv.tv_usec = 0; 189 errno = ETIMEDOUT; 190 if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 191 err(1, "select"); 192 193 /* remove fd of children that closed their end */ 194 for (i = 0; i < procs; i++) { 195 if (rfds[i] >= 0) { 196 if (FD_ISSET(rfds[i], fdset)) { 197 if (close(rfds[i]) == -1) 198 err(1, "close read"); 199 FD_CLR(rfds[i], fdset); 200 rfds[i] = -1; 201 } else { 202 FD_SET(rfds[i], fdset); 203 waiting = 1; 204 } 205 } 206 } 207 } while (waiting); 208 209 /* kill all children simultaneously, parent exits in signal handler */ 210 if (signal(SIGTERM, sigexit) == SIG_ERR) 211 err(1, "signal SIGTERM"); 212 if (kill(-pgrp, SIGTERM) == -1) 213 err(1, "kill %d", -pgrp); 214 215 errx(1, "alive"); 216 } 217