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