xref: /freebsd/usr.bin/mail/tests/mailx_signal_test.c (revision d5e5e24179f4a98efaadea2b3c43006b322d7f15)
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