| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <signal.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <sys/prctl.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| |
| #include <atomic> |
| #include <vector> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/starnix/tests/syscalls/cpp/test_helper.h" |
| |
| namespace { |
| |
| std::vector<int> g_received_signals; |
| std::atomic<int> g_bad_signal_code; |
| |
| void handler(int signum, siginfo_t *info, void *ucontext) { |
| if (info->si_code != CLD_EXITED) { |
| g_bad_signal_code.store(info->si_code); |
| return; |
| } |
| |
| g_received_signals.push_back(signum); |
| } |
| |
| // This test_helper::CloneHelper instance must only be used after a clone without 'CLONE_THREAD | |
| // CLONE_VM'. |
| test_helper::CloneHelper nested_clone_helper; |
| |
| void ensureWait(int pid, unsigned int waitFlags) { |
| int actual_waitpid = waitpid(pid, nullptr, waitFlags); |
| EXPECT_EQ(errno, 0); |
| EXPECT_EQ(pid, actual_waitpid); |
| } |
| } // namespace |
| |
| class SignalHelper { |
| public: |
| SignalHelper() { |
| g_received_signals.clear(); |
| g_received_signals.reserve(10); |
| g_bad_signal_code.store(0); |
| errno = 0; |
| |
| struct sigaction sa; |
| sa.sa_sigaction = handler; |
| sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NOCLDSTOP; |
| |
| sigaction(SIGUSR1, &sa, &old_usr1_act_); |
| sigaction(SIGCHLD, &sa, &old_chld_act_); |
| } |
| |
| ~SignalHelper() { |
| sigaction(SIGUSR1, &old_usr1_act_, nullptr); |
| sigaction(SIGCHLD, &old_chld_act_, nullptr); |
| } |
| |
| SignalHelper(const SignalHelper &) = delete; |
| SignalHelper &operator=(const SignalHelper &) = delete; |
| |
| private: |
| struct sigaction old_usr1_act_; |
| struct sigaction old_chld_act_; |
| }; |
| |
| /* |
| * Main process (P0) creates a child process (P1). |
| * On termination, P1 sends its exit signal (if any) to P0. |
| */ |
| TEST(WaitpidExitSignalTest, childProcessSendsDefaultSignalOnTerminationToParentProcess) { |
| test_helper::ForkHelper helper; |
| |
| helper.RunInForkedProcess([] { |
| SignalHelper signal_helper; |
| test_helper::CloneHelper test_clone_helper; |
| int pid = test_clone_helper.runInClonedChild(SIGCHLD, test_helper::CloneHelper::doNothing); |
| ensureWait(pid, __WALL); |
| EXPECT_TRUE(g_received_signals.size() == 1); |
| EXPECT_EQ(g_received_signals[0], SIGCHLD); |
| EXPECT_EQ(0, g_bad_signal_code.load()); |
| }); |
| |
| EXPECT_TRUE(helper.WaitForChildren()); |
| } |
| |
| TEST(WaitpidExitSignalTest, childProcessSendsCustomExitSignalOnTerminationToParentProcess) { |
| test_helper::ForkHelper helper; |
| |
| helper.RunInForkedProcess([] { |
| SignalHelper signal_helper; |
| test_helper::CloneHelper test_clone_helper; |
| int pid = test_clone_helper.runInClonedChild(SIGUSR1, test_helper::CloneHelper::doNothing); |
| ensureWait(pid, __WALL); |
| EXPECT_TRUE(g_received_signals.size() == 1); |
| EXPECT_EQ(g_received_signals[0], SIGUSR1); |
| EXPECT_EQ(0, g_bad_signal_code.load()); |
| }); |
| EXPECT_TRUE(helper.WaitForChildren()); |
| } |
| |
| TEST(WaitpidExitSignalTest, childProcessSendsNoExitSignalOnTerminationToParentProcess) { |
| test_helper::ForkHelper helper; |
| |
| helper.RunInForkedProcess([] { |
| SignalHelper signal_helper; |
| test_helper::CloneHelper test_clone_helper; |
| int pid = test_clone_helper.runInClonedChild(0, test_helper::CloneHelper::doNothing); |
| ensureWait(pid, __WALL); |
| EXPECT_TRUE(g_received_signals.empty()); |
| }); |
| EXPECT_TRUE(helper.WaitForChildren()); |
| } |
| |
| /* |
| * Main process (P0) creates a child process (P1) and P1 creates a child thread (T1). |
| * After both P1 and T1 terminate, no matter the order of these termination, P0 should receive P1 |
| * exit signal. |
| */ |
| int processThatFinishAfterChildThread(void *) { |
| nested_clone_helper.runInClonedChild(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | SIGUSR2, |
| test_helper::CloneHelper::doNothing); |
| test_helper::CloneHelper::sleep_1sec(nullptr); |
| return 0; |
| } |
| |
| int processThatFinishBeforeChildThread(void *) { |
| nested_clone_helper.runInClonedChild(CLONE_THREAD | CLONE_VM | CLONE_SIGHAND | SIGUSR2, |
| test_helper::CloneHelper::sleep_1sec); |
| return 0; |
| } |
| |
| TEST(WaitpidExitSignalTest, childThreadGroupSendsCorrectExitSignalWhenLeaderTerminatesLast) { |
| test_helper::ForkHelper helper; |
| |
| helper.RunInForkedProcess([] { |
| SignalHelper signal_helper; |
| test_helper::CloneHelper test_clone_helper; |
| int pid = test_clone_helper.runInClonedChild(SIGUSR1, processThatFinishAfterChildThread); |
| ensureWait(pid, __WALL); |
| EXPECT_TRUE(g_received_signals.size() == 1); |
| EXPECT_EQ(g_received_signals[0], SIGUSR1); |
| EXPECT_EQ(0, g_bad_signal_code.load()); |
| }); |
| EXPECT_TRUE(helper.WaitForChildren()); |
| } |
| |
| TEST(WaitpidExitSignalTest, childThreadGroupSendsCorrectExitSignalWhenLeaderTerminatesFirst) { |
| test_helper::ForkHelper helper; |
| |
| helper.RunInForkedProcess([] { |
| SignalHelper signal_helper; |
| test_helper::CloneHelper test_clone_helper; |
| int pid = test_clone_helper.runInClonedChild(SIGUSR1, processThatFinishBeforeChildThread); |
| ensureWait(pid, __WALL); |
| EXPECT_TRUE(g_received_signals.size() == 1); |
| EXPECT_EQ(g_received_signals[0], SIGUSR1); |
| EXPECT_EQ(0, g_bad_signal_code.load()); |
| }); |
| EXPECT_TRUE(helper.WaitForChildren()); |
| } |
| |
| namespace { |
| |
| int pipefd[2]; |
| |
| int pdeath_waiter(void *) { |
| pid_t pid = 0; |
| read(pipefd[0], &pid, sizeof(pid)); |
| |
| // Wait for the parent to exit so that this task will |
| // have been reparented before exiting. |
| waitpid(pid, nullptr, 0); |
| |
| return 0; |
| } |
| |
| int parentSpawningPDeathWaiter(void *) { |
| EXPECT_EQ(pipe(pipefd), 0); |
| nested_clone_helper.runInClonedChild(SIGUSR2, pdeath_waiter); |
| |
| pid_t pid = getpid(); |
| write(pipefd[1], &pid, sizeof(pid)); |
| |
| return 0; |
| } |
| |
| TEST(WaitpidExitSignalTest, SubreaperCloneExitSignal) { |
| test_helper::ForkHelper fork_helper; |
| fork_helper.RunInForkedProcess([] { |
| test_helper::CloneHelper helper; |
| ASSERT_EQ(prctl(PR_SET_CHILD_SUBREAPER, 1), 0) << strerror(errno); |
| |
| sigset_t signal_set; |
| sigemptyset(&signal_set); |
| sigaddset(&signal_set, SIGUSR2); |
| sigaddset(&signal_set, SIGCHLD); |
| EXPECT_EQ(sigprocmask(SIG_BLOCK, &signal_set, nullptr), 0); |
| |
| helper.runInClonedChild(SIGUSR2, parentSpawningPDeathWaiter); |
| |
| // Wait for the parent to exit, which should exit with SIGUSR2. |
| int received_signal; |
| sigemptyset(&signal_set); |
| sigaddset(&signal_set, SIGUSR2); |
| std::cout << "Waiting for SIGUSR2" << std::endl; |
| EXPECT_EQ(sigwait(&signal_set, &received_signal), 0); |
| EXPECT_EQ(received_signal, SIGUSR2); |
| |
| // Wait for the child to exit, which should exit with SIGCHLD since |
| // it has been reparented. |
| sigemptyset(&signal_set); |
| sigaddset(&signal_set, SIGCHLD); |
| std::cout << "Waiting for SIGCHLD" << std::endl; |
| EXPECT_EQ(sigwait(&signal_set, &received_signal), 0); |
| EXPECT_EQ(received_signal, SIGCHLD); |
| }); |
| EXPECT_TRUE(fork_helper.WaitForChildren()); |
| } |
| } // namespace |