diff options
-rw-r--r-- | regress/sys/kern/Makefile | 4 | ||||
-rw-r--r-- | regress/sys/kern/fork-exit/Makefile | 40 | ||||
-rw-r--r-- | regress/sys/kern/fork-exit/fork-exit.c | 216 |
3 files changed, 258 insertions, 2 deletions
diff --git a/regress/sys/kern/Makefile b/regress/sys/kern/Makefile index f0114cc48ae..0a64a811317 100644 --- a/regress/sys/kern/Makefile +++ b/regress/sys/kern/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.88 2020/11/10 16:13:35 bluhm Exp $ +# $OpenBSD: Makefile,v 1.89 2021/04/28 17:59:53 bluhm Exp $ SUBDIR+= __syscall SUBDIR+= accept access @@ -6,7 +6,7 @@ SUBDIR+= bind SUBDIR+= clock_gettime cmsgsize SUBDIR+= descrip dup2 dup2_accept dup2_self SUBDIR+= exec_self execve exit extent -SUBDIR+= fchdir fchown fcntl_dup flock ftruncate futex +SUBDIR+= fchdir fchown fcntl_dup flock fork-exit ftruncate futex SUBDIR+= getpeereid getrusage gettimeofday SUBDIR+= itimer SUBDIR+= kqueue diff --git a/regress/sys/kern/fork-exit/Makefile b/regress/sys/kern/fork-exit/Makefile new file mode 100644 index 00000000000..5104429ffdd --- /dev/null +++ b/regress/sys/kern/fork-exit/Makefile @@ -0,0 +1,40 @@ +# $OpenBSD: Makefile,v 1.1 2021/04/28 17:59:53 bluhm Exp $ + +# Copyright (c) 2021 Alexander Bluhm <bluhm@openbsd.org> +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# To hunt kernel bugs during exit, terminate processes simultaneously. +# Fork 300 children that sleep. Kill them together as process group. +# Sleeping can optionally be done with individual memory layout by +# executing sleep(1). + +PROG= fork-exit +WARNINGS= yes + +REGRESS_TARGETS += run-fork1-exit +run-fork1-exit: + # test forking a single child + ./fork-exit + +REGRESS_TARGETS += run-fork-exit +run-fork-exit: + # fork 300 children and kill them simultaneously as process group + ulimit -p 500 -n 1000; ./fork-exit -p 300 + +REGRESS_TARGETS += run-fork-exec-exit +run-fork-exec-exit: + # fork 300 children, exec sleep programs, and kill process group + ulimit -p 500 -n 1000; ./fork-exit -e -p 300 + +.include <bsd.regress.mk> diff --git a/regress/sys/kern/fork-exit/fork-exit.c b/regress/sys/kern/fork-exit/fork-exit.c new file mode 100644 index 00000000000..b66e4a7dc34 --- /dev/null +++ b/regress/sys/kern/fork-exit/fork-exit.c @@ -0,0 +1,216 @@ +/* $OpenBSD: fork-exit.c,v 1.1 2021/04/28 17:59:53 bluhm Exp $ */ + +/* + * Copyright (c) 2021 Alexander Bluhm <bluhm@openbsd.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/select.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +int execute = 0; +int daemonize = 0; +int procs = 1; +int timeout = 30; + +static void __dead +usage(void) +{ + fprintf(stderr, "fork-exit [-ed] [-p procs] [-t timeout]\n" + " -e child execs sleep(1), default call sleep(3)\n" + " -d daemonize, if already process group leader\n" + " -p procs number of processes to fork, default 1\n" + " -t timeout parent and children will exit, default 30 sec\n"); + exit(2); +} + +static void __dead +exec_sleep(void) +{ + execl("/bin/sleep", "sleep", "30", NULL); + err(1, "exec sleep"); +} + +static void +fork_sleep(int fd) +{ + switch (fork()) { + case -1: + err(1, "fork"); + case 0: + break; + default: + return; + } + /* close pipe to parent and sleep until killed */ + if (execute) { + if (fcntl(fd, F_SETFD, FD_CLOEXEC)) + err(1, "fcntl FD_CLOEXEC"); + exec_sleep(); + } else { + if (close(fd) == -1) + err(1, "close write"); + if (sleep(timeout) != 0) + err(1, "sleep %d", timeout); + } + _exit(0); +} + +static void +sigexit(int sig) +{ + int i, status; + pid_t pid; + + /* all children must terminate in time */ + if ((int)alarm(timeout) == -1) + err(1, "alarm"); + + for (i = 0; i < procs; i++) { + pid = wait(&status); + if (pid == -1) + err(1, "wait"); + if (!WIFSIGNALED(status)) + errx(1, "child %d not killed", pid); + if(WTERMSIG(status) != SIGTERM) + errx(1, "child %d signal %d", pid, WTERMSIG(status)); + } + exit(0); +} + +int +main(int argc, char *argv[]) +{ + const char *errstr; + int ch, i, fdmax, fdlen, *rfds, waiting; + fd_set *fdset; + pid_t pgrp; + struct timeval tv; + + while ((ch = getopt(argc, argv, "edp:t:")) != -1) { + switch (ch) { + case 'e': + execute = 1; + break; + case 'd': + daemonize = 1; + break; + case 'p': + procs = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(1, "number of procs is %s: %s", errstr, + optarg); + break; + case 't': + timeout = strtonum(optarg, 0, INT_MAX, &errstr); + if (errstr != NULL) + errx(1, "timeout is %s: %s", errstr, optarg); + default: + usage(); + } + } + + /* become process group leader */ + pgrp = setsid(); + if (pgrp == -1) { + if (errno == EPERM && daemonize) { + /* get rid of leadership */ + switch (fork()) { + case -1: + err(1, "fork parent"); + case 0: + /* try again */ + pgrp = setsid(); + break; + default: + _exit(0); + } + } + if (!daemonize) + warnx("try -d to become process group leader"); + if (pgrp == -1) + err(1, "setsid"); + } + + /* create pipes to keep in contact with children */ + rfds = reallocarray(NULL, procs, sizeof(int)); + if (rfds == NULL) + err(1, "rfds"); + fdmax = 0; + + /* fork child processes and pass writing end of pipe */ + for (i = 0; i < procs; i++) { + int pipefds[2]; + + if (pipe(pipefds) == -1) + err(1, "pipe"); + if (fdmax < pipefds[0]) + fdmax = pipefds[0]; + rfds[i] = pipefds[0]; + fork_sleep(pipefds[1]); + if (close(pipefds[1]) == -1) + err(1, "close parent"); + } + + /* create select mask with all reading ends of child pipes */ + fdlen = howmany(fdmax + 1, NFDBITS); + fdset = calloc(fdlen, sizeof(fd_mask)); + if (fdset == NULL) + err(1, "fdset"); + for (i = 0; i < procs; i++) { + FD_SET(rfds[i], fdset); + } + + /* wait until all child processes are waiting */ + do { + waiting = 0; + tv.tv_sec = timeout; + tv.tv_usec = 0; + errno = ETIMEDOUT; + if (select(fdmax + 1, fdset, NULL, NULL, &tv) <= 0) + err(1, "select"); + + /* remove fd of children that closed their end */ + for (i = 0; i < procs; i++) { + if (rfds[i] >= 0) { + if (FD_ISSET(rfds[i], fdset)) { + if (close(rfds[i]) == -1) + err(1, "close read"); + FD_CLR(rfds[i], fdset); + rfds[i] = -1; + } else { + FD_SET(rfds[i], fdset); + waiting = 1; + } + } + } + } while (waiting); + + /* kill all children simultaneously, parent exits in signal handler */ + if (signal(SIGTERM, sigexit) == SIG_ERR) + err(1, "signal SIGTERM"); + if (kill(-pgrp, SIGTERM) == -1) + err(1, "kill %d", -pgrp); + + errx(1, "alive"); +} |