diff options
Diffstat (limited to 'utils/signals/interrupts.cpp')
-rw-r--r-- | utils/signals/interrupts.cpp | 309 |
1 files changed, 309 insertions, 0 deletions
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); +} |