xref: /freebsd/bin/pwait/tests/pwait_reap.c (revision 51c0cdb04919f776516a7fac8529e7279ea5efad)
1 /*-
2  * Copyright (c) 2026 Dag-Erling Smørgrav
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/wait.h>
8 
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <poll.h>
12 #include <signal.h>
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <unistd.h>
17 
18 #include <atf-c.h>
19 
20 /*
21  * Wait for a process to terminate, optionally reap it, and verify that it
22  * exited cleanly.
23  */
24 static void
wait_zero(pid_t pid,bool reap)25 wait_zero(pid_t pid, bool reap)
26 {
27 	int wstatus;
28 	pid_t ret;
29 
30 	do {
31 		ret = waitpid(pid, &wstatus, reap ? 0 : WNOWAIT);
32 		ATF_REQUIRE(ret == pid || (ret < 0 && errno == EINTR));
33 	} while (ret != pid);
34 	ATF_CHECK(WIFEXITED(wstatus));
35 	ATF_CHECK_EQ(WEXITSTATUS(wstatus), 0);
36 }
37 
38 /*
39  * Verify that pwait is still waiting for the target process.
40  */
41 static void
pwait_is_waiting(pid_t pwpid,pid_t tpid,int pwout,int pwerr)42 pwait_is_waiting(pid_t pwpid, pid_t tpid, int pwout, int pwerr)
43 {
44 	struct pollfd pfds[2];
45 	char line[64] = "", *eol, *rest;
46 	ssize_t ret;
47 
48 	pfds[0].fd = pwout;
49 	pfds[0].events = POLLIN;
50 	pfds[1].fd = pwerr;
51 	pfds[1].events = POLLIN;
52 
53 	/* loop until we have some output */
54 	do {
55 		/* send SIGINFO so pwait prints the target PID on stderr */
56 		ATF_REQUIRE(kill(pwpid, SIGINFO) == 0);
57 	} while ((ret = poll(pfds, 2, 100)) == 0);
58 	ATF_REQUIRE_EQ(ret, 1);
59 	ATF_REQUIRE_EQ(pfds[1].revents, POLLIN);
60 
61 	/* read line from stderr */
62 	ATF_REQUIRE((ret = read(pwerr, line, sizeof(line) - 1)) > 0);
63 
64 	/* verify the line */
65 	ATF_REQUIRE((eol = strchr(line, '\n')) != NULL);
66 	*eol = '\0';
67 	ATF_REQUIRE_EQ(strtol(line, &rest, 10), tpid);
68 	ATF_REQUIRE_STREQ(rest, "");
69 }
70 
71 /*
72  * Verify that pwait has reported that the target process has terminated,
73  * then reap it.
74  */
75 static void
pwait_is_done(pid_t pwpid,pid_t tpid,int pwout,int pwerr)76 pwait_is_done(pid_t pwpid, pid_t tpid, int pwout, int pwerr)
77 {
78 	struct pollfd pfds[2];
79 	char line[64] = "", *eol, *rest;
80 	ssize_t ret;
81 
82 	pfds[0].fd = pwout;
83 	pfds[0].events = POLLIN;
84 	pfds[1].fd = pwerr;
85 	pfds[1].events = POLLIN;
86 
87 	/* loop until we have some output */
88 	do {
89 		/* nothing */
90 	} while ((ret = poll(pfds, 2, 100)) == 0);
91 	ATF_REQUIRE_EQ(ret, 1);
92 	ATF_REQUIRE_EQ(pfds[0].revents, POLLIN);
93 
94 	/* read line from stdout */
95 	ATF_REQUIRE((ret = read(pwout, line, sizeof(line) - 1)) > 0);
96 
97 	/* verify the line */
98 	ATF_REQUIRE((eol = strchr(line, '\n')) != NULL);
99 	*eol = '\0';
100 	ATF_REQUIRE_EQ(strtol(line, &rest, 10), tpid);
101 	ATF_REQUIRE_STREQ(rest, ": exited with status 0.");
102 
103 	/* reap pwait */
104 	wait_zero(pwpid, true);
105 }
106 
107 static void
pwait_reap_test(bool reap)108 pwait_reap_test(bool reap)
109 {
110 	char pidstr[16];
111 	pid_t tpid, pwpid;
112 	int tpipe[2], pwout[2], pwerr[2];
113 
114 	/* fork target process */
115 	ATF_REQUIRE(pipe2(tpipe, O_CLOEXEC) == 0);
116 	ATF_REQUIRE((tpid = fork()) >= 0);
117 	if (tpid == 0) {
118 		(void)close(tpipe[1]);
119 		/* block until parent closes its end of the pipe */
120 		(void)read(tpipe[0], pidstr, 1);
121 		_exit(0);
122 	}
123 	(void)close(tpipe[0]);
124 
125 	/* fork pwait */
126 	snprintf(pidstr, sizeof(pidstr), "%ld", (long)tpid);
127 	ATF_REQUIRE(pipe2(pwout, O_CLOEXEC) == 0);
128 	ATF_REQUIRE(pipe2(pwerr, O_CLOEXEC) == 0);
129 	ATF_REQUIRE((pwpid = fork()) >= 0);
130 	if (pwpid == 0) {
131 		(void)dup2(pwout[0], STDOUT_FILENO);
132 		(void)dup2(pwerr[0], STDERR_FILENO);
133 		execlp("pwait", "pwait", reap ? "-rv" : "-v", pidstr, NULL);
134 		_exit(99);
135 	}
136 
137 	/* wait for pwait to become ready */
138 	pwait_is_waiting(pwpid, tpid, pwout[1], pwerr[1]);
139 
140 	/* unblock the target and wait for it to terminate */
141 	(void)close(tpipe[1]);
142 	wait_zero(tpid, false);
143 
144 	if (reap) {
145 		/* check that pwait is still waiting */
146 		pwait_is_waiting(pwpid, tpid, pwout[1], pwerr[1]);
147 	} else {
148 		/* check that pwait has reported completion */
149 		pwait_is_done(pwpid, tpid, pwout[1], pwerr[1]);
150 	}
151 
152 	/* reap the target */
153 	wait_zero(tpid, true);
154 
155 	if (reap) {
156 		/* check that pwait has reported completion */
157 		pwait_is_done(pwpid, tpid, pwout[1], pwerr[1]);
158 	}
159 }
160 
161 ATF_TC(pwait_normal);
ATF_TC_HEAD(pwait_normal,tc)162 ATF_TC_HEAD(pwait_normal, tc)
163 {
164 	atf_tc_set_md_var(tc, "descr", "Test that pwait returns before the "
165 	    "target process has been reaped");
166 }
ATF_TC_BODY(pwait_normal,tc)167 ATF_TC_BODY(pwait_normal, tc)
168 {
169 	pwait_reap_test(false);
170 }
171 
172 ATF_TC(pwait_reap);
ATF_TC_HEAD(pwait_reap,tc)173 ATF_TC_HEAD(pwait_reap, tc)
174 {
175 	atf_tc_set_md_var(tc, "descr", "Test that pwait -r does not return "
176 	    "until the target process has been reaped");
177 }
ATF_TC_BODY(pwait_reap,tc)178 ATF_TC_BODY(pwait_reap, tc)
179 {
180 	pwait_reap_test(true);
181 }
182 
ATF_TP_ADD_TCS(tp)183 ATF_TP_ADD_TCS(tp)
184 {
185 	ATF_TP_ADD_TC(tp, pwait_normal);
186 	ATF_TP_ADD_TC(tp, pwait_reap);
187 	return (atf_no_error());
188 }
189