Skip site navigation (1)Skip section navigation (2)



index | | raw e-mail

diff --git a/bin/pwait/tests/Makefile b/bin/pwait/tests/Makefile
index 8e916ae47b38..0f9359c92593 100644
--- a/bin/pwait/tests/Makefile
+++ b/bin/pwait/tests/Makefile
@@ -1,4 +1,4 @@
-
+ATF_TESTS_C=	pwait_reap
 ATF_TESTS_SH=	pwait_test
 
 .include <bsd.test.mk>
diff --git a/bin/pwait/tests/pwait_reap.c b/bin/pwait/tests/pwait_reap.c
new file mode 100644
index 000000000000..96c036ba2059
--- /dev/null
+++ b/bin/pwait/tests/pwait_reap.c
@@ -0,0 +1,188 @@
+/*-
+ * Copyright (c) 2026 Dag-Erling Smørgrav
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <atf-c.h>
+
+/*
+ * Wait for a process to terminate, optionally reap it, and verify that it
+ * exited cleanly.
+ */
+static void
+wait_zero(pid_t pid, bool reap)
+{
+	int wstatus;
+	pid_t ret;
+
+	do {
+		ret = waitpid(pid, &wstatus, reap ? 0 : WNOWAIT);
+		ATF_REQUIRE(ret == pid || (ret < 0 && errno == EINTR));
+	} while (ret != pid);
+	ATF_CHECK(WIFEXITED(wstatus));
+	ATF_CHECK_EQ(WEXITSTATUS(wstatus), 0);
+}
+
+/*
+ * Verify that pwait is still waiting for the target process.
+ */
+static void
+pwait_is_waiting(pid_t pwpid, pid_t tpid, int pwout, int pwerr)
+{
+	struct pollfd pfds[2];
+	char line[64] = "", *eol, *rest;
+	ssize_t ret;
+
+	pfds[0].fd = pwout;
+	pfds[0].events = POLLIN;
+	pfds[1].fd = pwerr;
+	pfds[1].events = POLLIN;
+
+	/* loop until we have some output */
+	do {
+		/* send SIGINFO so pwait prints the target PID on stderr */
+		ATF_REQUIRE(kill(pwpid, SIGINFO) == 0);
+	} while ((ret = poll(pfds, 2, 100)) == 0);
+	ATF_REQUIRE_EQ(ret, 1);
+	ATF_REQUIRE_EQ(pfds[1].revents, POLLIN);
+
+	/* read line from stderr */
+	ATF_REQUIRE((ret = read(pwerr, line, sizeof(line) - 1)) > 0);
+
+	/* verify the line */
+	ATF_REQUIRE((eol = strchr(line, '\n')) != NULL);
+	*eol = '\0';
+	ATF_REQUIRE_EQ(strtol(line, &rest, 10), tpid);
+	ATF_REQUIRE_STREQ(rest, "");
+}
+
+/*
+ * Verify that pwait has reported that the target process has terminated,
+ * then reap it.
+ */
+static void
+pwait_is_done(pid_t pwpid, pid_t tpid, int pwout, int pwerr)
+{
+	struct pollfd pfds[2];
+	char line[64] = "", *eol, *rest;
+	ssize_t ret;
+
+	pfds[0].fd = pwout;
+	pfds[0].events = POLLIN;
+	pfds[1].fd = pwerr;
+	pfds[1].events = POLLIN;
+
+	/* loop until we have some output */
+	do {
+		/* nothing */
+	} while ((ret = poll(pfds, 2, 100)) == 0);
+	ATF_REQUIRE_EQ(ret, 1);
+	ATF_REQUIRE_EQ(pfds[0].revents, POLLIN);
+
+	/* read line from stdout */
+	ATF_REQUIRE((ret = read(pwout, line, sizeof(line) - 1)) > 0);
+
+	/* verify the line */
+	ATF_REQUIRE((eol = strchr(line, '\n')) != NULL);
+	*eol = '\0';
+	ATF_REQUIRE_EQ(strtol(line, &rest, 10), tpid);
+	ATF_REQUIRE_STREQ(rest, ": exited with status 0.");
+
+	/* reap pwait */
+	wait_zero(pwpid, true);
+}
+
+static void
+pwait_reap_test(bool reap)
+{
+	char pidstr[16];
+	pid_t tpid, pwpid;
+	int tpipe[2], pwout[2], pwerr[2];
+
+	/* fork target process */
+	ATF_REQUIRE(pipe2(tpipe, O_CLOEXEC) == 0);
+	ATF_REQUIRE((tpid = fork()) >= 0);
+	if (tpid == 0) {
+		(void)close(tpipe[1]);
+		/* block until parent closes its end of the pipe */
+		(void)read(tpipe[0], pidstr, 1);
+		_exit(0);
+	}
+	(void)close(tpipe[0]);
+
+	/* fork pwait */
+	snprintf(pidstr, sizeof(pidstr), "%ld", (long)tpid);
+	ATF_REQUIRE(pipe2(pwout, O_CLOEXEC) == 0);
+	ATF_REQUIRE(pipe2(pwerr, O_CLOEXEC) == 0);
+	ATF_REQUIRE((pwpid = fork()) >= 0);
+	if (pwpid == 0) {
+		(void)dup2(pwout[0], STDOUT_FILENO);
+		(void)dup2(pwerr[0], STDERR_FILENO);
+		execlp("pwait", "pwait", reap ? "-rv" : "-v", pidstr, NULL);
+		_exit(99);
+	}
+
+	/* wait for pwait to become ready */
+	pwait_is_waiting(pwpid, tpid, pwout[1], pwerr[1]);
+
+	/* unblock the target and wait for it to terminate */
+	(void)close(tpipe[1]);
+	wait_zero(tpid, false);
+
+	if (reap) {
+		/* check that pwait is still waiting */
+		pwait_is_waiting(pwpid, tpid, pwout[1], pwerr[1]);
+	} else {
+		/* check that pwait has reported completion */
+		pwait_is_done(pwpid, tpid, pwout[1], pwerr[1]);
+	}
+
+	/* reap the target */
+	wait_zero(tpid, true);
+
+	if (reap) {
+		/* check that pwait has reported completion */
+		pwait_is_done(pwpid, tpid, pwout[1], pwerr[1]);
+	}
+}
+
+ATF_TC(pwait_normal);
+ATF_TC_HEAD(pwait_normal, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Test that pwait returns before the "
+	    "target process has been reaped");
+}
+ATF_TC_BODY(pwait_normal, tc)
+{
+	pwait_reap_test(true);
+}
+
+ATF_TC(pwait_reap);
+ATF_TC_HEAD(pwait_reap, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "Test that pwait -r does not return "
+	    "until the target process has been reaped");
+}
+ATF_TC_BODY(pwait_reap, tc)
+{
+	pwait_reap_test(true);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, pwait_normal);
+	ATF_TP_ADD_TC(tp, pwait_reap);
+	return (atf_no_error());
+}


home | help