| // Copyright 2023 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "interrupt_handling.h" |
| |
| #include "test.h" |
| #include "util.h" |
| |
| #ifdef _WIN32 |
| |
| // TODO(digit): Write real test for _WIN32 |
| char dummy_; |
| |
| #else // !_WIN32 |
| |
| #include <sys/signal.h> |
| #include <unistd.h> |
| |
| namespace { |
| |
| // Retrieve a signal mask for SIGINT/SIGHUP/SIGTERM |
| sigset_t GetInterruptSignalMask() { |
| sigset_t mask; |
| sigemptyset(&mask); |
| sigaddset(&mask, SIGINT); |
| sigaddset(&mask, SIGHUP); |
| sigaddset(&mask, SIGTERM); |
| return mask; |
| } |
| |
| // Set the signal action for a given |signum|, returning the previous one |
| // in |*old_action| is |old_action != nullptr|. |
| void SetSignalAction(int signum, const struct sigaction* action, |
| struct sigaction* old_action) { |
| if (sigaction(signum, action, old_action) < 0) |
| ErrnoFatal("sigaction"); |
| } |
| |
| // Base class for all tests, used to set the signal mask and actions for |
| // interrupts to the same base state. |
| class InterruptHandlingTest : public ::testing::Test { |
| public: |
| InterruptHandlingTest() { |
| // Block signals before changing the action handler + save previous mask. |
| sigset_t mask = GetInterruptSignalMask(); |
| sigprocmask(SIG_BLOCK, &mask, &prev_mask_); |
| |
| // Change signal handlers. |
| struct sigaction sigint_action = {}; |
| sigint_action.sa_handler = [](int) { s_got_sigint = 1; }; |
| SetSignalAction(SIGINT, &sigint_action, &prev_sigint_action_); |
| |
| struct sigaction sighup_action = {}; |
| sighup_action.sa_handler = [](int) { s_got_sighup = 1; }; |
| SetSignalAction(SIGHUP, &sighup_action, &prev_sighup_action_); |
| |
| struct sigaction sigterm_action = {}; |
| sigterm_action.sa_handler = [](int) { s_got_sigterm = 1; }; |
| SetSignalAction(SIGTERM, &sigterm_action, &prev_sigterm_action_); |
| |
| // Unblock signals. |
| sigprocmask(SIG_UNBLOCK, &mask, nullptr); |
| } |
| |
| ~InterruptHandlingTest() { |
| // Block signals before changing handlers. |
| sigset_t mask = GetInterruptSignalMask(); |
| sigprocmask(SIG_BLOCK, &mask, nullptr); |
| |
| // Restore previous handlers. |
| SetSignalAction(SIGINT, &prev_sigint_action_, nullptr); |
| SetSignalAction(SIGHUP, &prev_sighup_action_, nullptr); |
| SetSignalAction(SIGTERM, &prev_sigterm_action_, nullptr); |
| |
| // Clear flags for next test. |
| Clear(); |
| |
| // Restore previous signal mask. |
| sigprocmask(SIG_SETMASK, &prev_mask_, nullptr); |
| } |
| |
| void Clear() { |
| s_got_sigint = 0; |
| s_got_sighup = 0; |
| s_got_sigterm = 0; |
| } |
| |
| void SendSelfSignal(int signum) { ::kill(getpid(), signum); } |
| |
| sigset_t prev_mask_; |
| struct sigaction prev_sigint_action_; |
| struct sigaction prev_sighup_action_; |
| struct sigaction prev_sigterm_action_; |
| |
| static volatile sig_atomic_t s_got_sigint; |
| static volatile sig_atomic_t s_got_sighup; |
| static volatile sig_atomic_t s_got_sigterm; |
| }; |
| |
| volatile sig_atomic_t InterruptHandlingTest::s_got_sigint = 0; |
| volatile sig_atomic_t InterruptHandlingTest::s_got_sighup = 0; |
| volatile sig_atomic_t InterruptHandlingTest::s_got_sigterm = 0; |
| |
| } // namespace |
| |
| TEST_F(InterruptHandlingTest, SendSelfSignals) { |
| // Verify that the interrupt signals are not blocked |
| sigset_t empty_mask; |
| sigemptyset(&empty_mask); |
| sigset_t cur_mask; |
| sigprocmask(SIG_BLOCK, &empty_mask, &cur_mask); |
| ASSERT_FALSE(sigismember(&cur_mask, SIGINT)); |
| ASSERT_FALSE(sigismember(&cur_mask, SIGHUP)); |
| ASSERT_FALSE(sigismember(&cur_mask, SIGTERM)); |
| |
| SendSelfSignal(SIGINT); |
| ASSERT_TRUE(s_got_sigint); |
| |
| SendSelfSignal(SIGHUP); |
| ASSERT_TRUE(s_got_sighup); |
| |
| SendSelfSignal(SIGTERM); |
| ASSERT_TRUE(s_got_sigterm); |
| |
| Clear(); |
| ASSERT_FALSE(s_got_sigint); |
| ASSERT_FALSE(s_got_sighup); |
| ASSERT_FALSE(s_got_sigterm); |
| } |
| |
| TEST_F(InterruptHandlingTest, InterruptBlocker) { |
| { |
| InterruptBlocker blocker; |
| |
| SendSelfSignal(SIGINT); |
| ASSERT_FALSE(s_got_sigint); |
| |
| SendSelfSignal(SIGHUP); |
| ASSERT_FALSE(s_got_sighup); |
| |
| SendSelfSignal(SIGTERM); |
| ASSERT_FALSE(s_got_sigterm); |
| } |
| ASSERT_TRUE(s_got_sigint); |
| ASSERT_TRUE(s_got_sighup); |
| ASSERT_TRUE(s_got_sigterm); |
| } |
| |
| TEST_F(InterruptHandlingTest, InterruptCatcher) { |
| { |
| InterruptCatcher catcher; |
| |
| SendSelfSignal(SIGINT); |
| ASSERT_FALSE(s_got_sigint); |
| ASSERT_EQ(SIGINT, catcher.interrupted()); |
| |
| SendSelfSignal(SIGHUP); |
| ASSERT_FALSE(s_got_sighup); |
| ASSERT_EQ(SIGHUP, catcher.interrupted()); |
| |
| SendSelfSignal(SIGTERM); |
| ASSERT_FALSE(s_got_sigterm); |
| ASSERT_EQ(SIGTERM, catcher.interrupted()); |
| } |
| |
| // Verify that a second instance does not keep the old interrupted() |
| // value from a stale global variable. |
| { |
| InterruptCatcher catcher; |
| ASSERT_EQ(0, catcher.interrupted()); |
| } |
| } |
| |
| TEST_F(InterruptHandlingTest, InterruptForwarder) { |
| ASSERT_FALSE(s_got_sigint); |
| |
| // Create child process with fork. |
| pid_t child_pid = fork(); |
| ASSERT_GE(child_pid, 0); |
| |
| if (child_pid == 0) { |
| // In the child process, do nothing, just wait for an interrupt. |
| sleep(10); |
| exit(0); |
| } |
| |
| InterruptForwarder forwarder(child_pid); |
| |
| SendSelfSignal(SIGINT); |
| ASSERT_FALSE(s_got_sigint); |
| } |
| |
| #endif // !_WIN32 |