1 /* $OpenBSD: fork-exit.c,v 1.3 2021/05/04 13:24:49 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/mman.h> 20 #include <sys/select.h> 21 #include <sys/wait.h> 22 23 #include <err.h> 24 #include <errno.h> 25 #include <fcntl.h> 26 #include <limits.h> 27 #include <pthread.h> 28 #include <signal.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 int execute = 0; 35 int daemonize = 0; 36 int heap = 0; 37 int procs = 1; 38 int stack = 0; 39 int threads = 0; 40 int timeout = 30; 41 42 pthread_barrier_t thread_barrier; 43 char timeoutstr[sizeof("-2147483647")]; 44 45 static void __dead 46 usage(void) 47 { 48 fprintf(stderr, "fork-exit [-ed] [-p procs] [-t threads] [-T timeout]\n" 49 " -e child execs sleep(1), default call sleep(3)\n" 50 " -d daemonize, use if already process group leader\n" 51 " -h heap allocate pages of heap memory, default 0\n" 52 " -p procs number of processes to fork, default 1\n" 53 " -s stack allocate pages of stack memory, default 0\n" 54 " -t threads number of threads to create, default 0\n" 55 " -T timeout parent and children will exit, default 30 sec\n"); 56 exit(2); 57 } 58 59 static void 60 recurse_page(int depth) 61 { 62 int p[4096 / sizeof(int)]; 63 64 if (depth == 0) 65 return; 66 p[1] = 0x9abcdef0; 67 explicit_bzero(p, sizeof(int)); 68 recurse_page(depth - 1); 69 } 70 71 static void 72 alloc_stack(void) 73 { 74 recurse_page(stack); 75 } 76 77 static void 78 alloc_heap(void) 79 { 80 int *p; 81 int i; 82 83 for(i = 0; i < heap; i++) { 84 p = mmap(0, 4096, PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); 85 if (p == MAP_FAILED) 86 err(1, "mmap"); 87 p[1] = 0x12345678; 88 explicit_bzero(p, sizeof(int)); 89 } 90 } 91 92 static void * __dead 93 run_thread(void *arg) 94 { 95 int error; 96 97 if (heap) 98 alloc_heap(); 99 if (stack) 100 alloc_stack(); 101 102 error = pthread_barrier_wait(&thread_barrier); 103 if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 104 errc(1, error, "pthread_barrier_wait"); 105 106 if (sleep(timeout) != 0) 107 err(1, "sleep %d", timeout); 108 109 /* should not happen */ 110 _exit(0); 111 } 112 113 static void 114 create_threads(void) 115 { 116 pthread_t *thrs; 117 int i, error; 118 119 error = pthread_barrier_init(&thread_barrier, NULL, threads + 1); 120 if (error) 121 errc(1, errno, "pthread_barrier_init"); 122 123 thrs = reallocarray(NULL, threads, sizeof(pthread_t)); 124 if (thrs == NULL) 125 err(1, "thrs"); 126 127 for (i = 0; i < threads; i++) { 128 error = pthread_create(&thrs[i], NULL, run_thread, NULL); 129 if (error) 130 errc(1, error, "pthread_create"); 131 } 132 133 error = pthread_barrier_wait(&thread_barrier); 134 if (error && error != PTHREAD_BARRIER_SERIAL_THREAD) 135 errc(1, error, "pthread_barrier_wait"); 136 137 /* return to close child's pipe and sleep */ 138 } 139 140 static void __dead 141 exec_sleep(void) 142 { 143 execl("/bin/sleep", "sleep", timeoutstr, NULL); 144 err(1, "exec sleep"); 145 } 146 147 static void __dead 148 run_child(int fd) 149 { 150 /* close pipe to parent and sleep until killed */ 151 if (execute) { 152 if (fcntl(fd, F_SETFD, FD_CLOEXEC)) 153 err(1, "fcntl FD_CLOEXEC"); 154 exec_sleep(); 155 } else { 156 if (threads) { 157 create_threads(); 158 } else { 159 if (heap) 160 alloc_heap(); 161 if (stack) 162 alloc_stack(); 163 } 164 if (close(fd) == -1) 165 err(1, "close child"); 166 if (sleep(timeout) != 0) 167 err(1, "sleep %d", timeout); 168 } 169 170 /* should not happen */ 171 _exit(0); 172 } 173 174 static void 175 sigexit(int sig) 176 { 177 int i, status; 178 pid_t pid; 179 180 /* all children must terminate in time */ 181 if ((int)alarm(timeout) == -1) 182 err(1, "alarm"); 183 184 for (i = 0; i < procs; i++) { 185 pid = wait(&status); 186 if (pid == -1) 187 err(1, "wait"); 188 if (!WIFSIGNALED(status)) 189 errx(1, "child %d not killed", pid); 190 if(WTERMSIG(status) != SIGTERM) 191 errx(1, "child %d signal %d", pid, WTERMSIG(status)); 192 } 193 exit(0); 194 } 195 196 int 197 main(int argc, char *argv[]) 198 { 199 const char *errstr; 200 int ch, i, fdmax, fdlen, *rfds, waiting; 201 fd_set *fdset; 202 pid_t pgrp; 203 struct timeval tv; 204 205 while ((ch = getopt(argc, argv, "edh:p:s:T:t:")) != -1) { 206 switch (ch) { 207 case 'e': 208 execute = 1; 209 break; 210 case 'd': 211 daemonize = 1; 212 break; 213 case 'h': 214 heap = strtonum(optarg, 0, INT_MAX, &errstr); 215 if (errstr != NULL) 216 errx(1, "number of heap allocations is %s: %s", 217 errstr, optarg); 218 break; 219 case 'p': 220 procs = strtonum(optarg, 0, INT_MAX, &errstr); 221 if (errstr != NULL) 222 errx(1, "number of procs is %s: %s", errstr, 223 optarg); 224 break; 225 case 's': 226 stack = strtonum(optarg, 0, INT_MAX, &errstr); 227 if (errstr != NULL) 228 errx(1, "number of stack allocations is %s: %s", 229 errstr, optarg); 230 break; 231 case 't': 232 threads = strtonum(optarg, 0, INT_MAX, &errstr); 233 if (errstr != NULL) 234 errx(1, "number of threads is %s: %s", errstr, 235 optarg); 236 break; 237 case 'T': 238 timeout = strtonum(optarg, 0, INT_MAX, &errstr); 239 if (errstr != NULL) 240 errx(1, "timeout is %s: %s", errstr, optarg); 241 break; 242 default: 243 usage(); 244 } 245 } 246 if (execute) { 247 int ret; 248 249 if (threads > 0) 250 errx(1, "execute sleep cannot be used with threads"); 251 252 ret = snprintf(timeoutstr, sizeof(timeoutstr), "%d", timeout); 253 if (ret < 0 || (size_t)ret >= sizeof(timeoutstr)) 254 err(1, "snprintf"); 255 } 256 257 /* become process group leader */ 258 if (daemonize) { 259 /* get rid of process leadership */ 260 switch (fork()) { 261 case -1: 262 err(1, "fork parent"); 263 case 0: 264 break; 265 default: 266 /* parent leaves orphan behind to do the work */ 267 _exit(0); 268 } 269 } 270 pgrp = setsid(); 271 if (pgrp == -1) { 272 if (!daemonize) 273 warnx("try -d to become process group leader"); 274 err(1, "setsid"); 275 } 276 277 /* create pipes to keep in contact with children */ 278 rfds = reallocarray(NULL, procs, sizeof(int)); 279 if (rfds == NULL) 280 err(1, "rfds"); 281 fdmax = 0; 282 283 /* fork child processes and pass writing end of pipe */ 284 for (i = 0; i < procs; i++) { 285 int pipefds[2], error; 286 287 if (pipe(pipefds) == -1) 288 err(1, "pipe"); 289 if (fdmax < pipefds[0]) 290 fdmax = pipefds[0]; 291 rfds[i] = pipefds[0]; 292 293 switch (fork()) { 294 case -1: 295 /* resource temporarily unavailable may happen */ 296 error = errno; 297 /* reap children, but not parent */ 298 signal(SIGTERM, SIG_IGN); 299 kill(-pgrp, SIGTERM); 300 errc(1, error, "fork child"); 301 case 0: 302 /* child closes reading end, read is for the parent */ 303 if (close(pipefds[0]) == -1) 304 err(1, "close read"); 305 run_child(pipefds[1]); 306 /* cannot happen */ 307 _exit(0); 308 default: 309 /* parent closes writing end, write is for the child */ 310 if (close(pipefds[1]) == -1) 311 err(1, "close write"); 312 break; 313 } 314 } 315 316 /* create select mask with all reading ends of child pipes */ 317 fdlen = howmany(fdmax + 1, NFDBITS); 318 fdset = calloc(fdlen, sizeof(fd_mask)); 319 if (fdset == NULL) 320 err(1, "fdset"); 321 waiting = 0; 322 for (i = 0; i < procs; i++) { 323 FD_SET(rfds[i], fdset); 324 waiting = 1; 325 } 326 327 /* wait until all child processes are waiting */ 328 while (waiting) { 329 tv.tv_sec = timeout; 330 tv.tv_usec = 0; 331 errno = ETIMEDOUT; 332 if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) 333 err(1, "select"); 334 335 waiting = 0; 336 /* remove fd of children that closed their end */ 337 for (i = 0; i < procs; i++) { 338 if (rfds[i] >= 0) { 339 if (FD_ISSET(rfds[i], fdset)) { 340 if (close(rfds[i]) == -1) 341 err(1, "close parent"); 342 FD_CLR(rfds[i], fdset); 343 rfds[i] = -1; 344 } else { 345 FD_SET(rfds[i], fdset); 346 waiting = 1; 347 } 348 } 349 } 350 } 351 352 /* kill all children simultaneously, parent exits in signal handler */ 353 if (signal(SIGTERM, sigexit) == SIG_ERR) 354 err(1, "signal SIGTERM"); 355 if (kill(-pgrp, SIGTERM) == -1) 356 err(1, "kill %d", -pgrp); 357 358 errx(1, "alive"); 359 } 360