diff options
Diffstat (limited to 'corebinutils/pwait/pwait.c')
| -rw-r--r-- | corebinutils/pwait/pwait.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/corebinutils/pwait/pwait.c b/corebinutils/pwait/pwait.c new file mode 100644 index 0000000000..323a798e86 --- /dev/null +++ b/corebinutils/pwait/pwait.c @@ -0,0 +1,332 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2004-2009, Jilles Tjoelker + * Copyright (c) 2026 Project Tick. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with + * or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * 2. Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/syscall.h> +#include <sys/timerfd.h> +#include <sys/wait.h> + +#include <err.h> +#include <errno.h> +#include <poll.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> +#include <limits.h> +#include <stdint.h> + +static int +x_pidfd_open(pid_t pid) +{ +#if defined(__NR_pidfd_open) + return syscall(__NR_pidfd_open, pid, 0); +#elif defined(SYS_pidfd_open) + return syscall(SYS_pidfd_open, pid, 0); +#else + errno = ENOSYS; + return -1; +#endif +} + +// We need to keep track of pids, whether they are done, and their open pidfd +struct target_pid { + long pid; + int fd; + bool done; +}; + +static int +pidcmp(const void *a, const void *b) +{ + const struct target_pid *pa = a; + const struct target_pid *pb = b; + return (pa->pid > pb->pid ? 1 : pa->pid < pb->pid ? -1 : 0); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: pwait [-t timeout] [-opv] pid ...\n"); + exit(EX_USAGE); +} + +/* + * pwait - wait for processes to terminate + */ +int +main(int argc, char *argv[]) +{ + struct target_pid *targets = NULL; + char *end, *s; + double timeout = 0.0; + long pid; + pid_t mypid; + int ntargets = 0, nallocated = 0; + int ndone = 0, nleft = 0, opt, ret; + bool oflag = false, pflag = false, tflag = false, verbose = false; + + while ((opt = getopt(argc, argv, "opt:v")) != -1) { + switch (opt) { + case 'o': + oflag = true; + break; + case 'p': + pflag = true; + break; + case 't': + tflag = true; + errno = 0; + timeout = strtod(optarg, &end); + if (end == optarg || errno == ERANGE || timeout < 0) { + errx(EX_DATAERR, "timeout value"); + } + switch (*end) { + case '\0': + break; + case 's': + end++; + break; + case 'h': + timeout *= 60; + /* FALLTHROUGH */ + case 'm': + timeout *= 60; + end++; + break; + default: + errx(EX_DATAERR, "timeout unit"); + } + if (*end != '\0') { + errx(EX_DATAERR, "timeout unit"); + } + if (timeout > 100000000L) { + errx(EX_DATAERR, "timeout value"); + } + break; + case 'v': + verbose = true; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (argc == 0) { + usage(); + } + + mypid = getpid(); + long pid_max = 4194304; + FILE *f = fopen("/proc/sys/kernel/pid_max", "r"); + if (f != NULL) { + if (fscanf(f, "%ld", &pid_max) != 1) { + pid_max = 4194304; + } + fclose(f); + } + + for (int n = 0; n < argc; n++) { + s = argv[n]; + /* Undocumented Solaris compat */ + if (strncmp(s, "/proc/", 6) == 0) { + s += 6; + } + errno = 0; + pid = strtol(s, &end, 10); + if (pid < 0 || pid > pid_max || *end != '\0' || errno != 0) { + warnx("%s: bad process id", s); + continue; + } + if (pid == mypid) { + warnx("%s: skipping my own pid", s); + continue; + } + + /* Check for duplicates */ + bool dup = false; + for (int i = 0; i < ntargets; i++) { + if (targets[i].pid == pid) { + dup = true; + break; + } + } + if (dup) { + continue; + } + + if (ntargets >= nallocated) { + nallocated = nallocated == 0 ? 16 : nallocated * 2; + targets = realloc(targets, nallocated * sizeof(struct target_pid)); + if (targets == NULL) { + err(EX_OSERR, "realloc"); + } + } + + targets[ntargets].pid = pid; + targets[ntargets].done = false; + int fd = x_pidfd_open((pid_t)pid); + if (fd == -1) { + /* Not found or invalid */ + warn("%ld", pid); + targets[ntargets].fd = -1; + targets[ntargets].done = true; + ndone++; + } else { + targets[ntargets].fd = fd; + nleft++; + } + ntargets++; + } + + /* Sort targets ascending by PID to match BSD behavior */ + qsort(targets, ntargets, sizeof(struct target_pid), pidcmp); + + int timer_fd = -1; + if ((ndone == 0 || !oflag) && nleft > 0 && tflag) { + timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC); + if (timer_fd == -1) { + err(EX_OSERR, "timerfd_create"); + } + struct itimerspec its = {}; + its.it_value.tv_sec = (time_t)timeout; + its.it_value.tv_nsec = (long)((timeout - its.it_value.tv_sec) * 1000000000UL); + if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) { + /* timerfd needs non-zero value to arm */ + its.it_value.tv_nsec = 1; + } + if (timerfd_settime(timer_fd, 0, &its, NULL) == -1) { + err(EX_OSERR, "timerfd_settime"); + } + } + + struct pollfd *pfds = calloc(ntargets + 1, sizeof(struct pollfd)); + if (pfds == NULL) { + err(EX_OSERR, "calloc"); + } + + ret = EX_OK; + while ((ndone == 0 || !oflag) && ret == EX_OK && nleft > 0) { + int npoll = 0; + if (timer_fd != -1) { + pfds[npoll].fd = timer_fd; + pfds[npoll].events = POLLIN; + pfds[npoll].revents = 0; + npoll++; + } + + for (int i = 0; i < ntargets; i++) { + if (!targets[i].done) { + pfds[npoll].fd = targets[i].fd; + pfds[npoll].events = POLLIN | POLLHUP | POLLERR; + pfds[npoll].revents = 0; + npoll++; + } + } + + int n = poll(pfds, npoll, -1); + if (n == -1) { + if (errno == EINTR) { + continue; + } + err(EX_OSERR, "poll"); + } + + if (timer_fd != -1 && (pfds[0].revents & POLLIN)) { + uint64_t expirations; + (void)read(timer_fd, &expirations, sizeof(expirations)); + if (verbose) { + printf("timeout\n"); + } + ret = 124; + } + + int pfds_idx = timer_fd != -1 ? 1 : 0; + for (int i = 0; i < ntargets; i++) { + if (targets[i].done) { + continue; + } + if (pfds[pfds_idx].revents & (POLLIN | POLLHUP | POLLERR)) { + pid = targets[i].pid; + if (verbose) { + siginfo_t info = {}; + int wret = waitid(P_PIDFD, targets[i].fd, &info, WEXITED | WNOHANG); + if (wret == 0 && info.si_pid != 0) { + if (info.si_code == CLD_EXITED) { + printf("%ld: exited with status %d.\n", + pid, info.si_status); + } else if (info.si_code == CLD_KILLED || info.si_code == CLD_DUMPED) { + printf("%ld: killed by signal %d.\n", + pid, info.si_status); + } else { + printf("%ld: terminated.\n", pid); + } + } else { + printf("%ld: terminated.\n", pid); + } + } + close(targets[i].fd); + targets[i].fd = -1; + targets[i].done = true; + ndone++; + nleft--; + } + pfds_idx++; + } + } + + if (timer_fd != -1) { + close(timer_fd); + } + free(pfds); + + if (pflag) { + for (int i = 0; i < ntargets; i++) { + if (!targets[i].done) { + printf("%ld\n", targets[i].pid); + } + } + } + + free(targets); + exit(ret); +} |
