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 <vernon.mauery@linux.intel.com>
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 @@
 {
     getIoContext()->post(std::forward<WorkFn>(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<SignalResponse(int)>& 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 @@
 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 <forward_list>
+#include <ipmid/api.hpp>
+#include <memory>
+#include <phosphor-logging/log.hpp>
+#include <vector>
+
+using namespace phosphor::logging;
+
+namespace
+{
+
+class SignalHandler
+{
+  public:
+    SignalHandler(std::shared_ptr<boost::asio::io_context>& io, int sigNum) :
+        signal(std::make_unique<boost::asio::signal_set>(*io, sigNum))
+    {
+        asyncWait();
+    }
+
+    ~SignalHandler()
+    {
+        // unregister with asio to unmask the signal
+        signal->cancel();
+        signal->clear();
+    }
+
+    void registerHandler(int prio,
+                         const std::function<SignalResponse(int)>& 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<level::ERR>("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<SignalResponse(int)>& 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<std::tuple<int, std::function<SignalResponse(int)>>>
+        handlers;
+    std::unique_ptr<boost::asio::signal_set> 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<std::unique_ptr<SignalHandler>> signals;
+
+} // namespace
+
+void registerSignalHandler(int priority, int signalNumber,
+                           const std::function<SignalResponse(int)>& handler)
+{
+    if (signalNumber >= SIGRTMAX)
+    {
+        return;
+    }
+
+    if (signals.empty())
+    {
+        signals.resize(SIGRTMAX);
+    }
+
+    if (!signals[signalNumber])
+    {
+        std::shared_ptr<boost::asio::io_context> io = getIoContext();
+        signals[signalNumber] =
+            std::make_unique<SignalHandler>(io, signalNumber);
+    }
+    signals[signalNumber]->registerHandler(priority, handler);
+}