diff options
author | Brooks Davis <brooks@FreeBSD.org> | 2020-03-17 16:56:50 +0000 |
---|---|---|
committer | Brooks Davis <brooks@FreeBSD.org> | 2020-03-17 16:56:50 +0000 |
commit | 08334c51dbb99d9ecd2bb86a2d94ed06da9e167a (patch) | |
tree | c43eb24d59bd5c963583a5190caef80fc8387322 /utils/signals |
Notes
Diffstat (limited to 'utils/signals')
-rw-r--r-- | utils/signals/Kyuafile | 9 | ||||
-rw-r--r-- | utils/signals/Makefile.am.inc | 73 | ||||
-rw-r--r-- | utils/signals/exceptions.cpp | 102 | ||||
-rw-r--r-- | utils/signals/exceptions.hpp | 83 | ||||
-rw-r--r-- | utils/signals/exceptions_test.cpp | 73 | ||||
-rw-r--r-- | utils/signals/interrupts.cpp | 309 | ||||
-rw-r--r-- | utils/signals/interrupts.hpp | 83 | ||||
-rw-r--r-- | utils/signals/interrupts_fwd.hpp | 46 | ||||
-rw-r--r-- | utils/signals/interrupts_test.cpp | 266 | ||||
-rw-r--r-- | utils/signals/misc.cpp | 106 | ||||
-rw-r--r-- | utils/signals/misc.hpp | 49 | ||||
-rw-r--r-- | utils/signals/misc_test.cpp | 133 | ||||
-rw-r--r-- | utils/signals/programmer.cpp | 138 | ||||
-rw-r--r-- | utils/signals/programmer.hpp | 63 | ||||
-rw-r--r-- | utils/signals/programmer_fwd.hpp | 49 | ||||
-rw-r--r-- | utils/signals/programmer_test.cpp | 140 | ||||
-rw-r--r-- | utils/signals/timer.cpp | 547 | ||||
-rw-r--r-- | utils/signals/timer.hpp | 86 | ||||
-rw-r--r-- | utils/signals/timer_fwd.hpp | 45 | ||||
-rw-r--r-- | utils/signals/timer_test.cpp | 426 |
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); +} |