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