From 3719c2fc50d33dbe407eb5351d94252406ccec9e Mon Sep 17 00:00:00 2001 From: Vernon Mauery Date: Wed, 20 Mar 2019 13:00:20 -0700 Subject: Add generic signal handling API to work with boost::asio This allows providers or the main application to handle POSIX signals using a callback chain. Each handler can return continueExecution or breakExecution to stop the signal handling chain or allow it to continue. Each handler is registered with a priority and upon reciept of a signal, each handler is executed in priority order until the end of the list is reached or one returns with breakExecution. Change-Id: Idd83625eb1a2d3bdafc92bdd839e0d6386177ff2 Signed-off-by: Vernon Mauery --- include/ipmid/api.hpp | 35 +++++++++++++++++ libipmid/Makefile.am | 1 + libipmid/signals.cpp | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 libipmid/signals.cpp diff --git a/include/ipmid/api.hpp b/include/ipmid/api.hpp index b691bed..b825796 100644 --- a/include/ipmid/api.hpp +++ b/include/ipmid/api.hpp @@ -241,3 +241,38 @@ static inline void post_work(WorkFn work) { getIoContext()->post(std::forward(work)); } + +enum class SignalResponse : int +{ + breakExecution, + continueExecution, +}; + +/** + * @brief add a signal handler + * + * This registers a handler to be called asynchronously via the execution + * queue when the specified signal is received. + * + * Priority allows a signal handler to specify what order in the handler + * chain it gets called. Lower priority numbers will cause the handler to + * be executed later in the chain, while the highest priority numbers will cause + * the handler to be executed first. + * + * In order to facilitate a chain of handlers, each handler in the chain will be + * able to return breakExecution or continueExecution. Returning breakExecution + * will break the chain and no further handlers will execute for that signal. + * Returning continueExecution will allow lower-priority handlers to execute. + * + * By default, the main asio execution loop will register a low priority + * (prioOpenBmcBase) handler for SIGINT and SIGTERM to cause the process to stop + * on either of those signals. To prevent one of those signals from causing the + * process to stop, simply register a higher priority handler that returns + * breakExecution. + * + * @param int - priority of handler + * @param int - signal number to wait for + * @param handler - the callback function to be executed + */ +void registerSignalHandler(int priority, int signalNumber, + const std::function& handler); diff --git a/libipmid/Makefile.am b/libipmid/Makefile.am index ac35bbf..e4899e1 100644 --- a/libipmid/Makefile.am +++ b/libipmid/Makefile.am @@ -13,6 +13,7 @@ pkgconfig_DATA = libipmid.pc lib_LTLIBRARIES = libipmid.la libipmid_la_SOURCES = \ sdbus-asio.cpp \ + signals.cpp \ systemintf-sdbus.cpp libipmid_la_LDFLAGS = \ $(SYSTEMD_LIBS) \ diff --git a/libipmid/signals.cpp b/libipmid/signals.cpp new file mode 100644 index 0000000..3838e95 --- /dev/null +++ b/libipmid/signals.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include + +using namespace phosphor::logging; + +namespace +{ + +class SignalHandler +{ + public: + SignalHandler(std::shared_ptr& io, int sigNum) : + signal(std::make_unique(*io, sigNum)) + { + asyncWait(); + } + + ~SignalHandler() + { + // unregister with asio to unmask the signal + signal->cancel(); + signal->clear(); + } + + void registerHandler(int prio, + const std::function& handler) + { + // check for initial placement + if (handlers.empty() || std::get<0>(handlers.front()) < prio) + { + handlers.emplace_front(std::make_tuple(prio, handler)); + return; + } + // walk the list and put it in the right place + auto j = handlers.begin(); + for (auto i = j; i != handlers.end() && std::get<0>(*i) > prio; i++) + { + j = i; + } + handlers.emplace_after(j, std::make_tuple(prio, handler)); + } + + void handleSignal(const boost::system::error_code& ec, int sigNum) + { + if (ec) + { + log("Error in common signal handler", + entry("SIGNAL=%d", sigNum), + entry("ERROR=%s", ec.message().c_str())); + return; + } + for (auto h = handlers.begin(); h != handlers.end(); h++) + { + std::function& handler = std::get<1>(*h); + if (handler(sigNum) == SignalResponse::breakExecution) + { + break; + } + } + // start the wait for the next signal + asyncWait(); + } + + protected: + void asyncWait() + { + signal->async_wait([this](const boost::system::error_code& ec, + int sigNum) { handleSignal(ec, sigNum); }); + } + + std::forward_list>> + handlers; + std::unique_ptr signal; +}; + +// SIGRTMAX is defined as a non-constexpr function call and thus cannot be used +// as an array size. Get around this by making a vector and resizing it the +// first time it is needed +std::vector> signals; + +} // namespace + +void registerSignalHandler(int priority, int signalNumber, + const std::function& handler) +{ + if (signalNumber >= SIGRTMAX) + { + return; + } + + if (signals.empty()) + { + signals.resize(SIGRTMAX); + } + + if (!signals[signalNumber]) + { + std::shared_ptr io = getIoContext(); + signals[signalNumber] = + std::make_unique(io, signalNumber); + } + signals[signalNumber]->registerHandler(priority, handler); +} -- cgit v1.2.1