git: 3d73146baeb9 - main - pwait: Add an option to print remaining processes

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Tue, 28 Oct 2025 11:57:43 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=3d73146baeb933fe955c7496572b483a9f92914c

commit 3d73146baeb933fe955c7496572b483a9f92914c
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-10-28 11:56:36 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-10-28 11:57:16 +0000

    pwait: Add an option to print remaining processes
    
    * On startup, insert all valid PIDs into a tree.
    * In our main loop, whenever a process terminates, remove its PID
      from the tree.
    * On exit, if the -p flag was specified, print the remaining PIDs.
    
    MFC after:      3 days
    Reviewed by:    bcr, markj
    Differential Revision:  https://reviews.freebsd.org/D53293
---
 bin/pwait/pwait.1             |  6 ++-
 bin/pwait/pwait.c             | 98 ++++++++++++++++++++++++++++---------------
 bin/pwait/tests/pwait_test.sh | 38 +++++++++++++++++
 3 files changed, 107 insertions(+), 35 deletions(-)

diff --git a/bin/pwait/pwait.1 b/bin/pwait/pwait.1
index 83ac8bcef317..d92b829b1d6a 100644
--- a/bin/pwait/pwait.1
+++ b/bin/pwait/pwait.1
@@ -30,7 +30,7 @@
 .\" USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 .\" OF SUCH DAMAGE.
 .\"
-.Dd January 21, 2021
+.Dd October 22, 2025
 .Dt PWAIT 1
 .Os
 .Sh NAME
@@ -39,7 +39,7 @@
 .Sh SYNOPSIS
 .Nm
 .Op Fl t Ar duration
-.Op Fl ov
+.Op Fl opv
 .Ar pid
 \&...
 .Sh DESCRIPTION
@@ -51,6 +51,8 @@ The following option is available:
 .Bl -tag -width indent
 .It Fl o
 Exit when any of the given processes has terminated.
+.It Fl p
+On exit, print a list of processes that have not terminated.
 .It Fl t Ar duration
 If any process is still running after
 .Ar duration ,
diff --git a/bin/pwait/pwait.c b/bin/pwait/pwait.c
index 27f4c8e9858d..59bf0eb93ced 100644
--- a/bin/pwait/pwait.c
+++ b/bin/pwait/pwait.c
@@ -33,7 +33,9 @@
 
 #include <sys/types.h>
 #include <sys/event.h>
+#include <sys/sysctl.h>
 #include <sys/time.h>
+#include <sys/tree.h>
 #include <sys/wait.h>
 
 #include <err.h>
@@ -46,10 +48,25 @@
 #include <sysexits.h>
 #include <unistd.h>
 
+struct pid {
+	RB_ENTRY(pid) entry;
+	pid_t pid;
+};
+
+static int
+pidcmp(const struct pid *a, const struct pid *b)
+{
+	return (a->pid > b->pid ? 1 : a->pid < b->pid ? -1 : 0);
+}
+
+RB_HEAD(pidtree, pid);
+static struct pidtree pids = RB_INITIALIZER(&pids);
+RB_GENERATE_STATIC(pidtree, pid, entry, pidcmp);
+
 static void
 usage(void)
 {
-	fprintf(stderr, "usage: pwait [-t timeout] [-ov] pid ...\n");
+	fprintf(stderr, "usage: pwait [-t timeout] [-opv] pid ...\n");
 	exit(EX_USAGE);
 }
 
@@ -61,22 +78,28 @@ main(int argc, char *argv[])
 {
 	struct itimerval itv;
 	struct kevent *e;
+	struct pid k, *p;
 	char *end, *s;
 	double timeout;
+	size_t sz;
 	long pid;
 	pid_t mypid;
-	int i, kq, n, nleft, opt, status;
-	bool oflag, tflag, verbose;
+	int i, kq, n, ndone, nleft, opt, pid_max, ret, status;
+	bool oflag, pflag, tflag, verbose;
 
 	oflag = false;
+	pflag = false;
 	tflag = false;
 	verbose = false;
 	memset(&itv, 0, sizeof(itv));
 
-	while ((opt = getopt(argc, argv, "ot:v")) != -1) {
+	while ((opt = getopt(argc, argv, "opt:v")) != -1) {
 		switch (opt) {
 		case 'o':
-			oflag = 1;
+			oflag = true;
+			break;
+		case 'p':
+			pflag = true;
 			break;
 		case 't':
 			tflag = true;
@@ -128,16 +151,17 @@ main(int argc, char *argv[])
 		usage();
 	}
 
-	kq = kqueue();
-	if (kq == -1) {
+	if ((kq = kqueue()) < 0)
 		err(EX_OSERR, "kqueue");
-	}
 
-	e = malloc((argc + tflag) * sizeof(struct kevent));
-	if (e == NULL) {
+	sz = sizeof(pid_max);
+	if (sysctlbyname("kern.pid_max", &pid_max, &sz, NULL, 0) != 0) {
+		pid_max = 99999;
+	}
+	if ((e = malloc((argc + tflag) * sizeof(*e))) == NULL) {
 		err(EX_OSERR, "malloc");
 	}
-	nleft = 0;
+	ndone = nleft = 0;
 	mypid = getpid();
 	for (n = 0; n < argc; n++) {
 		s = argv[n];
@@ -147,7 +171,7 @@ main(int argc, char *argv[])
 		}
 		errno = 0;
 		pid = strtol(s, &end, 10);
-		if (pid < 0 || *end != '\0' || errno != 0) {
+		if (pid < 0 || pid > pid_max || *end != '\0' || errno != 0) {
 			warnx("%s: bad process id", s);
 			continue;
 		}
@@ -155,27 +179,29 @@ main(int argc, char *argv[])
 			warnx("%s: skipping my own pid", s);
 			continue;
 		}
-		for (i = 0; i < nleft; i++) {
-			if (e[i].ident == (uintptr_t)pid) {
-				break;
-			}
+		if ((p = malloc(sizeof(*p))) == NULL) {
+			err(EX_OSERR, NULL);
 		}
-		if (i < nleft) {
+		p->pid = pid;
+		if (RB_INSERT(pidtree, &pids, p) != NULL) {
 			/* Duplicate. */
+			free(p);
 			continue;
 		}
 		EV_SET(e + nleft, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
 		if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) {
+			if (errno != ESRCH)
+				err(EX_OSERR, "kevent()");
 			warn("%ld", pid);
-			if (oflag) {
-				exit(EX_OK);
-			}
+			RB_REMOVE(pidtree, &pids, p);
+			free(p);
+			ndone++;
 		} else {
 			nleft++;
 		}
 	}
 
-	if (nleft > 0 && tflag) {
+	if ((ndone == 0 || !oflag) && nleft > 0 && tflag) {
 		/*
 		 * Explicitly detect SIGALRM so that an exit status of 124
 		 * can be returned rather than 142.
@@ -190,7 +216,8 @@ main(int argc, char *argv[])
 			err(EX_OSERR, "setitimer");
 		}
 	}
-	while (nleft > 0) {
+	ret = EX_OK;
+	while ((ndone == 0 || !oflag) && ret == EX_OK && nleft > 0) {
 		n = kevent(kq, NULL, 0, e, nleft + tflag, NULL);
 		if (n == -1) {
 			err(EX_OSERR, "kevent");
@@ -200,29 +227,34 @@ main(int argc, char *argv[])
 				if (verbose) {
 					printf("timeout\n");
 				}
-				exit(124);
+				ret = 124;
 			}
+			pid = e[i].ident;
 			if (verbose) {
 				status = e[i].data;
 				if (WIFEXITED(status)) {
 					printf("%ld: exited with status %d.\n",
-					    (long)e[i].ident,
-					    WEXITSTATUS(status));
+					    pid, WEXITSTATUS(status));
 				} else if (WIFSIGNALED(status)) {
 					printf("%ld: killed by signal %d.\n",
-					    (long)e[i].ident,
-					    WTERMSIG(status));
+					    pid, WTERMSIG(status));
 				} else {
-					printf("%ld: terminated.\n",
-					    (long)e[i].ident);
+					printf("%ld: terminated.\n", pid);
 				}
 			}
-			if (oflag) {
-				exit(EX_OK);
+			k.pid = pid;
+			if ((p = RB_FIND(pidtree, &pids, &k)) != NULL) {
+				RB_REMOVE(pidtree, &pids, p);
+				free(p);
+				ndone++;
 			}
 			--nleft;
 		}
 	}
-
-	exit(EX_OK);
+	if (pflag) {
+		RB_FOREACH(p, pidtree, &pids) {
+			printf("%d\n", p->pid);
+		}
+	}
+	exit(ret);
 }
diff --git a/bin/pwait/tests/pwait_test.sh b/bin/pwait/tests/pwait_test.sh
index 66bdd6981704..d31ca21cff93 100644
--- a/bin/pwait/tests/pwait_test.sh
+++ b/bin/pwait/tests/pwait_test.sh
@@ -310,6 +310,43 @@ or_flag_cleanup()
 	wait $p2 $p4 $p6 >/dev/null 2>&1
 }
 
+atf_test_case print
+print_head()
+{
+	atf_set "descr" "Test the -p flag"
+}
+
+print_body()
+{
+	sleep 1 &
+	p1=$!
+
+	sleep 5 &
+	p5=$!
+
+	sleep 10 &
+	p10=$!
+
+	atf_check \
+		-o inline:"$p5\n$p10\n" \
+		-s exit:124 \
+		pwait -t 2 -p $p10 $p5 $p1 $p5 $p10
+
+	atf_check \
+		-e inline:"kill: $p1: No such process\n" \
+		-s exit:1 \
+		kill -0 $p1
+
+	atf_check kill -0 $p5
+	atf_check kill -0 $p10
+}
+
+print_cleanup()
+{
+	kill $p1 $p5 $p10 >/dev/null 2>&1
+	wait $p1 $p5 $p10 >/dev/null 2>&1
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case basic
@@ -318,4 +355,5 @@ atf_init_test_cases()
 	atf_add_test_case timeout_no_timeout
 	atf_add_test_case timeout_many
 	atf_add_test_case or_flag
+	atf_add_test_case print
 }