diff options
Diffstat (limited to 'usr.bin/mail/tests/mailx_signal_test.c')
-rw-r--r-- | usr.bin/mail/tests/mailx_signal_test.c | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/usr.bin/mail/tests/mailx_signal_test.c b/usr.bin/mail/tests/mailx_signal_test.c new file mode 100644 index 000000000000..be03b54fd51e --- /dev/null +++ b/usr.bin/mail/tests/mailx_signal_test.c @@ -0,0 +1,196 @@ +/*- + * Copyright (c) 2025 Klara, Inc. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/poll.h> +#include <sys/wait.h> + +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <atf-c.h> + +#define MAILX "mailx" +#define BODY "hello\n" +#define BODYLEN (sizeof(BODY) - 1) + +/* + * When interactive, mailx(1) should print a message on receipt of SIGINT, + * then exit cleanly on receipt of a second. + * + * When not interactive, mailx(1) should terminate on receipt of SIGINT. + * + * In either case, mailx(1) should terminate on receipt of SIGHUP. + */ +static void +mailx_signal_test(int signo, bool interactive) +{ + char obuf[1024] = ""; + char ebuf[1024] = ""; + struct pollfd fds[2]; + int ipd[2], opd[2], epd[2], spd[2]; + time_t start, now; + size_t olen = 0, elen = 0; + ssize_t rlen; + pid_t pid; + int kc, status; + + /* input, output, error, sync pipes */ + if (pipe(ipd) != 0 || pipe(opd) != 0 || pipe(epd) != 0 || + pipe(spd) != 0 || fcntl(spd[1], F_SETFD, FD_CLOEXEC) != 0) + atf_tc_fail("failed to pipe"); + /* fork child */ + if ((pid = fork()) < 0) + atf_tc_fail("failed to fork"); + if (pid == 0) { + /* child */ + sigset_t set; + + /* + * Ensure mailx(1) will handle SIGINT; i.e., that it's not + * ignored or blocked. + */ + (void)signal(signo, SIG_DFL); + sigemptyset(&set); + sigaddset(&set, signo); + ATF_REQUIRE_INTEQ(0, sigprocmask(SIG_UNBLOCK, &set, NULL)); + + dup2(ipd[0], STDIN_FILENO); + close(ipd[0]); + close(ipd[1]); + dup2(opd[1], STDOUT_FILENO); + close(opd[0]); + close(opd[1]); + dup2(epd[1], STDERR_FILENO); + close(epd[0]); + close(epd[1]); + close(spd[0]); + /* force dead.letter to go to cwd */ + setenv("HOME", ".", 1); + /* exec mailx */ + execlp(MAILX, + MAILX, + interactive ? "-Is" : "-s", + "test", + "test@example.com", + NULL); + _exit(2); + } + /* parent */ + close(ipd[0]); + close(opd[1]); + close(epd[1]); + close(spd[1]); + /* block until child execs or exits */ + (void)read(spd[0], &spd[1], sizeof(spd[1])); + /* send one line of input */ + ATF_REQUIRE_INTEQ(BODYLEN, write(ipd[1], BODY, BODYLEN)); + /* give it a chance to process */ + poll(NULL, 0, 2000); + /* send first signal */ + ATF_CHECK_INTEQ(0, kill(pid, signo)); + kc = 1; + /* receive output until child terminates */ + fds[0].fd = opd[0]; + fds[0].events = POLLIN; + fds[1].fd = epd[0]; + fds[1].events = POLLIN; + time(&start); + for (;;) { + ATF_REQUIRE(poll(fds, 2, 1000) >= 0); + if (fds[0].revents == POLLIN && olen < sizeof(obuf)) { + rlen = read(opd[0], obuf + olen, sizeof(obuf) - olen - 1); + ATF_REQUIRE(rlen >= 0); + olen += rlen; + } + if (fds[1].revents == POLLIN && elen < sizeof(ebuf)) { + rlen = read(epd[0], ebuf + elen, sizeof(ebuf) - elen - 1); + ATF_REQUIRE(rlen >= 0); + elen += rlen; + } + time(&now); + if (now - start > 1 && elen > 0 && kc == 1) { + ATF_CHECK_INTEQ(0, kill(pid, signo)); + kc++; + } + if (now - start > 15 && kc > 0) { + (void)kill(pid, SIGKILL); + kc = -1; + } + if (waitpid(pid, &status, WNOHANG) == pid) + break; + } + close(ipd[1]); + close(opd[0]); + close(epd[0]); + close(spd[0]); + /* + * In interactive mode, SIGINT results in a prompt, and a second + * SIGINT results in exit(1). In all other cases, we should see + * the signal terminate the process. + */ + if (interactive && signo == SIGINT) { + ATF_CHECK(WIFEXITED(status)); + if (WIFEXITED(status)) + ATF_CHECK_INTEQ(1, WEXITSTATUS(status)); + ATF_CHECK_INTEQ(2, kc); + ATF_CHECK_STREQ("", obuf); + ATF_CHECK_MATCH("Interrupt -- one more to kill letter", ebuf); + } else { + ATF_CHECK(WIFSIGNALED(status)); + if (WIFSIGNALED(status)) + ATF_CHECK_INTEQ(signo, WTERMSIG(status)); + ATF_CHECK_INTEQ(1, kc); + ATF_CHECK_STREQ("", obuf); + ATF_CHECK_STREQ("", ebuf); + } + /* + * In interactive mode, and only in interactive mode, mailx should + * save whatever was typed before termination in ~/dead.letter. + * This is why we set HOME to "." in the child. + */ + if (interactive) { + atf_utils_compare_file("dead.letter", BODY); + } else { + ATF_CHECK_INTEQ(-1, access("dead.letter", F_OK)); + } +} + +ATF_TC_WITHOUT_HEAD(mailx_sighup_interactive); +ATF_TC_BODY(mailx_sighup_interactive, tc) +{ + mailx_signal_test(SIGHUP, true); +} + +ATF_TC_WITHOUT_HEAD(mailx_sighup_noninteractive); +ATF_TC_BODY(mailx_sighup_noninteractive, tc) +{ + mailx_signal_test(SIGHUP, false); +} + +ATF_TC_WITHOUT_HEAD(mailx_sigint_interactive); +ATF_TC_BODY(mailx_sigint_interactive, tc) +{ + mailx_signal_test(SIGINT, true); +} + +ATF_TC_WITHOUT_HEAD(mailx_sigint_noninteractive); +ATF_TC_BODY(mailx_sigint_noninteractive, tc) +{ + mailx_signal_test(SIGINT, false); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, mailx_sighup_interactive); + ATF_TP_ADD_TC(tp, mailx_sighup_noninteractive); + ATF_TP_ADD_TC(tp, mailx_sigint_interactive); + ATF_TP_ADD_TC(tp, mailx_sigint_noninteractive); + return (atf_no_error()); +} |