Add multi-host support

This refactoring includes:
- added multi-host mode support;
- added support for graceful shutdown of the service;
- added support to flush the log buffer as it fills;
- D-Bus service xyz.openbmc_project.HostLogger replaced with SIGUSR1
  signal handler;
- self diagnostic messages now registered via phosphor-logging;
- added unit tests;
- build system migrated from autotools to meson;
- source code aligned with OpenBMC conventions.

Change-Id: If6c1dfde278af685d8563450543a6587a282c7e4
Signed-off-by: Artem Senichev <a.senichev@yadro.com>
diff --git a/src/dbus_loop.cpp b/src/dbus_loop.cpp
new file mode 100644
index 0000000..e628d24
--- /dev/null
+++ b/src/dbus_loop.cpp
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (C) 2020 YADRO
+
+#include "dbus_loop.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+#include <system_error>
+
+using namespace phosphor::logging;
+
+DbusLoop::DbusLoop() : bus(nullptr), event(nullptr)
+{
+    int rc;
+
+    rc = sd_bus_default(&bus);
+    if (rc < 0)
+    {
+        std::error_code ec(-rc, std::generic_category());
+        throw std::system_error(ec, "Unable to initiate D-Bus connection");
+    }
+
+    rc = sd_event_default(&event);
+    if (rc < 0)
+    {
+        sd_bus_unref(bus);
+        std::error_code ec(-rc, std::generic_category());
+        throw std::system_error(ec, "Unable to create D-Bus event loop");
+    }
+
+    rc = sd_bus_attach_event(bus, event, SD_EVENT_PRIORITY_NORMAL);
+    if (rc < 0)
+    {
+        sd_bus_unref(bus);
+        sd_event_unref(event);
+        std::error_code ec(-rc, std::generic_category());
+        throw std::system_error(ec, "Unable to attach D-Bus event");
+    }
+}
+
+DbusLoop::~DbusLoop()
+{
+    sd_bus_unref(bus);
+    sd_event_unref(event);
+}
+
+int DbusLoop::run() const
+{
+    return sd_event_loop(event);
+}
+
+void DbusLoop::stop(int code) const
+{
+    sd_event_exit(event, code);
+}
+
+void DbusLoop::addPropertyHandler(const std::string& objPath,
+                                  const WatchProperties& props,
+                                  std::function<void()> callback)
+{
+    // Add match handler
+    const int rc = sd_bus_match_signal(bus, nullptr, nullptr, objPath.c_str(),
+                                       "org.freedesktop.DBus.Properties",
+                                       "PropertiesChanged", msgCallback, this);
+    if (rc < 0)
+    {
+        std::error_code ec(-rc, std::generic_category());
+        throw std::system_error(ec, "Unable to register property watcher");
+    }
+
+    propWatch = props;
+    propHandler = callback;
+}
+
+void DbusLoop::addIoHandler(int fd, std::function<void()> callback)
+{
+    ioHandler = callback;
+    const int rc = sd_event_add_io(event, nullptr, fd, EPOLLIN,
+                                   &DbusLoop::ioCallback, this);
+    if (rc < 0)
+    {
+        std::error_code ec(-rc, std::generic_category());
+        throw std::system_error(ec, "Unable to register IO handler");
+    }
+}
+
+void DbusLoop::addSignalHandler(int signal, std::function<void()> callback)
+{
+    // Block the signal
+    sigset_t ss;
+    if (sigemptyset(&ss) < 0 || sigaddset(&ss, signal) < 0 ||
+        sigprocmask(SIG_BLOCK, &ss, nullptr) < 0)
+    {
+        std::error_code ec(errno, std::generic_category());
+        std::string err = "Unable to block signal ";
+        err += strsignal(signal);
+        throw std::system_error(ec, err);
+    }
+
+    signalHandlers.insert(std::make_pair(signal, callback));
+
+    // Register handler
+    const int rc = sd_event_add_signal(event, nullptr, signal,
+                                       &DbusLoop::signalCallback, this);
+    if (rc < 0)
+    {
+        std::error_code ec(-rc, std::generic_category());
+        std::string err = "Unable to register handler for signal ";
+        err += strsignal(signal);
+        throw std::system_error(ec, err);
+    }
+}
+
+int DbusLoop::msgCallback(sd_bus_message* msg, void* userdata,
+                          sd_bus_error* /*err*/)
+{
+    const WatchProperties& propWatch =
+        static_cast<DbusLoop*>(userdata)->propWatch;
+
+    try
+    {
+        int rc;
+
+        // Filter out by interface name
+        const char* interface;
+        rc = sd_bus_message_read(msg, "s", &interface);
+        if (rc < 0)
+        {
+            std::error_code ec(-rc, std::generic_category());
+            throw std::system_error(ec, "Unable to read interface name");
+        }
+        const auto& itIface = propWatch.find(interface);
+        if (itIface == propWatch.end())
+        {
+            return 0; // Interface is now watched
+        }
+        const Properties& props = itIface->second;
+
+        // Read message: go through list of changed properties
+        rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_ARRAY, "{sv}");
+        if (rc < 0)
+        {
+            std::error_code ec(-rc, std::generic_category());
+            throw std::system_error(ec, "Unable to open message container");
+        }
+        while ((rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_DICT_ENTRY,
+                                                    "sv")) > 0)
+        {
+            // Get property's name
+            const char* name;
+            rc = sd_bus_message_read(msg, "s", &name);
+            if (rc < 0)
+            {
+                sd_bus_message_exit_container(msg);
+                std::error_code ec(-rc, std::generic_category());
+                throw std::system_error(ec, "Unable to get property name");
+            }
+
+            // Get and check property's type
+            const char* type;
+            rc = sd_bus_message_peek_type(msg, nullptr, &type);
+            if (rc < 0 || strcmp(type, "s"))
+            {
+                sd_bus_message_exit_container(msg);
+                continue;
+            }
+
+            // Get property's value
+            const char* value;
+            rc = sd_bus_message_enter_container(msg, SD_BUS_TYPE_VARIANT, type);
+            if (rc < 0)
+            {
+                sd_bus_message_exit_container(msg);
+                std::error_code ec(-rc, std::generic_category());
+                throw std::system_error(ec, "Unable to open property value");
+            }
+            rc = sd_bus_message_read(msg, type, &value);
+            if (rc < 0)
+            {
+                sd_bus_message_exit_container(msg);
+                sd_bus_message_exit_container(msg);
+                std::error_code ec(-rc, std::generic_category());
+                throw std::system_error(ec, "Unable to get property value");
+            }
+            sd_bus_message_exit_container(msg);
+
+            // Check property name/value and handle the match
+            const auto& itProps = props.find(name);
+            if (itProps != props.end() &&
+                itProps->second.find(value) != itProps->second.end())
+            {
+                static_cast<DbusLoop*>(userdata)->propHandler();
+            }
+
+            sd_bus_message_exit_container(msg);
+        }
+        sd_bus_message_exit_container(msg);
+    }
+    catch (const std::exception& ex)
+    {
+        log<level::WARNING>(ex.what());
+    }
+
+    return 0;
+}
+
+int DbusLoop::signalCallback(sd_event_source* /*src*/,
+                             const struct signalfd_siginfo* si, void* userdata)
+{
+    DbusLoop* instance = static_cast<DbusLoop*>(userdata);
+    const auto it = instance->signalHandlers.find(si->ssi_signo);
+    if (it != instance->signalHandlers.end())
+    {
+        it->second();
+    }
+    else
+    {
+        std::string msg = "Unhandled signal ";
+        msg += strsignal(si->ssi_signo);
+        log<level::WARNING>(msg.c_str());
+    }
+    return 0;
+}
+
+int DbusLoop::ioCallback(sd_event_source* /*src*/, int /*fd*/,
+                         uint32_t /*revents*/, void* userdata)
+{
+    static_cast<DbusLoop*>(userdata)->ioHandler();
+    return 0;
+}