1a6bac0a9SDag-Erling Smørgrav /*-
2a6bac0a9SDag-Erling Smørgrav * Copyright (c) 2025 Klara, Inc.
3a6bac0a9SDag-Erling Smørgrav *
4a6bac0a9SDag-Erling Smørgrav * SPDX-License-Identifier: BSD-2-Clause
5a6bac0a9SDag-Erling Smørgrav */
6a6bac0a9SDag-Erling Smørgrav
7a6bac0a9SDag-Erling Smørgrav #include <sys/poll.h>
8a6bac0a9SDag-Erling Smørgrav #include <sys/wait.h>
9a6bac0a9SDag-Erling Smørgrav
10a6bac0a9SDag-Erling Smørgrav #include <fcntl.h>
11a6bac0a9SDag-Erling Smørgrav #include <signal.h>
12a6bac0a9SDag-Erling Smørgrav #include <stdbool.h>
13a6bac0a9SDag-Erling Smørgrav #include <stdlib.h>
14a6bac0a9SDag-Erling Smørgrav #include <time.h>
15a6bac0a9SDag-Erling Smørgrav #include <unistd.h>
16a6bac0a9SDag-Erling Smørgrav
17a6bac0a9SDag-Erling Smørgrav #include <atf-c.h>
18a6bac0a9SDag-Erling Smørgrav
19a6bac0a9SDag-Erling Smørgrav #define MAILX "mailx"
20a6bac0a9SDag-Erling Smørgrav #define BODY "hello\n"
21a6bac0a9SDag-Erling Smørgrav #define BODYLEN (sizeof(BODY) - 1)
22a6bac0a9SDag-Erling Smørgrav
23a6bac0a9SDag-Erling Smørgrav /*
24a6bac0a9SDag-Erling Smørgrav * When interactive, mailx(1) should print a message on receipt of SIGINT,
25a6bac0a9SDag-Erling Smørgrav * then exit cleanly on receipt of a second.
26a6bac0a9SDag-Erling Smørgrav *
27a6bac0a9SDag-Erling Smørgrav * When not interactive, mailx(1) should terminate on receipt of SIGINT.
28a6bac0a9SDag-Erling Smørgrav *
29a6bac0a9SDag-Erling Smørgrav * In either case, mailx(1) should terminate on receipt of SIGHUP.
30a6bac0a9SDag-Erling Smørgrav */
31a6bac0a9SDag-Erling Smørgrav static void
mailx_signal_test(int signo,bool interactive)32a6bac0a9SDag-Erling Smørgrav mailx_signal_test(int signo, bool interactive)
33a6bac0a9SDag-Erling Smørgrav {
34a6bac0a9SDag-Erling Smørgrav char obuf[1024] = "";
35a6bac0a9SDag-Erling Smørgrav char ebuf[1024] = "";
36a6bac0a9SDag-Erling Smørgrav struct pollfd fds[2];
37a6bac0a9SDag-Erling Smørgrav int ipd[2], opd[2], epd[2], spd[2];
38a6bac0a9SDag-Erling Smørgrav time_t start, now;
39a6bac0a9SDag-Erling Smørgrav size_t olen = 0, elen = 0;
40a6bac0a9SDag-Erling Smørgrav ssize_t rlen;
41a6bac0a9SDag-Erling Smørgrav pid_t pid;
42a6bac0a9SDag-Erling Smørgrav int kc, status;
43a6bac0a9SDag-Erling Smørgrav
44a6bac0a9SDag-Erling Smørgrav /* input, output, error, sync pipes */
45a6bac0a9SDag-Erling Smørgrav if (pipe(ipd) != 0 || pipe(opd) != 0 || pipe(epd) != 0 ||
46a6bac0a9SDag-Erling Smørgrav pipe(spd) != 0 || fcntl(spd[1], F_SETFD, FD_CLOEXEC) != 0)
47a6bac0a9SDag-Erling Smørgrav atf_tc_fail("failed to pipe");
48a6bac0a9SDag-Erling Smørgrav /* fork child */
49a6bac0a9SDag-Erling Smørgrav if ((pid = fork()) < 0)
50a6bac0a9SDag-Erling Smørgrav atf_tc_fail("failed to fork");
51a6bac0a9SDag-Erling Smørgrav if (pid == 0) {
52a6bac0a9SDag-Erling Smørgrav /* child */
53*d5e5e241SKyle Evans sigset_t set;
54*d5e5e241SKyle Evans
55*d5e5e241SKyle Evans /*
56*d5e5e241SKyle Evans * Ensure mailx(1) will handle SIGINT; i.e., that it's not
57*d5e5e241SKyle Evans * ignored or blocked.
58*d5e5e241SKyle Evans */
59*d5e5e241SKyle Evans (void)signal(signo, SIG_DFL);
60*d5e5e241SKyle Evans sigemptyset(&set);
61*d5e5e241SKyle Evans sigaddset(&set, signo);
62*d5e5e241SKyle Evans ATF_REQUIRE_INTEQ(0, sigprocmask(SIG_UNBLOCK, &set, NULL));
63*d5e5e241SKyle Evans
64a6bac0a9SDag-Erling Smørgrav dup2(ipd[0], STDIN_FILENO);
65a6bac0a9SDag-Erling Smørgrav close(ipd[0]);
66a6bac0a9SDag-Erling Smørgrav close(ipd[1]);
67a6bac0a9SDag-Erling Smørgrav dup2(opd[1], STDOUT_FILENO);
68a6bac0a9SDag-Erling Smørgrav close(opd[0]);
69a6bac0a9SDag-Erling Smørgrav close(opd[1]);
70a6bac0a9SDag-Erling Smørgrav dup2(epd[1], STDERR_FILENO);
71a6bac0a9SDag-Erling Smørgrav close(epd[0]);
72a6bac0a9SDag-Erling Smørgrav close(epd[1]);
73a6bac0a9SDag-Erling Smørgrav close(spd[0]);
74a6bac0a9SDag-Erling Smørgrav /* force dead.letter to go to cwd */
75a6bac0a9SDag-Erling Smørgrav setenv("HOME", ".", 1);
76a6bac0a9SDag-Erling Smørgrav /* exec mailx */
77a6bac0a9SDag-Erling Smørgrav execlp(MAILX,
78a6bac0a9SDag-Erling Smørgrav MAILX,
79a6bac0a9SDag-Erling Smørgrav interactive ? "-Is" : "-s",
80a6bac0a9SDag-Erling Smørgrav "test",
81a6bac0a9SDag-Erling Smørgrav "test@example.com",
82a6bac0a9SDag-Erling Smørgrav NULL);
83a6bac0a9SDag-Erling Smørgrav _exit(2);
84a6bac0a9SDag-Erling Smørgrav }
85a6bac0a9SDag-Erling Smørgrav /* parent */
86a6bac0a9SDag-Erling Smørgrav close(ipd[0]);
87a6bac0a9SDag-Erling Smørgrav close(opd[1]);
88a6bac0a9SDag-Erling Smørgrav close(epd[1]);
89a6bac0a9SDag-Erling Smørgrav close(spd[1]);
90a6bac0a9SDag-Erling Smørgrav /* block until child execs or exits */
91a6bac0a9SDag-Erling Smørgrav (void)read(spd[0], &spd[1], sizeof(spd[1]));
92a6bac0a9SDag-Erling Smørgrav /* send one line of input */
93a6bac0a9SDag-Erling Smørgrav ATF_REQUIRE_INTEQ(BODYLEN, write(ipd[1], BODY, BODYLEN));
94a6bac0a9SDag-Erling Smørgrav /* give it a chance to process */
95a6bac0a9SDag-Erling Smørgrav poll(NULL, 0, 2000);
96a6bac0a9SDag-Erling Smørgrav /* send first signal */
97a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(0, kill(pid, signo));
98a6bac0a9SDag-Erling Smørgrav kc = 1;
99a6bac0a9SDag-Erling Smørgrav /* receive output until child terminates */
100a6bac0a9SDag-Erling Smørgrav fds[0].fd = opd[0];
101a6bac0a9SDag-Erling Smørgrav fds[0].events = POLLIN;
102a6bac0a9SDag-Erling Smørgrav fds[1].fd = epd[0];
103a6bac0a9SDag-Erling Smørgrav fds[1].events = POLLIN;
104a6bac0a9SDag-Erling Smørgrav time(&start);
105a6bac0a9SDag-Erling Smørgrav for (;;) {
106a6bac0a9SDag-Erling Smørgrav ATF_REQUIRE(poll(fds, 2, 1000) >= 0);
107a6bac0a9SDag-Erling Smørgrav if (fds[0].revents == POLLIN && olen < sizeof(obuf)) {
108a6bac0a9SDag-Erling Smørgrav rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen - 1);
109a6bac0a9SDag-Erling Smørgrav ATF_REQUIRE(rlen >= 0);
110a6bac0a9SDag-Erling Smørgrav olen += rlen;
111a6bac0a9SDag-Erling Smørgrav }
112a6bac0a9SDag-Erling Smørgrav if (fds[1].revents == POLLIN && elen < sizeof(ebuf)) {
113a6bac0a9SDag-Erling Smørgrav rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen - 1);
114a6bac0a9SDag-Erling Smørgrav ATF_REQUIRE(rlen >= 0);
115a6bac0a9SDag-Erling Smørgrav elen += rlen;
116a6bac0a9SDag-Erling Smørgrav }
117a6bac0a9SDag-Erling Smørgrav time(&now);
118a6bac0a9SDag-Erling Smørgrav if (now - start > 1 && elen > 0 && kc == 1) {
119a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(0, kill(pid, signo));
120a6bac0a9SDag-Erling Smørgrav kc++;
121a6bac0a9SDag-Erling Smørgrav }
122a6bac0a9SDag-Erling Smørgrav if (now - start > 15 && kc > 0) {
123a6bac0a9SDag-Erling Smørgrav (void)kill(pid, SIGKILL);
124a6bac0a9SDag-Erling Smørgrav kc = -1;
125a6bac0a9SDag-Erling Smørgrav }
126a6bac0a9SDag-Erling Smørgrav if (waitpid(pid, &status, WNOHANG) == pid)
127a6bac0a9SDag-Erling Smørgrav break;
128a6bac0a9SDag-Erling Smørgrav }
129a6bac0a9SDag-Erling Smørgrav close(ipd[1]);
130a6bac0a9SDag-Erling Smørgrav close(opd[0]);
131a6bac0a9SDag-Erling Smørgrav close(epd[0]);
132a6bac0a9SDag-Erling Smørgrav close(spd[0]);
133a6bac0a9SDag-Erling Smørgrav /*
134a6bac0a9SDag-Erling Smørgrav * In interactive mode, SIGINT results in a prompt, and a second
135a6bac0a9SDag-Erling Smørgrav * SIGINT results in exit(1). In all other cases, we should see
136a6bac0a9SDag-Erling Smørgrav * the signal terminate the process.
137a6bac0a9SDag-Erling Smørgrav */
138a6bac0a9SDag-Erling Smørgrav if (interactive && signo == SIGINT) {
139a6bac0a9SDag-Erling Smørgrav ATF_CHECK(WIFEXITED(status));
140a6bac0a9SDag-Erling Smørgrav if (WIFEXITED(status))
141a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(1, WEXITSTATUS(status));
142a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(2, kc);
143a6bac0a9SDag-Erling Smørgrav ATF_CHECK_STREQ("", obuf);
144a6bac0a9SDag-Erling Smørgrav ATF_CHECK_MATCH("Interrupt -- one more to kill letter", ebuf);
145a6bac0a9SDag-Erling Smørgrav } else {
146a6bac0a9SDag-Erling Smørgrav ATF_CHECK(WIFSIGNALED(status));
147a6bac0a9SDag-Erling Smørgrav if (WIFSIGNALED(status))
148a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(signo, WTERMSIG(status));
149a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(1, kc);
150a6bac0a9SDag-Erling Smørgrav ATF_CHECK_STREQ("", obuf);
151a6bac0a9SDag-Erling Smørgrav ATF_CHECK_STREQ("", ebuf);
152a6bac0a9SDag-Erling Smørgrav }
153a6bac0a9SDag-Erling Smørgrav /*
154a6bac0a9SDag-Erling Smørgrav * In interactive mode, and only in interactive mode, mailx should
155a6bac0a9SDag-Erling Smørgrav * save whatever was typed before termination in ~/dead.letter.
156a6bac0a9SDag-Erling Smørgrav * This is why we set HOME to "." in the child.
157a6bac0a9SDag-Erling Smørgrav */
158a6bac0a9SDag-Erling Smørgrav if (interactive) {
159a6bac0a9SDag-Erling Smørgrav atf_utils_compare_file("dead.letter", BODY);
160a6bac0a9SDag-Erling Smørgrav } else {
161a6bac0a9SDag-Erling Smørgrav ATF_CHECK_INTEQ(-1, access("dead.letter", F_OK));
162a6bac0a9SDag-Erling Smørgrav }
163a6bac0a9SDag-Erling Smørgrav }
164a6bac0a9SDag-Erling Smørgrav
165a6bac0a9SDag-Erling Smørgrav ATF_TC_WITHOUT_HEAD(mailx_sighup_interactive);
ATF_TC_BODY(mailx_sighup_interactive,tc)166a6bac0a9SDag-Erling Smørgrav ATF_TC_BODY(mailx_sighup_interactive, tc)
167a6bac0a9SDag-Erling Smørgrav {
168a6bac0a9SDag-Erling Smørgrav mailx_signal_test(SIGHUP, true);
169a6bac0a9SDag-Erling Smørgrav }
170a6bac0a9SDag-Erling Smørgrav
171a6bac0a9SDag-Erling Smørgrav ATF_TC_WITHOUT_HEAD(mailx_sighup_noninteractive);
ATF_TC_BODY(mailx_sighup_noninteractive,tc)172a6bac0a9SDag-Erling Smørgrav ATF_TC_BODY(mailx_sighup_noninteractive, tc)
173a6bac0a9SDag-Erling Smørgrav {
174a6bac0a9SDag-Erling Smørgrav mailx_signal_test(SIGHUP, false);
175a6bac0a9SDag-Erling Smørgrav }
176a6bac0a9SDag-Erling Smørgrav
177a6bac0a9SDag-Erling Smørgrav ATF_TC_WITHOUT_HEAD(mailx_sigint_interactive);
ATF_TC_BODY(mailx_sigint_interactive,tc)178a6bac0a9SDag-Erling Smørgrav ATF_TC_BODY(mailx_sigint_interactive, tc)
179a6bac0a9SDag-Erling Smørgrav {
180a6bac0a9SDag-Erling Smørgrav mailx_signal_test(SIGINT, true);
181a6bac0a9SDag-Erling Smørgrav }
182a6bac0a9SDag-Erling Smørgrav
183a6bac0a9SDag-Erling Smørgrav ATF_TC_WITHOUT_HEAD(mailx_sigint_noninteractive);
ATF_TC_BODY(mailx_sigint_noninteractive,tc)184a6bac0a9SDag-Erling Smørgrav ATF_TC_BODY(mailx_sigint_noninteractive, tc)
185a6bac0a9SDag-Erling Smørgrav {
186a6bac0a9SDag-Erling Smørgrav mailx_signal_test(SIGINT, false);
187a6bac0a9SDag-Erling Smørgrav }
188a6bac0a9SDag-Erling Smørgrav
ATF_TP_ADD_TCS(tp)189a6bac0a9SDag-Erling Smørgrav ATF_TP_ADD_TCS(tp)
190a6bac0a9SDag-Erling Smørgrav {
191a6bac0a9SDag-Erling Smørgrav ATF_TP_ADD_TC(tp, mailx_sighup_interactive);
192a6bac0a9SDag-Erling Smørgrav ATF_TP_ADD_TC(tp, mailx_sighup_noninteractive);
193a6bac0a9SDag-Erling Smørgrav ATF_TP_ADD_TC(tp, mailx_sigint_interactive);
194a6bac0a9SDag-Erling Smørgrav ATF_TP_ADD_TC(tp, mailx_sigint_noninteractive);
195a6bac0a9SDag-Erling Smørgrav return (atf_no_error());
196a6bac0a9SDag-Erling Smørgrav }
197