summaryrefslogtreecommitdiff
path: root/utils/signals
diff options
context:
space:
mode:
authorBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
committerBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
commit08334c51dbb99d9ecd2bb86a2d94ed06da9e167a (patch)
treec43eb24d59bd5c963583a5190caef80fc8387322 /utils/signals
Notes
Diffstat (limited to 'utils/signals')
-rw-r--r--utils/signals/Kyuafile9
-rw-r--r--utils/signals/Makefile.am.inc73
-rw-r--r--utils/signals/exceptions.cpp102
-rw-r--r--utils/signals/exceptions.hpp83
-rw-r--r--utils/signals/exceptions_test.cpp73
-rw-r--r--utils/signals/interrupts.cpp309
-rw-r--r--utils/signals/interrupts.hpp83
-rw-r--r--utils/signals/interrupts_fwd.hpp46
-rw-r--r--utils/signals/interrupts_test.cpp266
-rw-r--r--utils/signals/misc.cpp106
-rw-r--r--utils/signals/misc.hpp49
-rw-r--r--utils/signals/misc_test.cpp133
-rw-r--r--utils/signals/programmer.cpp138
-rw-r--r--utils/signals/programmer.hpp63
-rw-r--r--utils/signals/programmer_fwd.hpp49
-rw-r--r--utils/signals/programmer_test.cpp140
-rw-r--r--utils/signals/timer.cpp547
-rw-r--r--utils/signals/timer.hpp86
-rw-r--r--utils/signals/timer_fwd.hpp45
-rw-r--r--utils/signals/timer_test.cpp426
20 files changed, 2826 insertions, 0 deletions
diff --git a/utils/signals/Kyuafile b/utils/signals/Kyuafile
new file mode 100644
index 000000000000..09da3e166cd2
--- /dev/null
+++ b/utils/signals/Kyuafile
@@ -0,0 +1,9 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="interrupts_test"}
+atf_test_program{name="misc_test"}
+atf_test_program{name="programmer_test"}
+atf_test_program{name="timer_test"}
diff --git a/utils/signals/Makefile.am.inc b/utils/signals/Makefile.am.inc
new file mode 100644
index 000000000000..b01089c80fea
--- /dev/null
+++ b/utils/signals/Makefile.am.inc
@@ -0,0 +1,73 @@
+# Copyright 2010 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/signals/exceptions.cpp
+libutils_a_SOURCES += utils/signals/exceptions.hpp
+libutils_a_SOURCES += utils/signals/interrupts.cpp
+libutils_a_SOURCES += utils/signals/interrupts.hpp
+libutils_a_SOURCES += utils/signals/interrupts_fwd.hpp
+libutils_a_SOURCES += utils/signals/misc.cpp
+libutils_a_SOURCES += utils/signals/misc.hpp
+libutils_a_SOURCES += utils/signals/programmer.cpp
+libutils_a_SOURCES += utils/signals/programmer.hpp
+libutils_a_SOURCES += utils/signals/programmer_fwd.hpp
+libutils_a_SOURCES += utils/signals/timer.cpp
+libutils_a_SOURCES += utils/signals/timer.hpp
+libutils_a_SOURCES += utils/signals/timer_fwd.hpp
+
+if WITH_ATF
+tests_utils_signalsdir = $(pkgtestsdir)/utils/signals
+
+tests_utils_signals_DATA = utils/signals/Kyuafile
+EXTRA_DIST += $(tests_utils_signals_DATA)
+
+tests_utils_signals_PROGRAMS = utils/signals/exceptions_test
+utils_signals_exceptions_test_SOURCES = utils/signals/exceptions_test.cpp
+utils_signals_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/interrupts_test
+utils_signals_interrupts_test_SOURCES = utils/signals/interrupts_test.cpp
+utils_signals_interrupts_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_interrupts_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/misc_test
+utils_signals_misc_test_SOURCES = utils/signals/misc_test.cpp
+utils_signals_misc_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_misc_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/programmer_test
+utils_signals_programmer_test_SOURCES = utils/signals/programmer_test.cpp
+utils_signals_programmer_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_programmer_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_signals_PROGRAMS += utils/signals/timer_test
+utils_signals_timer_test_SOURCES = utils/signals/timer_test.cpp
+utils_signals_timer_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_signals_timer_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/signals/exceptions.cpp b/utils/signals/exceptions.cpp
new file mode 100644
index 000000000000..70e0dbe8a5d1
--- /dev/null
+++ b/utils/signals/exceptions.cpp
@@ -0,0 +1,102 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/exceptions.hpp"
+
+#include <cstring>
+
+#include "utils/format/macros.hpp"
+
+namespace signals = utils::signals;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+signals::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+signals::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new interrupted error.
+///
+/// \param signo_ The signal that caused the interrupt.
+signals::interrupted_error::interrupted_error(const int signo_) :
+ error(F("Interrupted by signal %s") % signo_),
+ _signo(signo_)
+{
+}
+
+
+/// Destructor for the error.
+signals::interrupted_error::~interrupted_error(void) throw()
+{
+}
+
+
+/// Queries the signal number of the interruption.
+///
+/// \return A signal number.
+int
+signals::interrupted_error::signo(void) const
+{
+ return _signo;
+}
+
+
+/// Constructs a new error based on an errno code.
+///
+/// \param message_ The message describing what caused the error.
+/// \param errno_ The error code.
+signals::system_error::system_error(const std::string& message_,
+ const int errno_) :
+ error(F("%s: %s") % message_ % strerror(errno_)),
+ _original_errno(errno_)
+{
+}
+
+
+/// Destructor for the error.
+signals::system_error::~system_error(void) throw()
+{
+}
+
+
+/// \return The original errno value.
+int
+signals::system_error::original_errno(void) const throw()
+{
+ return _original_errno;
+}
diff --git a/utils/signals/exceptions.hpp b/utils/signals/exceptions.hpp
new file mode 100644
index 000000000000..35cd2c9e8168
--- /dev/null
+++ b/utils/signals/exceptions.hpp
@@ -0,0 +1,83 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/exceptions.hpp
+/// Exception types raised by the signals module.
+
+#if !defined(UTILS_SIGNALS_EXCEPTIONS_HPP)
+#define UTILS_SIGNALS_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+namespace utils {
+namespace signals {
+
+
+/// Base exceptions for signals errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Denotes the reception of a signal to controlledly terminate execution.
+class interrupted_error : public error {
+ /// Signal that caused the interrupt.
+ int _signo;
+
+public:
+ explicit interrupted_error(const int signo_);
+ ~interrupted_error(void) throw();
+
+ int signo(void) const;
+};
+
+
+/// Exceptions for errno-based errors.
+///
+/// TODO(jmmv): This code is duplicated in, at least, utils::fs. Figure
+/// out a way to reuse this exception while maintaining the correct inheritance
+/// (i.e. be able to keep it as a child of signals::error).
+class system_error : public error {
+ /// Error number describing this libc error condition.
+ int _original_errno;
+
+public:
+ explicit system_error(const std::string&, const int);
+ ~system_error(void) throw();
+
+ int original_errno(void) const throw();
+};
+
+
+} // namespace signals
+} // namespace utils
+
+
+#endif // !defined(UTILS_SIGNALS_EXCEPTIONS_HPP)
diff --git a/utils/signals/exceptions_test.cpp b/utils/signals/exceptions_test.cpp
new file mode 100644
index 000000000000..40db536f1a8c
--- /dev/null
+++ b/utils/signals/exceptions_test.cpp
@@ -0,0 +1,73 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/exceptions.hpp"
+
+#include <cerrno>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+
+namespace signals = utils::signals;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const signals::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupted_error);
+ATF_TEST_CASE_BODY(interrupted_error)
+{
+ const signals::interrupted_error e(5);
+ ATF_REQUIRE(std::strcmp("Interrupted by signal 5", e.what()) == 0);
+ ATF_REQUIRE_EQ(5, e.signo());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(system_error);
+ATF_TEST_CASE_BODY(system_error)
+{
+ const signals::system_error e("Call failed", ENOENT);
+ const std::string expected = F("Call failed: %s") % std::strerror(ENOENT);
+ ATF_REQUIRE_EQ(expected, e.what());
+ ATF_REQUIRE_EQ(ENOENT, e.original_errno());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, interrupted_error);
+ ATF_ADD_TEST_CASE(tcs, system_error);
+}
diff --git a/utils/signals/interrupts.cpp b/utils/signals/interrupts.cpp
new file mode 100644
index 000000000000..956a83c66802
--- /dev/null
+++ b/utils/signals/interrupts.cpp
@@ -0,0 +1,309 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/interrupts.hpp"
+
+extern "C" {
+#include <sys/types.h>
+
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <cstring>
+#include <set>
+
+#include "utils/logging/macros.hpp"
+#include "utils/process/operations.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace signals = utils::signals;
+namespace process = utils::process;
+
+
+namespace {
+
+
+/// The interrupt signal that fired, or -1 if none.
+static volatile int fired_signal = -1;
+
+
+/// Collection of PIDs.
+typedef std::set< pid_t > pids_set;
+
+
+/// List of processes to kill upon reception of a signal.
+static pids_set pids_to_kill;
+
+
+/// Programmer status for the SIGHUP signal.
+static std::auto_ptr< signals::programmer > sighup_handler;
+/// Programmer status for the SIGINT signal.
+static std::auto_ptr< signals::programmer > sigint_handler;
+/// Programmer status for the SIGTERM signal.
+static std::auto_ptr< signals::programmer > sigterm_handler;
+
+
+/// Signal mask to restore after exiting a signal inhibited section.
+static sigset_t global_old_sigmask;
+
+
+/// Whether there is an interrupts_handler object in existence or not.
+static bool interrupts_handler_active = false;
+
+
+/// Whether there is an interrupts_inhibiter object in existence or not.
+static std::size_t interrupts_inhibiter_active = 0;
+
+
+/// Generic handler to capture interrupt signals.
+///
+/// From this handler, we record that an interrupt has happened so that
+/// check_interrupt() can know whether there execution has to be stopped or not.
+/// We also terminate any of our child processes (started by the
+/// utils::process::children class) so that any ongoing wait(2) system calls
+/// terminate.
+///
+/// \param signo The signal that caused this handler to be called.
+static void
+signal_handler(const int signo)
+{
+ static const char* message = "[-- Signal caught; please wait for "
+ "cleanup --]\n";
+ if (::write(STDERR_FILENO, message, std::strlen(message)) == -1) {
+ // We are exiting: the message printed here is only for informational
+ // purposes. If we fail to print it (which probably means something
+ // is really bad), there is not much we can do within the signal
+ // handler, so just ignore this.
+ }
+
+ fired_signal = signo;
+
+ for (pids_set::const_iterator iter = pids_to_kill.begin();
+ iter != pids_to_kill.end(); ++iter) {
+ process::terminate_group(*iter);
+ }
+}
+
+
+/// Installs signal handlers for potential interrupts.
+///
+/// \pre Must not have been called before.
+/// \post The various sig*_handler global variables are atomically updated.
+static void
+setup_handlers(void)
+{
+ PRE(sighup_handler.get() == NULL);
+ PRE(sigint_handler.get() == NULL);
+ PRE(sigterm_handler.get() == NULL);
+
+ // Create the handlers on the stack first so that, if any of them fails, the
+ // stack unwinding cleans things up.
+ std::auto_ptr< signals::programmer > tmp_sighup_handler(
+ new signals::programmer(SIGHUP, signal_handler));
+ std::auto_ptr< signals::programmer > tmp_sigint_handler(
+ new signals::programmer(SIGINT, signal_handler));
+ std::auto_ptr< signals::programmer > tmp_sigterm_handler(
+ new signals::programmer(SIGTERM, signal_handler));
+
+ // Now, update the global pointers, which is an operation that cannot fail.
+ sighup_handler = tmp_sighup_handler;
+ sigint_handler = tmp_sigint_handler;
+ sigterm_handler = tmp_sigterm_handler;
+}
+
+
+/// Uninstalls the signal handlers installed by setup_handlers().
+static void
+cleanup_handlers(void)
+{
+ sighup_handler->unprogram(); sighup_handler.reset(NULL);
+ sigint_handler->unprogram(); sigint_handler.reset(NULL);
+ sigterm_handler->unprogram(); sigterm_handler.reset(NULL);
+}
+
+
+
+/// Masks the signals installed by setup_handlers().
+///
+/// \param[out] old_sigmask The old signal mask to save via the
+/// \code oset \endcode argument with sigprocmask(2).
+static void
+mask_signals(sigset_t* old_sigmask)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGALRM);
+ sigaddset(&mask, SIGHUP);
+ sigaddset(&mask, SIGINT);
+ sigaddset(&mask, SIGTERM);
+ const int ret = ::sigprocmask(SIG_BLOCK, &mask, old_sigmask);
+ INV(ret != -1);
+}
+
+
+/// Resets the signal masking put in place by mask_signals().
+///
+/// \param[in] old_sigmask The old signal mask to restore via the
+/// \code set \endcode argument with sigprocmask(2).
+static void
+unmask_signals(sigset_t* old_sigmask)
+{
+ const int ret = ::sigprocmask(SIG_SETMASK, old_sigmask, NULL);
+ INV(ret != -1);
+}
+
+
+} // anonymous namespace
+
+
+/// Constructor that sets up the signal handlers.
+signals::interrupts_handler::interrupts_handler(void) :
+ _programmed(false)
+{
+ PRE(!interrupts_handler_active);
+ setup_handlers();
+ _programmed = true;
+ interrupts_handler_active = true;
+}
+
+
+/// Destructor that removes the signal handlers.
+///
+/// Given that this is a destructor and it can't report errors back to the
+/// caller, the caller must attempt to call unprogram() on its own.
+signals::interrupts_handler::~interrupts_handler(void)
+{
+ if (_programmed) {
+ LW("Destroying still-programmed signals::interrupts_handler object");
+ try {
+ unprogram();
+ } catch (const error& e) {
+ UNREACHABLE;
+ }
+ }
+}
+
+
+/// Unprograms all signals captured by the interrupts handler.
+///
+/// \throw system_error If the unprogramming of any signal fails.
+void
+signals::interrupts_handler::unprogram(void)
+{
+ PRE(_programmed);
+
+ // Modify the control variables first before unprogramming the handlers. If
+ // we fail to do the latter, we do not want to try again because we will not
+ // succeed (and we'll cause a crash due to failed preconditions).
+ _programmed = false;
+ interrupts_handler_active = false;
+
+ cleanup_handlers();
+ fired_signal = -1;
+}
+
+
+/// Constructor that sets up signal masking.
+signals::interrupts_inhibiter::interrupts_inhibiter(void)
+{
+ sigset_t old_sigmask;
+ mask_signals(&old_sigmask);
+ if (interrupts_inhibiter_active == 0) {
+ global_old_sigmask = old_sigmask;
+ }
+ ++interrupts_inhibiter_active;
+}
+
+
+/// Destructor that removes signal masking.
+signals::interrupts_inhibiter::~interrupts_inhibiter(void)
+{
+ if (interrupts_inhibiter_active > 1) {
+ --interrupts_inhibiter_active;
+ } else {
+ interrupts_inhibiter_active = false;
+ unmask_signals(&global_old_sigmask);
+ }
+}
+
+
+/// Checks if an interrupt has fired.
+///
+/// Calls to this function should be sprinkled in strategic places through the
+/// code protected by an interrupts_handler object.
+///
+/// Only one call to this function will raise an exception per signal received.
+/// This is to allow executing cleanup actions without reraising interrupt
+/// exceptions unless the user has fired another interrupt.
+///
+/// \throw interrupted_error If there has been an interrupt.
+void
+signals::check_interrupt(void)
+{
+ if (fired_signal != -1) {
+ const int original_fired_signal = fired_signal;
+ fired_signal = -1;
+ throw interrupted_error(original_fired_signal);
+ }
+}
+
+
+/// Registers a child process to be killed upon reception of an interrupt.
+///
+/// \pre Must be called with interrupts being inhibited. The caller must ensure
+/// that the call call to fork() and the addition of the PID happen atomically.
+///
+/// \param pid The PID of the child process. Must not have been yet regsitered.
+void
+signals::add_pid_to_kill(const pid_t pid)
+{
+ PRE(interrupts_inhibiter_active);
+ PRE(pids_to_kill.find(pid) == pids_to_kill.end());
+ pids_to_kill.insert(pid);
+}
+
+
+/// Unregisters a child process previously registered via add_pid_to_kill().
+///
+/// \pre Must be called with interrupts being inhibited. This is not necessary,
+/// but pushing this to the caller simplifies our logic and provides consistency
+/// with the add_pid_to_kill() call.
+///
+/// \param pid The PID of the child process. Must have been registered
+/// previously, and the process must have already been awaited for.
+void
+signals::remove_pid_to_kill(const pid_t pid)
+{
+ PRE(interrupts_inhibiter_active);
+ PRE(pids_to_kill.find(pid) != pids_to_kill.end());
+ pids_to_kill.erase(pid);
+}
diff --git a/utils/signals/interrupts.hpp b/utils/signals/interrupts.hpp
new file mode 100644
index 000000000000..b181114bb245
--- /dev/null
+++ b/utils/signals/interrupts.hpp
@@ -0,0 +1,83 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/interrupts.hpp
+/// Handling of interrupts.
+
+#if !defined(UTILS_SIGNALS_INTERRUPTS_HPP)
+#define UTILS_SIGNALS_INTERRUPTS_HPP
+
+#include "utils/signals/interrupts_fwd.hpp"
+
+#include <unistd.h>
+
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace signals {
+
+
+/// Provides a scope in which interrupts can be detected and handled.
+///
+/// This RAII-modeled object installs signal handler when instantiated and
+/// removes them upon destruction. While this object is active, the
+/// check_interrupt() free function can be used to determine if an interrupt has
+/// happened.
+class interrupts_handler : noncopyable {
+ /// Whether the interrupts are still programmed or not.
+ ///
+ /// Used by the destructor to prevent double-unprogramming when unprogram()
+ /// is explicitly called by the user.
+ bool _programmed;
+
+public:
+ interrupts_handler(void);
+ ~interrupts_handler(void);
+
+ void unprogram(void);
+};
+
+
+/// Disables interrupts while the object is alive.
+class interrupts_inhibiter : noncopyable {
+public:
+ interrupts_inhibiter(void);
+ ~interrupts_inhibiter(void);
+};
+
+
+void check_interrupt(void);
+
+void add_pid_to_kill(const pid_t);
+void remove_pid_to_kill(const pid_t);
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_INTERRUPTS_HPP)
diff --git a/utils/signals/interrupts_fwd.hpp b/utils/signals/interrupts_fwd.hpp
new file mode 100644
index 000000000000..e4dfe68d54e2
--- /dev/null
+++ b/utils/signals/interrupts_fwd.hpp
@@ -0,0 +1,46 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/interrupts_fwd.hpp
+/// Forward declarations for utils/signals/interrupts.hpp
+
+#if !defined(UTILS_SIGNALS_INTERRUPTS_FWD_HPP)
+#define UTILS_SIGNALS_INTERRUPTS_FWD_HPP
+
+namespace utils {
+namespace signals {
+
+
+class interrupts_handler;
+class interrupts_inhibiter;
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_INTERRUPTS_FWD_HPP)
diff --git a/utils/signals/interrupts_test.cpp b/utils/signals/interrupts_test.cpp
new file mode 100644
index 000000000000..ef8758d8d5f1
--- /dev/null
+++ b/utils/signals/interrupts_test.cpp
@@ -0,0 +1,266 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/interrupts.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+/// Set to the signal that fired; -1 if none.
+static volatile int fired_signal = -1;
+
+
+/// Test handler for signals.
+///
+/// \post fired_signal is set to the signal that triggered the handler.
+///
+/// \param signo The signal that triggered the handler.
+static void
+signal_handler(const int signo)
+{
+ PRE(fired_signal == -1 || fired_signal == signo);
+ fired_signal = signo;
+}
+
+
+/// Child process that pauses waiting to be killed.
+static void
+pause_child(void)
+{
+ sigset_t mask;
+ sigemptyset(&mask);
+ // We loop waiting for signals because we want the parent process to send us
+ // a SIGKILL that we cannot handle, not just any non-deadly signal.
+ for (;;) {
+ std::cerr << F("Waiting for any signal; pid=%s\n") % ::getpid();
+ ::sigsuspend(&mask);
+ std::cerr << F("Signal received; pid=%s\n") % ::getpid();
+ }
+}
+
+
+/// Checks that interrupts_handler() handles a particular signal.
+///
+/// This indirectly checks the check_interrupt() function, which is not part of
+/// the class but is tightly related.
+///
+/// \param signo The signal to check.
+/// \param explicit_unprogram Whether to call interrupts_handler::unprogram()
+/// explicitly before letting the object go out of scope.
+static void
+check_interrupts_handler(const int signo, const bool explicit_unprogram)
+{
+ fired_signal = -1;
+
+ signals::programmer test_handler(signo, signal_handler);
+
+ {
+ signals::interrupts_handler interrupts;
+
+ // No pending interrupts at first.
+ signals::check_interrupt();
+
+ // Send us an interrupt and check for it.
+ ::kill(getpid(), signo);
+ ATF_REQUIRE_THROW_RE(signals::interrupted_error,
+ F("Interrupted by signal %s") % signo,
+ signals::check_interrupt());
+
+ // Interrupts should have been cleared now, so this should not throw.
+ signals::check_interrupt();
+
+ // Check to see if a second interrupt is detected.
+ ::kill(getpid(), signo);
+ ATF_REQUIRE_THROW_RE(signals::interrupted_error,
+ F("Interrupted by signal %s") % signo,
+ signals::check_interrupt());
+
+ // And ensure the interrupt was cleared again.
+ signals::check_interrupt();
+
+ if (explicit_unprogram) {
+ interrupts.unprogram();
+ }
+ }
+
+ ATF_REQUIRE_EQ(-1, fired_signal);
+ ::kill(getpid(), signo);
+ ATF_REQUIRE_EQ(signo, fired_signal);
+
+ test_handler.unprogram();
+}
+
+
+/// Checks that interrupts_inhibiter() handles a particular signal.
+///
+/// \param signo The signal to check.
+static void
+check_interrupts_inhibiter(const int signo)
+{
+ signals::programmer test_handler(signo, signal_handler);
+
+ {
+ signals::interrupts_inhibiter inhibiter;
+ {
+ signals::interrupts_inhibiter nested_inhibiter;
+ ::kill(::getpid(), signo);
+ ATF_REQUIRE_EQ(-1, fired_signal);
+ }
+ ::kill(::getpid(), signo);
+ ATF_REQUIRE_EQ(-1, fired_signal);
+ }
+ ATF_REQUIRE_EQ(signo, fired_signal);
+
+ test_handler.unprogram();
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sighup);
+ATF_TEST_CASE_BODY(interrupts_handler__sighup)
+{
+ // We run this twice in sequence to ensure that we can actually program two
+ // interrupts handlers in a row.
+ check_interrupts_handler(SIGHUP, true);
+ check_interrupts_handler(SIGHUP, false);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sigint);
+ATF_TEST_CASE_BODY(interrupts_handler__sigint)
+{
+ // We run this twice in sequence to ensure that we can actually program two
+ // interrupts handlers in a row.
+ check_interrupts_handler(SIGINT, true);
+ check_interrupts_handler(SIGINT, false);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sigterm);
+ATF_TEST_CASE_BODY(interrupts_handler__sigterm)
+{
+ // We run this twice in sequence to ensure that we can actually program two
+ // interrupts handlers in a row.
+ check_interrupts_handler(SIGTERM, true);
+ check_interrupts_handler(SIGTERM, false);
+}
+
+
+ATF_TEST_CASE(interrupts_handler__kill_children);
+ATF_TEST_CASE_HEAD(interrupts_handler__kill_children)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(interrupts_handler__kill_children)
+{
+ std::auto_ptr< process::child > child1(process::child::fork_files(
+ pause_child, fs::path("/dev/stdout"), fs::path("/dev/stderr")));
+ std::auto_ptr< process::child > child2(process::child::fork_files(
+ pause_child, fs::path("/dev/stdout"), fs::path("/dev/stderr")));
+
+ signals::interrupts_handler interrupts;
+
+ // Our children pause until the reception of a signal. Interrupting
+ // ourselves will cause the signal to be re-delivered to our children due to
+ // the interrupts_handler semantics. If this does not happen, the wait
+ // calls below would block indefinitely and cause our test to time out.
+ ::kill(::getpid(), SIGHUP);
+
+ const process::status status1 = child1->wait();
+ ATF_REQUIRE(status1.signaled());
+ ATF_REQUIRE_EQ(SIGKILL, status1.termsig());
+ const process::status status2 = child2->wait();
+ ATF_REQUIRE(status2.signaled());
+ ATF_REQUIRE_EQ(SIGKILL, status2.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigalrm);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sigalrm)
+{
+ check_interrupts_inhibiter(SIGALRM);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sighup);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sighup)
+{
+ check_interrupts_inhibiter(SIGHUP);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigint);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sigint)
+{
+ check_interrupts_inhibiter(SIGINT);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigterm);
+ATF_TEST_CASE_BODY(interrupts_inhibiter__sigterm)
+{
+ check_interrupts_inhibiter(SIGTERM);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__sighup);
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__sigint);
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__sigterm);
+ ATF_ADD_TEST_CASE(tcs, interrupts_handler__kill_children);
+
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigalrm);
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sighup);
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigint);
+ ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigterm);
+}
diff --git a/utils/signals/misc.cpp b/utils/signals/misc.cpp
new file mode 100644
index 000000000000..b9eb1c402a28
--- /dev/null
+++ b/utils/signals/misc.cpp
@@ -0,0 +1,106 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/misc.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <signal.h>
+}
+
+#include <cerrno>
+#include <cstddef>
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/signals/exceptions.hpp"
+
+namespace signals = utils::signals;
+
+
+/// Number of the last valid signal.
+const int utils::signals::last_signo = LAST_SIGNO;
+
+
+/// Resets a signal handler to its default behavior.
+///
+/// \param signo The number of the signal handler to reset.
+///
+/// \throw signals::system_error If there is a problem trying to reset the
+/// signal handler to its default behavior.
+void
+signals::reset(const int signo)
+{
+ struct ::sigaction sa;
+ sa.sa_handler = SIG_DFL;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ if (::sigaction(signo, &sa, NULL) == -1) {
+ const int original_errno = errno;
+ throw system_error(F("Failed to reset signal %s") % signo,
+ original_errno);
+ }
+}
+
+
+/// Resets all signals to their default handlers.
+///
+/// \return True if all signals could be reset properly; false otherwise.
+bool
+signals::reset_all(void)
+{
+ bool ok = true;
+
+ for (int signo = 1; signo <= signals::last_signo; ++signo) {
+ if (signo == SIGKILL || signo == SIGSTOP) {
+ // Don't attempt to reset immutable signals.
+ } else {
+ try {
+ signals::reset(signo);
+ } catch (const signals::error& e) {
+#if defined(SIGTHR)
+ if (signo == SIGTHR) {
+ // If FreeBSD's libthr is loaded, it prevents us from
+ // modifying SIGTHR (at least in 11.0-CURRENT as of
+ // 2015-01-28). Skip failures for this signal if they
+ // happen to avoid this corner case.
+ continue;
+ }
+#endif
+ LW(e.what());
+ ok = false;
+ }
+ }
+ }
+
+ return ok;
+}
diff --git a/utils/signals/misc.hpp b/utils/signals/misc.hpp
new file mode 100644
index 000000000000..ad3763feabc4
--- /dev/null
+++ b/utils/signals/misc.hpp
@@ -0,0 +1,49 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/misc.hpp
+/// Free functions and globals.
+
+#if !defined(UTILS_SIGNALS_MISC_HPP)
+#define UTILS_SIGNALS_MISC_HPP
+
+namespace utils {
+namespace signals {
+
+
+extern const int last_signo;
+
+
+void reset(const int);
+bool reset_all(void);
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_MISC_HPP)
diff --git a/utils/signals/misc_test.cpp b/utils/signals/misc_test.cpp
new file mode 100644
index 000000000000..76f36b0e5082
--- /dev/null
+++ b/utils/signals/misc_test.cpp
@@ -0,0 +1,133 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/misc.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/process/child.ipp"
+#include "utils/process/status.hpp"
+#include "utils/signals/exceptions.hpp"
+
+namespace fs = utils::fs;
+namespace process = utils::process;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+static void program_reset_raise(void) UTILS_NORETURN;
+
+
+/// Body of a subprocess that tests the signals::reset function.
+///
+/// This function programs a signal to be ignored, then uses signal::reset to
+/// bring it back to its default handler and then delivers the signal to self.
+/// The default behavior of the signal is for the process to die, so this
+/// function should never return correctly (and thus the child process should
+/// always die due to a signal if all goes well).
+static void
+program_reset_raise(void)
+{
+ struct ::sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ if (::sigaction(SIGUSR1, &sa, NULL) == -1)
+ std::exit(EXIT_FAILURE);
+
+ signals::reset(SIGUSR1);
+ ::kill(::getpid(), SIGUSR1);
+
+ // Should not be reached, but we do not assert this condition because we
+ // want to exit cleanly if the signal does not abort our execution to let
+ // the parent easily know what happened.
+ std::exit(EXIT_SUCCESS);
+}
+
+
+/// Body of a subprocess that executes the signals::reset_all function.
+///
+/// The process exits with success if the function worked, or with a failure if
+/// an error is reported. No signals are tested.
+static void
+run_reset_all(void)
+{
+ const bool ok = signals::reset_all();
+ std::exit(ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset__ok);
+ATF_TEST_CASE_BODY(reset__ok)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ program_reset_raise, fs::path("/dev/stdout"), fs::path("/dev/stderr"));
+ process::status status = child->wait();
+ ATF_REQUIRE(status.signaled());
+ ATF_REQUIRE_EQ(SIGUSR1, status.termsig());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset__invalid);
+ATF_TEST_CASE_BODY(reset__invalid)
+{
+ ATF_REQUIRE_THROW(signals::system_error, signals::reset(-1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(reset_all);
+ATF_TEST_CASE_BODY(reset_all)
+{
+ std::auto_ptr< process::child > child = process::child::fork_files(
+ run_reset_all, fs::path("/dev/stdout"), fs::path("/dev/stderr"));
+ process::status status = child->wait();
+ ATF_REQUIRE(status.exited());
+ ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, reset__ok);
+ ATF_ADD_TEST_CASE(tcs, reset__invalid);
+ ATF_ADD_TEST_CASE(tcs, reset_all);
+}
diff --git a/utils/signals/programmer.cpp b/utils/signals/programmer.cpp
new file mode 100644
index 000000000000..c47d1cf85038
--- /dev/null
+++ b/utils/signals/programmer.cpp
@@ -0,0 +1,138 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/programmer.hpp"
+
+extern "C" {
+#include <signal.h>
+}
+
+#include <cerrno>
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+
+
+namespace utils {
+namespace signals {
+
+
+/// Internal implementation for the signals::programmer class.
+struct programmer::impl : utils::noncopyable {
+ /// The number of the signal managed by this programmer.
+ int signo;
+
+ /// Whether the signal is currently programmed by us or not.
+ bool programmed;
+
+ /// The signal handler that we replaced; to be restored on unprogramming.
+ struct ::sigaction old_sa;
+
+ /// Initializes the internal implementation of the programmer.
+ ///
+ /// \param signo_ The signal number.
+ impl(const int signo_) :
+ signo(signo_),
+ programmed(false)
+ {
+ }
+};
+
+
+} // namespace signals
+} // namespace utils
+
+
+namespace signals = utils::signals;
+
+
+/// Programs a signal handler.
+///
+/// \param signo The signal for which to install the handler.
+/// \param handler The handler to install.
+///
+/// \throw signals::system_error If there is an error programming the signal.
+signals::programmer::programmer(const int signo, const handler_type handler) :
+ _pimpl(new impl(signo))
+{
+ struct ::sigaction sa;
+ sa.sa_handler = handler;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+
+ if (::sigaction(_pimpl->signo, &sa, &_pimpl->old_sa) == -1) {
+ const int original_errno = errno;
+ throw system_error(F("Could not install handler for signal %s") %
+ _pimpl->signo, original_errno);
+ } else
+ _pimpl->programmed = true;
+}
+
+
+/// Destructor; unprograms the signal handler if still programmed.
+///
+/// Given that this is a destructor and it can't report errors back to the
+/// caller, the caller must attempt to call unprogram() on its own.
+signals::programmer::~programmer(void)
+{
+ if (_pimpl->programmed) {
+ LW("Destroying still-programmed signals::programmer object");
+ try {
+ unprogram();
+ } catch (const system_error& e) {
+ UNREACHABLE;
+ }
+ }
+}
+
+
+/// Unprograms the signal handler.
+///
+/// \pre The signal handler is programmed (i.e. this can only be called once).
+///
+/// \throw system_error If unprogramming the signal failed. If this happens,
+/// the signal is left programmed, this object forgets about the signal and
+/// therefore there is no way to restore the original handler.
+void
+signals::programmer::unprogram(void)
+{
+ PRE(_pimpl->programmed);
+
+ // If we fail, we don't want the destructor to attempt to unprogram the
+ // handler again, as it would result in a crash.
+ _pimpl->programmed = false;
+
+ if (::sigaction(_pimpl->signo, &_pimpl->old_sa, NULL) == -1) {
+ const int original_errno = errno;
+ throw system_error(F("Could not reset handler for signal %s") %
+ _pimpl->signo, original_errno);
+ }
+}
diff --git a/utils/signals/programmer.hpp b/utils/signals/programmer.hpp
new file mode 100644
index 000000000000..5ac5318f0bb9
--- /dev/null
+++ b/utils/signals/programmer.hpp
@@ -0,0 +1,63 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/programmer.hpp
+/// Provides the signals::programmer class.
+
+#if !defined(UTILS_SIGNALS_PROGRAMMER_HPP)
+#define UTILS_SIGNALS_PROGRAMMER_HPP
+
+#include "utils/signals/programmer_fwd.hpp"
+
+#include <memory>
+
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace signals {
+
+
+/// A RAII class to program signal handlers.
+class programmer : noncopyable {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+public:
+ programmer(const int, const handler_type);
+ ~programmer(void);
+
+ void unprogram(void);
+};
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_PROGRAMMER_HPP)
diff --git a/utils/signals/programmer_fwd.hpp b/utils/signals/programmer_fwd.hpp
new file mode 100644
index 000000000000..55dfd34af2eb
--- /dev/null
+++ b/utils/signals/programmer_fwd.hpp
@@ -0,0 +1,49 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/programmer_fwd.hpp
+/// Forward declarations for utils/signals/programmer.hpp
+
+#if !defined(UTILS_SIGNALS_PROGRAMMER_FWD_HPP)
+#define UTILS_SIGNALS_PROGRAMMER_FWD_HPP
+
+namespace utils {
+namespace signals {
+
+
+/// Function type for signal handlers.
+typedef void (*handler_type)(const int);
+
+
+class programmer;
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_PROGRAMMER_FWD_HPP)
diff --git a/utils/signals/programmer_test.cpp b/utils/signals/programmer_test.cpp
new file mode 100644
index 000000000000..0e95f84974b1
--- /dev/null
+++ b/utils/signals/programmer_test.cpp
@@ -0,0 +1,140 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/programmer.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <atf-c++.hpp>
+
+#include "utils/sanity.hpp"
+
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+namespace sigchld {
+
+
+static bool happened_1;
+static bool happened_2;
+
+
+void handler_1(const int signo) {
+ PRE(signo == SIGCHLD);
+ happened_1 = true;
+}
+
+
+void handler_2(const int signo) {
+ PRE(signo == SIGCHLD);
+ happened_2 = true;
+}
+
+
+} // namespace sigchld
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(program_unprogram);
+ATF_TEST_CASE_BODY(program_unprogram)
+{
+ signals::programmer programmer(SIGCHLD, sigchld::handler_1);
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+
+ programmer.unprogram();
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(scope);
+ATF_TEST_CASE_BODY(scope)
+{
+ {
+ signals::programmer programmer(SIGCHLD, sigchld::handler_1);
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+ }
+
+ sigchld::happened_1 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(nested);
+ATF_TEST_CASE_BODY(nested)
+{
+ signals::programmer programmer_1(SIGCHLD, sigchld::handler_1);
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+ ATF_REQUIRE(!sigchld::happened_2);
+
+ signals::programmer programmer_2(SIGCHLD, sigchld::handler_2);
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+ ATF_REQUIRE(sigchld::happened_2);
+
+ programmer_2.unprogram();
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(sigchld::happened_1);
+ ATF_REQUIRE(!sigchld::happened_2);
+
+ programmer_1.unprogram();
+ sigchld::happened_1 = false;
+ sigchld::happened_2 = false;
+ ::kill(::getpid(), SIGCHLD);
+ ATF_REQUIRE(!sigchld::happened_1);
+ ATF_REQUIRE(!sigchld::happened_2);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, program_unprogram);
+ ATF_ADD_TEST_CASE(tcs, scope);
+ ATF_ADD_TEST_CASE(tcs, nested);
+}
diff --git a/utils/signals/timer.cpp b/utils/signals/timer.cpp
new file mode 100644
index 000000000000..698b9835dc10
--- /dev/null
+++ b/utils/signals/timer.cpp
@@ -0,0 +1,547 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/timer.hpp"
+
+extern "C" {
+#include <sys/time.h>
+
+#include <signal.h>
+}
+
+#include <cerrno>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "utils/datetime.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.ipp"
+#include "utils/sanity.hpp"
+#include "utils/signals/exceptions.hpp"
+#include "utils/signals/interrupts.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace datetime = utils::datetime;
+namespace signals = utils::signals;
+
+using utils::none;
+using utils::optional;
+
+namespace {
+
+
+static void sigalrm_handler(const int);
+
+
+/// Calls setitimer(2) with exception-based error reporting.
+///
+/// This does not currently support intervals.
+///
+/// \param delta The time to the first activation of the programmed timer.
+/// \param old_timeval If not NULL, pointer to a timeval into which to store the
+/// existing system timer.
+///
+/// \throw system_error If the call to setitimer(2) fails.
+static void
+safe_setitimer(const datetime::delta& delta, itimerval* old_timeval)
+{
+ ::itimerval timeval;
+ timerclear(&timeval.it_interval);
+ timeval.it_value.tv_sec = delta.seconds;
+ timeval.it_value.tv_usec = delta.useconds;
+
+ if (::setitimer(ITIMER_REAL, &timeval, old_timeval) == -1) {
+ const int original_errno = errno;
+ throw signals::system_error("Failed to program system's interval timer",
+ original_errno);
+ }
+}
+
+
+/// Deadline scheduler for all user timers on top of the unique system timer.
+class global_state : utils::noncopyable {
+ /// Collection of active timers.
+ ///
+ /// Because this is a collection of pointers, all timers are guaranteed to
+ /// be unique, and we want all of these pointers to be valid.
+ typedef std::set< signals::timer* > timers_set;
+
+ /// Sequence of ordered timers.
+ typedef std::vector< signals::timer* > timers_vector;
+
+ /// Collection of active timestamps by their activation timestamp.
+ ///
+ /// This collection is ordered intentionally so that it can be scanned
+ /// sequentially to find either expired or expiring-now timers.
+ typedef std::map< datetime::timestamp, timers_set > timers_by_timestamp_map;
+
+ /// The original timer before any timer was programmed.
+ ::itimerval _old_timeval;
+
+ /// Programmer for the SIGALRM handler.
+ std::auto_ptr< signals::programmer > _sigalrm_programmer;
+
+ /// Time of the current activation of the timer.
+ datetime::timestamp _timer_activation;
+
+ /// Mapping of all active timers using their timestamp as the key.
+ timers_by_timestamp_map _all_timers;
+
+ /// Adds a timer to the _all_timers map.
+ ///
+ /// \param timer The timer to add.
+ void
+ add_to_all_timers(signals::timer* timer)
+ {
+ timers_set& timers = _all_timers[timer->when()];
+ INV(timers.find(timer) == timers.end());
+ timers.insert(timer);
+ }
+
+ /// Removes a timer from the _all_timers map.
+ ///
+ /// This ensures that empty vectors are removed from _all_timers if the
+ /// removal of the timer causes its bucket to be emptied.
+ ///
+ /// \param timer The timer to remove.
+ void
+ remove_from_all_timers(signals::timer* timer)
+ {
+ // We may not find the timer in _all_timers if the timer has fired,
+ // because fire() took it out from the map.
+ timers_by_timestamp_map::iterator iter = _all_timers.find(
+ timer->when());
+ if (iter != _all_timers.end()) {
+ timers_set& timers = (*iter).second;
+ INV(timers.find(timer) != timers.end());
+ timers.erase(timer);
+ if (timers.empty()) {
+ _all_timers.erase(iter);
+ }
+ }
+ }
+
+ /// Calculates all timers to execute at this timestamp.
+ ///
+ /// \param now The current timestamp.
+ ///
+ /// \post _all_timers is updated to contain only the timers that are
+ /// strictly in the future.
+ ///
+ /// \return A sequence of valid timers that need to be invoked in the order
+ /// of activation. These are all previously registered timers with
+ /// activations in the past.
+ timers_vector
+ compute_timers_to_run_and_prune_old(
+ const datetime::timestamp& now,
+ const signals::interrupts_inhibiter& /* inhibiter */)
+ {
+ timers_vector to_run;
+
+ timers_by_timestamp_map::iterator iter = _all_timers.begin();
+ while (iter != _all_timers.end() && (*iter).first <= now) {
+ const timers_set& timers = (*iter).second;
+ to_run.insert(to_run.end(), timers.begin(), timers.end());
+
+ // Remove expired entries here so that we can always assume that
+ // the first entry in all_timers corresponds to the next
+ // activation.
+ const timers_by_timestamp_map::iterator previous_iter = iter;
+ ++iter;
+ _all_timers.erase(previous_iter);
+ }
+
+ return to_run;
+ }
+
+ /// Adjusts the global system timer to point to the next activation.
+ ///
+ /// \param now The current timestamp.
+ ///
+ /// \throw system_error If the programming fails.
+ void
+ reprogram_system_timer(
+ const datetime::timestamp& now,
+ const signals::interrupts_inhibiter& /* inhibiter */)
+ {
+ if (_all_timers.empty()) {
+ // Nothing to do. We can reach this case if all the existing timers
+ // are in the past and they all fired. Just ignore the request and
+ // leave the global timer as is.
+ return;
+ }
+
+ // While fire() prunes old entries from the list of timers, it is
+ // possible for this routine to run with "expired" timers (i.e. timers
+ // whose deadline lies in the past but that have not yet fired for
+ // whatever reason that is out of our control) in the list. We have to
+ // iterate until we find the next activation instead of assuming that
+ // the first entry represents the desired value.
+ timers_by_timestamp_map::const_iterator iter = _all_timers.begin();
+ PRE(!(*iter).second.empty());
+ datetime::timestamp next = (*iter).first;
+ while (next < now) {
+ ++iter;
+ if (iter == _all_timers.end()) {
+ // Nothing to do. We can reach this case if all the existing
+ // timers are in the past but they have not yet fired.
+ return;
+ }
+ PRE(!(*iter).second.empty());
+ next = (*iter).first;
+ }
+
+ if (next < _timer_activation || now > _timer_activation) {
+ INV(next >= now);
+ const datetime::delta delta = next - now;
+ LD(F("Reprogramming timer; firing on %s; now is %s") % next % now);
+ safe_setitimer(delta, NULL);
+ _timer_activation = next;
+ }
+ }
+
+public:
+ /// Programs the first timer.
+ ///
+ /// The programming of the first timer involves setting up the SIGALRM
+ /// handler and installing a timer handler for the first time, which in turn
+ /// involves keeping track of the old handlers so that we can restore them.
+ ///
+ /// \param timer The timer being programmed.
+ /// \param now The current timestamp.
+ ///
+ /// \throw system_error If the programming fails.
+ global_state(signals::timer* timer, const datetime::timestamp& now) :
+ _timer_activation(timer->when())
+ {
+ PRE(now < timer->when());
+
+ signals::interrupts_inhibiter inhibiter;
+
+ const datetime::delta delta = timer->when() - now;
+ LD(F("Installing first timer; firing on %s; now is %s") %
+ timer->when() % now);
+
+ _sigalrm_programmer.reset(
+ new signals::programmer(SIGALRM, sigalrm_handler));
+ try {
+ safe_setitimer(delta, &_old_timeval);
+ _timer_activation = timer->when();
+ add_to_all_timers(timer);
+ } catch (...) {
+ _sigalrm_programmer.reset(NULL);
+ throw;
+ }
+ }
+
+ /// Unprograms all timers.
+ ///
+ /// This clears the global system timer and unsets the SIGALRM handler.
+ ~global_state(void)
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ LD("Unprogramming all timers");
+
+ if (::setitimer(ITIMER_REAL, &_old_timeval, NULL) == -1) {
+ UNREACHABLE_MSG("Failed to restore original timer");
+ }
+
+ _sigalrm_programmer->unprogram();
+ _sigalrm_programmer.reset(NULL);
+ }
+
+ /// Programs a new timer, possibly adjusting the global system timer.
+ ///
+ /// Programming any timer other than the first one only involves reloading
+ /// the existing timer, not backing up the previous handler nor installing a
+ /// handler for SIGALRM.
+ ///
+ /// \param timer The timer being programmed.
+ /// \param now The current timestamp.
+ ///
+ /// \throw system_error If the programming fails.
+ void
+ program_new(signals::timer* timer, const datetime::timestamp& now)
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ add_to_all_timers(timer);
+ reprogram_system_timer(now, inhibiter);
+ }
+
+ /// Unprograms a timer.
+ ///
+ /// This removes the timer from the global state and reprograms the global
+ /// system timer if necessary.
+ ///
+ /// \param timer The timer to unprogram.
+ ///
+ /// \return True if the system interval timer has been reprogrammed to
+ /// another future timer; false if there are no more active timers.
+ bool
+ unprogram(signals::timer* timer)
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ LD(F("Unprogramming timer; previously firing on %s") % timer->when());
+
+ remove_from_all_timers(timer);
+ if (_all_timers.empty()) {
+ return false;
+ } else {
+ reprogram_system_timer(datetime::timestamp::now(), inhibiter);
+ return true;
+ }
+ }
+
+ /// Executes active timers.
+ ///
+ /// Active timers are all those that fire on or before 'now'.
+ ///
+ /// \param now The current time.
+ void
+ fire(const datetime::timestamp& now)
+ {
+ timers_vector to_run;
+ {
+ signals::interrupts_inhibiter inhibiter;
+ to_run = compute_timers_to_run_and_prune_old(now, inhibiter);
+ reprogram_system_timer(now, inhibiter);
+ }
+
+ for (timers_vector::iterator iter = to_run.begin();
+ iter != to_run.end(); ++iter) {
+ signals::detail::invoke_do_fired(*iter);
+ }
+ }
+};
+
+
+/// Unique instance of the global state.
+static std::auto_ptr< global_state > globals;
+
+
+/// SIGALRM handler for the timer implementation.
+///
+/// \param signo The signal received; must be SIGALRM.
+static void
+sigalrm_handler(const int signo)
+{
+ PRE(signo == SIGALRM);
+ globals->fire(datetime::timestamp::now());
+}
+
+
+} // anonymous namespace
+
+
+/// Indirection to invoke the private do_fired() method of a timer.
+///
+/// \param timer The timer on which to run do_fired().
+void
+utils::signals::detail::invoke_do_fired(timer* timer)
+{
+ timer->do_fired();
+}
+
+
+/// Internal implementation for the timer.
+///
+/// We assume that there is a 1-1 mapping between timer objects and impl
+/// objects. If this assumption breaks, then the rest of the code in this
+/// module breaks as well because we use pointers to the parent timer as the
+/// identifier of the timer.
+struct utils::signals::timer::impl : utils::noncopyable {
+ /// Timestamp when this timer is expected to fire.
+ ///
+ /// Note that the timer might be processed after this timestamp, so users of
+ /// this field need to check for timers that fire on or before the
+ /// activation time.
+ datetime::timestamp when;
+
+ /// True until unprogram() is called.
+ bool programmed;
+
+ /// Whether this timer has fired already or not.
+ ///
+ /// This is updated from an interrupt context, hence why it is marked
+ /// volatile.
+ volatile bool fired;
+
+ /// Constructor.
+ ///
+ /// \param when_ Timestamp when this timer is expected to fire.
+ impl(const datetime::timestamp& when_) :
+ when(when_), programmed(true), fired(false)
+ {
+ }
+
+ /// Destructor.
+ ~impl(void) {
+ }
+};
+
+
+/// Constructor; programs a run-once timer.
+///
+/// This programs the global timer and signal handler if this is the first timer
+/// being installed. Otherwise, reprograms the global timer if this timer
+/// expires earlier than all other active timers.
+///
+/// \param delta The time until the timer fires.
+signals::timer::timer(const datetime::delta& delta)
+{
+ signals::interrupts_inhibiter inhibiter;
+
+ const datetime::timestamp now = datetime::timestamp::now();
+ _pimpl.reset(new impl(now + delta));
+ if (globals.get() == NULL) {
+ globals.reset(new global_state(this, now));
+ } else {
+ globals->program_new(this, now);
+ }
+}
+
+
+/// Destructor; unprograms the timer if still programmed.
+///
+/// Given that this is a destructor and it can't report errors back to the
+/// caller, the caller must attempt to call unprogram() on its own. This is
+/// extremely important because, otherwise, expired timers will never run!
+signals::timer::~timer(void)
+{
+ signals::interrupts_inhibiter inhibiter;
+
+ if (_pimpl->programmed) {
+ LW("Auto-destroying still-programmed signals::timer object");
+ try {
+ unprogram();
+ } catch (const system_error& e) {
+ UNREACHABLE;
+ }
+ }
+
+ if (!_pimpl->fired) {
+ const datetime::timestamp now = datetime::timestamp::now();
+ if (now > _pimpl->when) {
+ LW("Expired timer never fired; the code never called unprogram()!");
+ }
+ }
+}
+
+
+/// Returns the time of the timer activation.
+///
+/// \return A timestamp that has no relation to the current time (i.e. can be in
+/// the future or in the past) nor the timer's activation status.
+const datetime::timestamp&
+signals::timer::when(void) const
+{
+ return _pimpl->when;
+}
+
+
+/// Callback for the SIGALRM handler when this timer expires.
+///
+/// \warning This is executed from a signal handler context without signals
+/// inhibited. See signal(7) for acceptable system calls.
+void
+signals::timer::do_fired(void)
+{
+ PRE(!_pimpl->fired);
+ _pimpl->fired = true;
+ callback();
+}
+
+
+/// User-provided callback to run when the timer expires.
+///
+/// The default callback does nothing. We record the activation of the timer
+/// separately, which may be appropriate in the majority of the cases.
+///
+/// \warning This is executed from a signal handler context without signals
+/// inhibited. See signal(7) for acceptable system calls.
+void
+signals::timer::callback(void)
+{
+ // Intentionally left blank.
+}
+
+
+/// Checks whether the timer has fired already or not.
+///
+/// \return Returns true if the timer has fired.
+bool
+signals::timer::fired(void) const
+{
+ return _pimpl->fired;
+}
+
+
+/// Unprograms the timer.
+///
+/// \pre The timer is programmed (i.e. this can only be called once).
+///
+/// \post If the timer never fired asynchronously because the signal delivery
+/// did not arrive on time, make sure we invoke the timer's callback here.
+///
+/// \throw system_error If unprogramming the timer failed.
+void
+signals::timer::unprogram(void)
+{
+ signals::interrupts_inhibiter inhibiter;
+
+ if (!_pimpl->programmed) {
+ // We cannot assert that the timer is not programmed because it might
+ // have been unprogrammed asynchronously between the time we called
+ // unprogram() and the time we reach this. Simply return in this case.
+ LD("Called unprogram on already-unprogrammed timer; possibly just "
+ "a race");
+ return;
+ }
+
+ if (!globals->unprogram(this)) {
+ globals.reset(NULL);
+ }
+ _pimpl->programmed = false;
+
+ // Handle the case where the timer has expired before we ever got its
+ // corresponding signal. Do so by invoking its callback now.
+ if (!_pimpl->fired) {
+ const datetime::timestamp now = datetime::timestamp::now();
+ if (now > _pimpl->when) {
+ LW(F("Firing expired timer on destruction (was to fire on %s)") %
+ _pimpl->when);
+ do_fired();
+ }
+ }
+}
diff --git a/utils/signals/timer.hpp b/utils/signals/timer.hpp
new file mode 100644
index 000000000000..1174effe2b48
--- /dev/null
+++ b/utils/signals/timer.hpp
@@ -0,0 +1,86 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/timer.hpp
+/// Multiprogrammed support for timers.
+///
+/// The timer module and class implement a mechanism to program multiple timers
+/// concurrently by using a deadline scheduler and leveraging the "single timer"
+/// features of the underlying operating system.
+
+#if !defined(UTILS_SIGNALS_TIMER_HPP)
+#define UTILS_SIGNALS_TIMER_HPP
+
+#include "utils/signals/timer_fwd.hpp"
+
+#include <memory>
+
+#include "utils/datetime_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace signals {
+
+
+namespace detail {
+void invoke_do_fired(timer*);
+} // namespace detail
+
+
+/// Individual timer.
+///
+/// Asynchronously executes its callback() method, which can be overridden by
+/// subclasses, when the timeout given at construction expires.
+class timer : noncopyable {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+ friend void detail::invoke_do_fired(timer*);
+ void do_fired(void);
+
+protected:
+ virtual void callback(void);
+
+public:
+ timer(const utils::datetime::delta&);
+ virtual ~timer(void);
+
+ const utils::datetime::timestamp& when(void) const;
+
+ bool fired(void) const;
+
+ void unprogram(void);
+};
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_TIMER_HPP)
diff --git a/utils/signals/timer_fwd.hpp b/utils/signals/timer_fwd.hpp
new file mode 100644
index 000000000000..a3cf3e205d70
--- /dev/null
+++ b/utils/signals/timer_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/signals/timer_fwd.hpp
+/// Forward declarations for utils/signals/timer.hpp
+
+#if !defined(UTILS_SIGNALS_TIMER_FWD_HPP)
+#define UTILS_SIGNALS_TIMER_FWD_HPP
+
+namespace utils {
+namespace signals {
+
+
+class timer;
+
+
+} // namespace signals
+} // namespace utils
+
+#endif // !defined(UTILS_SIGNALS_TIMER_FWD_HPP)
diff --git a/utils/signals/timer_test.cpp b/utils/signals/timer_test.cpp
new file mode 100644
index 000000000000..61e9cac6b088
--- /dev/null
+++ b/utils/signals/timer_test.cpp
@@ -0,0 +1,426 @@
+// Copyright 2010 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/signals/timer.hpp"
+
+extern "C" {
+#include <signal.h>
+#include <unistd.h>
+}
+
+#include <cstddef>
+#include <iostream>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "utils/datetime.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/containers.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/signals/interrupts.hpp"
+#include "utils/signals/programmer.hpp"
+
+namespace datetime = utils::datetime;
+namespace signals = utils::signals;
+
+
+namespace {
+
+
+/// A timer that inserts an element into a vector on activation.
+class delayed_inserter : public signals::timer {
+ /// Vector into which to insert the element.
+ std::vector< int >& _destination;
+
+ /// Element to insert into _destination on activation.
+ const int _item;
+
+ /// Timer activation callback.
+ void
+ callback(void)
+ {
+ signals::interrupts_inhibiter inhibiter;
+ _destination.push_back(_item);
+ }
+
+public:
+ /// Constructor.
+ ///
+ /// \param delta Time to the timer activation.
+ /// \param destination Vector into which to insert the element.
+ /// \param item Element to insert into destination on activation.
+ delayed_inserter(const datetime::delta& delta,
+ std::vector< int >& destination, const int item) :
+ signals::timer(delta), _destination(destination), _item(item)
+ {
+ }
+};
+
+
+/// Signal handler that does nothing.
+static void
+null_handler(const int /* signo */)
+{
+}
+
+
+/// Waits for the activation of all given timers.
+///
+/// \param timers Pointers to all the timers to wait for.
+static void
+wait_timers(const std::vector< signals::timer* >& timers)
+{
+ std::size_t n_fired, old_n_fired = 0;
+ do {
+ n_fired = 0;
+ for (std::vector< signals::timer* >::const_iterator
+ iter = timers.begin(); iter != timers.end(); ++iter) {
+ const signals::timer* timer = *iter;
+ if (timer->fired())
+ ++n_fired;
+ }
+ if (old_n_fired < n_fired) {
+ std::cout << "Waiting; " << n_fired << " timers fired so far\n";
+ old_n_fired = n_fired;
+ }
+ ::usleep(100);
+ } while (n_fired < timers.size());
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE(program_seconds);
+ATF_TEST_CASE_HEAD(program_seconds)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(program_seconds)
+{
+ signals::timer timer(datetime::delta(1, 0));
+ ATF_REQUIRE(!timer.fired());
+ while (!timer.fired())
+ ::usleep(1000);
+}
+
+
+ATF_TEST_CASE(program_useconds);
+ATF_TEST_CASE_HEAD(program_useconds)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(program_useconds)
+{
+ signals::timer timer(datetime::delta(0, 500000));
+ ATF_REQUIRE(!timer.fired());
+ while (!timer.fired())
+ ::usleep(1000);
+}
+
+
+ATF_TEST_CASE(multiprogram_ordered);
+ATF_TEST_CASE_HEAD(multiprogram_ordered)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_ordered)
+{
+ static const std::size_t n_timers = 100;
+
+ std::vector< signals::timer* > timers;
+ std::vector< int > items, exp_items;
+
+ const int initial_delay_ms = 1000000;
+ for (std::size_t i = 0; i < n_timers; ++i) {
+ exp_items.push_back(i);
+
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, initial_delay_ms + (i + 1) * 10000),
+ items, i));
+ ATF_REQUIRE(!timers[i]->fired());
+ }
+
+ wait_timers(timers);
+
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(multiprogram_reorder_next_activations);
+ATF_TEST_CASE_HEAD(multiprogram_reorder_next_activations)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_reorder_next_activations)
+{
+ std::vector< signals::timer* > timers;
+ std::vector< int > items;
+
+ // First timer with an activation in the future.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 100000), items, 1));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation earlier than the previous one.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 50000), items, 2));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation later than all others.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 200000), items, 3));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation in between.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 150000), items, 4));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ wait_timers(timers);
+
+ std::vector< int > exp_items;
+ exp_items.push_back(2);
+ exp_items.push_back(1);
+ exp_items.push_back(4);
+ exp_items.push_back(3);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(multiprogram_and_cancel_some);
+ATF_TEST_CASE_HEAD(multiprogram_and_cancel_some)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_and_cancel_some)
+{
+ std::vector< signals::timer* > timers;
+ std::vector< int > items;
+
+ // First timer with an activation in the future.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 100000), items, 1));
+
+ // Timer with an activation earlier than the previous one.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 50000), items, 2));
+
+ // Timer with an activation later than all others.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 200000), items, 3));
+
+ // Timer with an activation in between.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 150000), items, 4));
+
+ // Cancel the first timer to reprogram next activation.
+ timers[1]->unprogram(); delete timers[1]; timers.erase(timers.begin() + 1);
+
+ // Cancel another timer without reprogramming next activation.
+ timers[2]->unprogram(); delete timers[2]; timers.erase(timers.begin() + 2);
+
+ wait_timers(timers);
+
+ std::vector< int > exp_items;
+ exp_items.push_back(1);
+ exp_items.push_back(3);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(multiprogram_and_expire_before_activations);
+ATF_TEST_CASE_HEAD(multiprogram_and_expire_before_activations)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(multiprogram_and_expire_before_activations)
+{
+ std::vector< signals::timer* > timers;
+ std::vector< int > items;
+
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ // First timer with an activation in the future.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 100000), items, 1));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ // Timer with an activation earlier than the previous one.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 50000), items, 2));
+ ATF_REQUIRE(!timers[timers.size() - 1]->fired());
+
+ ::sleep(1);
+
+ // Timer with an activation later than all others.
+ timers.push_back(new delayed_inserter(
+ datetime::delta(0, 200000), items, 3));
+
+ ::sleep(1);
+ }
+
+ wait_timers(timers);
+
+ std::vector< int > exp_items;
+ exp_items.push_back(2);
+ exp_items.push_back(1);
+ exp_items.push_back(3);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(expire_before_firing);
+ATF_TEST_CASE_HEAD(expire_before_firing)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(expire_before_firing)
+{
+ std::vector< int > items;
+
+ // The code below causes a signal to go pending. Make sure we ignore it
+ // when we unblock signals.
+ signals::programmer sigalrm(SIGALRM, null_handler);
+
+ {
+ signals::interrupts_inhibiter inhibiter;
+
+ delayed_inserter* timer = new delayed_inserter(
+ datetime::delta(0, 1000), items, 1234);
+ ::sleep(1);
+ // Interrupts are inhibited so we never got a chance to execute the
+ // timer before it was destroyed. However, the handler should run
+ // regardless at some point, possibly during deletion.
+ timer->unprogram();
+ delete timer;
+ }
+
+ std::vector< int > exp_items;
+ exp_items.push_back(1234);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(reprogram_from_scratch);
+ATF_TEST_CASE_HEAD(reprogram_from_scratch)
+{
+ set_md_var("timeout", "20");
+}
+ATF_TEST_CASE_BODY(reprogram_from_scratch)
+{
+ std::vector< int > items;
+
+ delayed_inserter* timer1 = new delayed_inserter(
+ datetime::delta(0, 100000), items, 1);
+ timer1->unprogram(); delete timer1;
+
+ // All constructed timers are now dead, so the interval timer should have
+ // been reprogrammed. Let's start over.
+
+ delayed_inserter* timer2 = new delayed_inserter(
+ datetime::delta(0, 200000), items, 2);
+ while (!timer2->fired())
+ ::usleep(1000);
+ timer2->unprogram(); delete timer2;
+
+ std::vector< int > exp_items;
+ exp_items.push_back(2);
+ ATF_REQUIRE_EQ(exp_items, items);
+}
+
+
+ATF_TEST_CASE(unprogram);
+ATF_TEST_CASE_HEAD(unprogram)
+{
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(unprogram)
+{
+ signals::timer timer(datetime::delta(0, 500000));
+ timer.unprogram();
+ usleep(500000);
+ ATF_REQUIRE(!timer.fired());
+}
+
+
+ATF_TEST_CASE(infinitesimal);
+ATF_TEST_CASE_HEAD(infinitesimal)
+{
+ set_md_var("descr", "Ensure that the ordering in which the signal, the "
+ "timer and the global state are programmed is correct; do so "
+ "by setting an extremely small delay for the timer hoping that "
+ "it can trigger such conditions");
+ set_md_var("timeout", "10");
+}
+ATF_TEST_CASE_BODY(infinitesimal)
+{
+ const std::size_t rounds = 100;
+ const std::size_t exp_good = 90;
+
+ std::size_t good = 0;
+ for (std::size_t i = 0; i < rounds; i++) {
+ signals::timer timer(datetime::delta(0, 1));
+
+ // From the setitimer(2) documentation:
+ //
+ // Time values smaller than the resolution of the system clock are
+ // rounded up to this resolution (typically 10 milliseconds).
+ //
+ // We don't know what this resolution is but we must wait for longer
+ // than we programmed; do a rough guess and hope it is good. This may
+ // be obviously wrong and thus lead to mysterious test failures in some
+ // systems, hence why we only expect a percentage of successes below.
+ // Still, we can fail...
+ ::usleep(1000);
+
+ if (timer.fired())
+ ++good;
+ timer.unprogram();
+ }
+ std::cout << F("Ran %s tests, %s passed; threshold is %s\n")
+ % rounds % good % exp_good;
+ ATF_REQUIRE(good >= exp_good);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, program_seconds);
+ ATF_ADD_TEST_CASE(tcs, program_useconds);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_ordered);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_reorder_next_activations);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_and_cancel_some);
+ ATF_ADD_TEST_CASE(tcs, multiprogram_and_expire_before_activations);
+ ATF_ADD_TEST_CASE(tcs, expire_before_firing);
+ ATF_ADD_TEST_CASE(tcs, reprogram_from_scratch);
+ ATF_ADD_TEST_CASE(tcs, unprogram);
+ ATF_ADD_TEST_CASE(tcs, infinitesimal);
+}