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