libchromeos: Adding a generic signal handler.
This adds an AsynchronousSignalHandler allowing to handle signal when
using chrome run loops.
This class originates and is generalized from
login_manager::ChildExitHandler and login_manager::TerminationHandler
and CL:7589
BUG=None
TEST=asynchronous_signal_handler_unittest
Change-Id: Ie6ab1c475059d846867612d51bd4453b1f4de627
Reviewed-on: https://chromium-review.googlesource.com/207330
Reviewed-by: Benjamin Lerman <qsr@chromium.org>
Commit-Queue: Benjamin Lerman <qsr@chromium.org>
Tested-by: Benjamin Lerman <qsr@chromium.org>
diff --git a/chromeos/asynchronous_signal_handler.cc b/chromeos/asynchronous_signal_handler.cc
new file mode 100644
index 0000000..ca6f8fe
--- /dev/null
+++ b/chromeos/asynchronous_signal_handler.cc
@@ -0,0 +1,107 @@
+// Copyright 2014 The Chromium OS 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 "chromeos/asynchronous_signal_handler.h"
+
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/file_util.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.h>
+
+#include "chromeos/utility.h"
+
+namespace {
+const int kInvalidDescriptor = -1;
+} // namespace
+
+namespace chromeos {
+
+AsynchronousSignalHandler::AsynchronousSignalHandler()
+ : fd_watcher_(new base::MessageLoopForIO::FileDescriptorWatcher),
+ descriptor_(kInvalidDescriptor) {
+ CHECK_EQ(sigemptyset(&signal_mask_), 0) << "Failed to initialize signal mask";
+ CHECK_EQ(sigemptyset(&saved_signal_mask_), 0)
+ << "Failed to initialize signal mask";
+}
+
+AsynchronousSignalHandler::~AsynchronousSignalHandler() {
+ if (descriptor_ != kInvalidDescriptor) {
+ fd_watcher_->StopWatchingFileDescriptor();
+
+ if (close(descriptor_) != 0)
+ PLOG(WARNING) << "Failed to close file descriptor";
+
+ descriptor_ = kInvalidDescriptor;
+ CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, NULL));
+ }
+}
+
+void AsynchronousSignalHandler::Init() {
+ CHECK_EQ(kInvalidDescriptor, descriptor_);
+ CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, &saved_signal_mask_));
+ descriptor_ =
+ signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK);
+ CHECK_NE(kInvalidDescriptor, descriptor_);
+ CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor(
+ descriptor_, true, base::MessageLoopForIO::WATCH_READ, fd_watcher_.get(),
+ this))
+ << "Watching shutdown pipe failed.";
+}
+
+void AsynchronousSignalHandler::RegisterHandler(int signal,
+ const SignalHandler& callback) {
+ registered_callbacks_[signal] = callback;
+ CHECK_EQ(0, sigaddset(&signal_mask_, signal));
+ UpdateSignals();
+}
+
+void AsynchronousSignalHandler::UnregisterHandler(int signal) {
+ Callbacks::iterator callback_it = registered_callbacks_.find(signal);
+ if (callback_it != registered_callbacks_.end()) {
+ registered_callbacks_.erase(callback_it);
+ ResetSignal(signal);
+ }
+}
+
+void AsynchronousSignalHandler::OnFileCanReadWithoutBlocking(int fd) {
+ struct signalfd_siginfo info;
+ while (base::ReadFromFD(fd, reinterpret_cast<char*>(&info), sizeof(info))) {
+ int signal = info.ssi_signo;
+ Callbacks::iterator callback_it = registered_callbacks_.find(signal);
+ if (callback_it == registered_callbacks_.end()) {
+ LOG(WARNING) << "Unable to find a signal handler for signal: " << signal;
+ // Can happen if a signal has been called multiple time, and the callback
+ // asked to be unregistered the first time.
+ continue;
+ }
+ const SignalHandler& callback = callback_it->second;
+ bool must_unregister = callback.Run(info);
+ if (must_unregister) {
+ UnregisterHandler(signal);
+ }
+ }
+}
+
+void AsynchronousSignalHandler::OnFileCanWriteWithoutBlocking(int fd) {
+ NOTREACHED();
+}
+
+void AsynchronousSignalHandler::ResetSignal(int signal) {
+ CHECK_EQ(0, sigdelset(&signal_mask_, signal));
+ UpdateSignals();
+}
+
+void AsynchronousSignalHandler::UpdateSignals() {
+ if (descriptor_ != kInvalidDescriptor) {
+ CHECK_EQ(0, sigprocmask(SIG_SETMASK, &saved_signal_mask_, NULL));
+ CHECK_EQ(0, sigprocmask(SIG_BLOCK, &signal_mask_, NULL));
+ CHECK_EQ(descriptor_,
+ signalfd(descriptor_, &signal_mask_, SFD_CLOEXEC | SFD_NONBLOCK));
+ }
+}
+
+} // namespace chromeos
diff --git a/chromeos/asynchronous_signal_handler.h b/chromeos/asynchronous_signal_handler.h
new file mode 100644
index 0000000..b0858ab
--- /dev/null
+++ b/chromeos/asynchronous_signal_handler.h
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_H_
+#define LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_H_
+
+#include <signal.h>
+#include <sys/signalfd.h>
+
+#include <map>
+
+#include <base/basictypes.h>
+#include <base/callback.h>
+#include <base/compiler_specific.h>
+#include <base/memory/scoped_ptr.h>
+#include <base/message_loop/message_loop.h>
+
+namespace chromeos {
+// Sets up signal handlers for registered signals, and converts signal receipt
+// into a write on a pipe. Watches that pipe for data and, when some appears,
+// execute the associated callback.
+class AsynchronousSignalHandler : public base::MessageLoopForIO::Watcher {
+ public:
+ AsynchronousSignalHandler();
+ virtual ~AsynchronousSignalHandler();
+
+ // The callback called when a signal is received.
+ typedef base::Callback<bool(const struct signalfd_siginfo&)> SignalHandler;
+
+ // Initialize the handler.
+ void Init();
+
+ // Register a new handler for the given |signal|, replacing any previously
+ // registered handler. |callback| will be called on the thread the
+ // |AsynchronousSignalHandler| is bound to when a signal is received. The
+ // received |signalfd_siginfo| will be passed to |callback|. |callback| must
+ // returns |true| if the signal handler must be unregistered, and |false|
+ // otherwise. Due to an implementation detail, you cannot set any sigaction
+ // flags you might be accustomed to using. This might matter if you hoped to
+ // use SA_NOCLDSTOP to avoid getting a SIGCHLD when a child process receives a
+ // SIGSTOP.
+ void RegisterHandler(int signal,
+ const SignalHandler& callback);
+
+ // Unregister a previously registered handler for the given |signal|.
+ void UnregisterHandler(int signal);
+
+ // Implementation of base::MessageLoopForIO::Watcher
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ private:
+ // Controller used to manage watching of signalling pipe.
+ scoped_ptr<base::MessageLoopForIO::FileDescriptorWatcher> fd_watcher_;
+
+ // The registered callbacks.
+ typedef std::map<int, SignalHandler> Callbacks;
+ Callbacks registered_callbacks_;
+
+ // File descriptor for accepting signals indicated by |signal_mask_|.
+ int descriptor_;
+
+ // A set of signals to be handled after the dispatcher is running.
+ sigset_t signal_mask_;
+
+ // A copy of the signal mask before the dispatcher starts, which will be
+ // used to restore to the original state when the dispatcher stops.
+ sigset_t saved_signal_mask_;
+
+ // Resets the given signal to its default behavior. Doesn't touch
+ // |registered_callbacks_|.
+ void ResetSignal(int signal);
+
+ // Updates the set of signals that this handler listens to.
+ void UpdateSignals();
+
+ DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandler);
+};
+
+} // namespace chromeos
+
+#endif // LIBCHROMEOS_CHROMEOS_ASYNCHRONOUS_SIGNAL_HANDLER_H_
diff --git a/chromeos/asynchronous_signal_handler_unittest.cc b/chromeos/asynchronous_signal_handler_unittest.cc
new file mode 100644
index 0000000..2928061
--- /dev/null
+++ b/chromeos/asynchronous_signal_handler_unittest.cc
@@ -0,0 +1,132 @@
+// Copyright 2014 The Chromium OS 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 "chromeos/asynchronous_signal_handler.h"
+
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include <base/at_exit.h>
+#include <base/basictypes.h>
+#include <base/bind.h>
+#include <base/message_loop/message_loop.h>
+#include <base/run_loop.h>
+#include <gtest/gtest.h>
+
+namespace chromeos {
+
+class AsynchronousSignalHandlerTest : public ::testing::Test {
+ public:
+ AsynchronousSignalHandlerTest() {}
+ virtual ~AsynchronousSignalHandlerTest() {}
+
+ virtual void SetUp() {
+ handler_.Init();
+ }
+
+ virtual void TearDown() {}
+
+ bool RecordInfoAndQuit(bool response, const struct signalfd_siginfo& info) {
+ infos_.push_back(info);
+ loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
+ return response;
+ }
+
+ void Run() {
+ base::RunLoop run_loop;
+ run_loop.Run();
+ }
+
+ protected:
+ base::AtExitManager at_exit_;
+ base::MessageLoopForIO loop_;
+ std::vector<struct signalfd_siginfo> infos_;
+ AsynchronousSignalHandler handler_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsynchronousSignalHandlerTest);
+};
+
+TEST_F(AsynchronousSignalHandlerTest, CheckTerm) {
+ handler_.RegisterHandler(
+ SIGTERM, base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this), true));
+ EXPECT_EQ(0, infos_.size());
+ EXPECT_EQ(0, kill(getpid(), SIGTERM));
+
+ // Spin the message loop.
+ Run();
+
+ ASSERT_EQ(1, infos_.size());
+ EXPECT_EQ(SIGTERM, infos_[0].ssi_signo);
+}
+
+TEST_F(AsynchronousSignalHandlerTest, CheckSignalUnregistration) {
+ handler_.RegisterHandler(
+ SIGCHLD, base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this), true));
+ EXPECT_EQ(0, infos_.size());
+ EXPECT_EQ(0, kill(getpid(), SIGCHLD));
+
+ // Spin the message loop.
+ Run();
+
+ ASSERT_EQ(1, infos_.size());
+ EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo);
+
+ EXPECT_EQ(0, kill(getpid(), SIGCHLD));
+
+ // Run the loop with a timeout, as no message are expected.
+ loop_.PostDelayedTask(FROM_HERE, base::MessageLoop::QuitClosure(),
+ base::TimeDelta::FromMilliseconds(10));
+ Run();
+
+ // The signal handle should have been unregistered. No new message are
+ // expected.
+ EXPECT_EQ(1, infos_.size());
+}
+
+TEST_F(AsynchronousSignalHandlerTest, CheckMultipleSignal) {
+ const uint8_t NB_SIGNALS = 5;
+ handler_.RegisterHandler(
+ SIGCHLD, base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this), false));
+ EXPECT_EQ(0, infos_.size());
+ for (int i = 0; i < NB_SIGNALS; ++i) {
+ EXPECT_EQ(0, kill(getpid(), SIGCHLD));
+
+ // Spin the message loop.
+ Run();
+ }
+
+ ASSERT_EQ(NB_SIGNALS, infos_.size());
+ for (int i = 0; i < NB_SIGNALS; ++i) {
+ EXPECT_EQ(SIGCHLD, infos_[i].ssi_signo);
+ }
+}
+
+TEST_F(AsynchronousSignalHandlerTest, CheckChld) {
+ handler_.RegisterHandler(
+ SIGCHLD, base::Bind(&AsynchronousSignalHandlerTest::RecordInfoAndQuit,
+ base::Unretained(this), false));
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ _Exit(EXIT_SUCCESS);
+ }
+
+ EXPECT_EQ(0, infos_.size());
+ // Spin the message loop.
+ Run();
+
+ ASSERT_EQ(1, infos_.size());
+ EXPECT_EQ(SIGCHLD, infos_[0].ssi_signo);
+ EXPECT_EQ(child_pid, infos_[0].ssi_pid);
+ EXPECT_EQ(static_cast<int>(CLD_EXITED), infos_[0].ssi_code);
+ EXPECT_EQ(EXIT_SUCCESS, infos_[0].ssi_status);
+}
+
+} // namespace chromeos