Add manager skeleton

Add stubbed Notify implementation and register for generated
signal callbacks.

Add a unit test; which, at this point does little more than
verify we don't coredump on startup.

Change-Id: I0cda71935947c0d082612a5c52e2b7eba98516ab
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/Makefile.am b/Makefile.am
index 3c1e3c8..9bc1cf2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,4 +1,10 @@
 sbin_PROGRAMS = phosphor-inventory
 
 phosphor_inventory_SOURCES = \
-	app.cpp
+	app.cpp \
+	server.cpp \
+	manager.cpp
+phosphor_inventory_LDFLAGS = $(SYSTEMD_LIBS)
+phosphor_inventory_CFLAGS = $(SYSTEMD_CFLAGS)
+
+SUBDIRS = test
diff --git a/app.cpp b/app.cpp
index bcef3eb..6b6f562 100644
--- a/app.cpp
+++ b/app.cpp
@@ -13,13 +13,28 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include "config.h"
+#include "manager.hpp"
+#include <sdbusplus/bus.hpp>
 #include <cstdlib>
+#include <iostream>
+#include <exception>
 
 int main(int argc, char *argv[])
 {
-    // TOOD
-
-    exit(EXIT_SUCCESS);
+    try {
+        auto manager = phosphor::inventory::manager::Manager(
+                sdbusplus::bus::new_system(),
+                BUSNAME,
+                INVENTORY_ROOT,
+                IFACE);
+        manager.run();
+        exit(EXIT_SUCCESS);
+    }
+    catch (const std::exception &e) {
+        std::cerr << e.what() << std::endl;
+    }
+    exit(EXIT_FAILURE);
 }
 
 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/configure.ac b/configure.ac
index 4496633..936bdec 100644
--- a/configure.ac
+++ b/configure.ac
@@ -11,6 +11,13 @@
 AC_PROG_INSTALL
 AC_PROG_MAKE_SET
 
+# Checks for libraries.
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221])
+
+# Checks for header files.
+AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])])
+AC_CHECK_HEADER(sdbusplus/server.hpp, ,[AC_MSG_ERROR([Could not find sdbusplus/server.hpp...sdbusplus developement package required])])
+
 # Checks for typedefs, structures, and compiler characteristics.
 AX_CXX_COMPILE_STDCXX_14([noext])
 AX_APPEND_COMPILE_FLAGS([-fpic -Wall -Werror], [CXXFLAGS])
@@ -18,6 +25,16 @@
 # Checks for library functions.
 LT_INIT # Removes 'unrecognized options: --with-libtool-sysroot'
 
+AC_ARG_VAR(BUSNAME, [The DBus busname to own.])
+AC_ARG_VAR(INVENTORY_ROOT, [The DBus inventory namespace root.])
+AC_ARG_VAR(IFACE, [The manager DBus interface.])
+AS_IF([test "x$BUSNAME" == "x"], [BUSNAME="xyz.openbmc_project.Inventory.Manager"])
+AS_IF([test "x$INVENTORY_ROOT" == "x"], [INVENTORY_ROOT="/xyz/openbmc_project/Inventory"])
+AS_IF([test "x$IFACE" == "x"], [IFACE="xyz.openbmc_project.Inventory.Manager"])
+AC_DEFINE_UNQUOTED([BUSNAME], ["$BUSNAME"], [The DBus busname to own.])
+AC_DEFINE_UNQUOTED([INVENTORY_ROOT], ["$INVENTORY_ROOT"], [The DBus inventory namespace root.])
+AC_DEFINE_UNQUOTED([IFACE], ["$IFACE"], [The manager DBus interface.])
+
 # Create configured output
-AC_CONFIG_FILES([Makefile])
+AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/manager.cpp b/manager.cpp
new file mode 100644
index 0000000..36d47a6
--- /dev/null
+++ b/manager.cpp
@@ -0,0 +1,164 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <iostream>
+#include <exception>
+#include "manager.hpp"
+
+namespace phosphor
+{
+namespace inventory
+{
+namespace manager
+{
+namespace details
+{
+
+/** @brief Fowrarding signal callback.
+ *
+ *  Extracts per-signal specific context and forwards the call to the manager
+ *  instance.
+ */
+auto _signal(sd_bus_message *m, void *data, sd_bus_error *e) noexcept
+{
+    try {
+        auto msg = sdbusplus::message::message(m);
+        auto &args = *static_cast<Manager::SigArg*>(data);
+        sd_bus_message_ref(m);
+        auto &mgr = *std::get<0>(args);
+        mgr.signal(msg, *std::get<1>(args));
+    }
+    catch (const std::exception &e) {
+        std::cerr << e.what() << std::endl;
+    }
+
+    return 0;
+}
+
+} // namespace details
+
+Manager::Manager(
+        sdbusplus::bus::bus &&bus,
+        const char *busname,
+        const char *root,
+        const char *iface) :
+    sdbusplus::server::xyz::openbmc_project::Inventory::Manager(bus, root),
+    _shutdown(false),
+    _root(root),
+    _bus(std::move(bus)),
+    _manager(sdbusplus::server::manager::manager(_bus, root))
+{
+    for (auto &x: _events) {
+        // Create a callback context for each event.
+        _sigargs.emplace_back(
+                std::make_unique<SigArg>(
+                    std::make_tuple(
+                        this,
+                        &x.second)));
+        // Register our callback and the context for
+        // each event.
+        _matches.emplace_back(
+                sdbusplus::server::match::match(
+                    _bus,
+                    std::get<0>(x.second),
+                    details::_signal,
+                    _sigargs.back().get()));
+    }
+
+    _bus.request_name(busname);
+}
+
+void Manager::shutdown() noexcept
+{
+    _shutdown = true;
+}
+
+void Manager::run() noexcept
+{
+    while(!_shutdown) {
+        try {
+            _bus.process_discard();
+            _bus.wait(5000000);
+        }
+        catch (const std::exception &e) {
+            std::cerr << e.what() << std::endl;
+        }
+    }
+}
+
+void Manager::notify(std::string path, Object object)
+{
+    try {
+        using MakerType = HolderPtr(*)(
+                sdbusplus::bus::bus &, const char *);
+        using Makers = std::map<std::string, MakerType>;
+
+        if(object.cbegin() == object.cend())
+            throw std::runtime_error(
+                    "No interfaces in " + path);
+
+        static const Makers makers{
+            // TODO - Add mappings here.
+        };
+
+        path.insert(0, _root);
+
+        auto obj = _refs.find(path);
+        if(obj != _refs.end())
+            throw std::runtime_error(
+                    obj->first + " already exists");
+
+        // Create an interface holder for each interface
+        // provided by the client and group them into
+        // a container.
+        InterfaceComposite ref;
+
+        for (auto &x: object) {
+            auto maker = makers.find(x.first.c_str());
+
+            if(maker == makers.end())
+                throw std::runtime_error(
+                        "Unimplemented interface: " + x.first);
+
+            ref.emplace(
+                    std::make_pair(
+                        x.first,
+                        (maker->second)(_bus, path.c_str())));
+        }
+
+        // Hang on to a reference to the object (interfaces)
+        // so it stays on the bus, and so we can make calls
+        // to it if needed.
+        _refs.emplace(
+                std::make_pair(
+                    path, std::move(ref)));
+    }
+    catch (const std::exception &e) {
+        std::cerr << e.what() << std::endl;
+    }
+}
+
+void Manager::signal(sdbusplus::message::message &msg, auto &args)
+{
+    // TODO - unstub
+}
+
+#include "generated.hpp"
+
+} // namespace manager
+} // namespace inventory
+} // namespace phosphor
+
+// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/manager.hpp b/manager.hpp
new file mode 100644
index 0000000..9b8338b
--- /dev/null
+++ b/manager.hpp
@@ -0,0 +1,168 @@
+#pragma once
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/Inventory/Manager/server.hpp>
+
+namespace phosphor
+{
+namespace inventory
+{
+namespace manager
+{
+namespace details
+{
+namespace interface
+{
+namespace holder
+{
+
+/** @struct Base
+ *  @brief sdbusplus server interface holder base.
+ *
+ *  Provides a common type for assembling containers of sdbusplus server
+ *  interfaces.  Typically a multiple inheritance scheme (sdbusplus::object)
+ *  would be used for this; however, for objects created by PIM the interfaces
+ *  are not known at build time.
+ */
+struct Base
+{
+    Base() = default;
+    virtual ~Base() = default;
+    Base(const Base&) = delete;
+    Base& operator=(const Base&) = delete;
+    Base(Base&&) = default;
+    Base& operator=(Base&&) = default;
+};
+
+/** @struct Holder
+ *  @brief sdbusplus server interface holder.
+ *
+ *  Holds a pointer to an sdbusplus server interface instance.
+ *
+ *  @tparam T - The sdbusplus server interface type to hold.
+ */
+template <typename T>
+struct Holder final : public Base
+{
+    Holder() = delete;
+    ~Holder() = default;
+    Holder(const Holder&) = delete;
+    Holder& operator=(const Holder&) = delete;
+    Holder(Holder&&) = default;
+    Holder& operator=(Holder&&) = default;
+    explicit Holder(auto &&ptr) noexcept : _ptr(std::move(ptr)) {}
+
+    /** @brief sdbusplus server interface holder factory method.
+     *
+     *  @param bus[in] - An sdbusplus bus connection
+     *  @param bus[in] - The path of the object for which
+     *          an interface is to be held.
+     *  @returns A held interface.
+     */
+    static auto make(sdbusplus::bus::bus &bus, const char *path)
+    {
+        return static_cast<std::unique_ptr<Base>>(
+                std::make_unique<Holder<T>>(
+                    std::make_unique<T>(bus, path)));
+    }
+
+    private:
+    std::unique_ptr<T> _ptr;
+};
+
+} // namespace holder
+} // namespace interface
+} // namespace details
+
+/** @class Manager
+ *  @brief OpenBMC inventory manager implementation.
+ *
+ *  A concrete implementation for the xyz.openbmc_project.Inventory.Manager
+ *  DBus API.
+ */
+class Manager final :
+    public sdbusplus::server::xyz::openbmc_project::Inventory::Manager
+{
+    public:
+    Manager() = delete;
+    Manager(const Manager&) = delete;
+    Manager& operator=(const Manager&) = delete;
+    Manager(Manager&&) = default;
+    Manager& operator=(Manager&&) = default;
+    ~Manager() = default;
+
+    /** @brief Construct an inventory manager.
+     *
+     *  @param[in] bus - An sdbusplus bus connection.
+     *  @param[in] busname - The DBus busname to own.
+     *  @param[in] root - The DBus path on which to implement
+     *      an inventory manager.
+     *  @param[in] iface - The DBus inventory interface to implement.
+     */
+    Manager(sdbusplus::bus::bus &&, const char *, const char*, const char*);
+
+    using Object = std::map<
+        std::string, std::map<
+            std::string, sdbusplus::message::variant<std::string>>>;
+
+    /** @brief Start processing DBus messages. */
+    void run() noexcept;
+
+    /** @brief Provided for testing only. */
+    void shutdown() noexcept;
+
+    /** @brief sd_bus Notify method implementation callback. */
+    void notify(std::string path, Object) override;
+
+    /** @brief sd_bus signal callback. */
+    void signal(sdbusplus::message::message &, auto &);
+
+    using Event = std::tuple<const char *>;
+    using SigArgs = std::vector<
+        std::unique_ptr<
+            std::tuple<
+                Manager *,
+                const Event *>>>;
+    using SigArg = SigArgs::value_type::element_type;
+
+    private:
+    using Holder = details::interface::holder::Base;
+    using HolderPtr = std::unique_ptr<Holder>;
+    using InterfaceComposite = std::map<std::string, HolderPtr>;
+    using ObjectReferences = std::map<std::string, InterfaceComposite>;
+    using Events = std::map<const char *, Event>;
+
+    /** @brief Provided for testing only. */
+    bool _shutdown;
+
+    /** @brief Path prefix applied to any relative paths. */
+    const char * _root;
+
+    /** @brief A container of sdbusplus server interface references. */
+    ObjectReferences _refs;
+
+    /** @brief A container contexts for signal callbacks. */
+    SigArgs _sigargs;
+
+    /** @brief A container of sdbusplus signal matches.  */
+    std::vector<sdbusplus::server::match::match> _matches;
+
+    /** @brief Persistent sdbusplus DBus bus connection. */
+    sdbusplus::bus::bus _bus;
+
+    /** @brief sdbusplus org.freedesktop.DBus.ObjectManager reference. */
+    sdbusplus::server::manager::manager _manager;
+
+    /** @brief A container of pimgen generated events and responses.  */
+    static const Events _events;
+};
+
+} // namespace manager
+} // namespace inventory
+} // namespace phosphor
+
+// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
diff --git a/pimgen.py b/pimgen.py
index f59e320..15c91f6 100755
--- a/pimgen.py
+++ b/pimgen.py
@@ -55,11 +55,11 @@
 
         fd.write('    {\n')
         fd.write('        "%s",\n' % self.name)
-        fd.write('        {\n')
+        fd.write('        std::make_tuple(\n')
         for s in sig:
             fd.write('            %s' % s)
-        fd.write(',\n')
-        fd.write('        },\n')
+        fd.write('\n')
+        fd.write('        ),\n')
         fd.write('    },\n')
 
 
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..4026307
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,8 @@
+check_PROGRAMS = phosphor-inventory-test
+
+phosphor_inventory_test_SOURCES = \
+	test.cpp
+phosphor_inventory_test_LDFLAGS = $(SYSTEMD_LIBS)
+phosphor_inventory_test_CFLAGS = $(SYSTEMD_CFLAGS)
+phosphor_inventory_test_LDADD = ${top_builddir}/manager.o \
+				${top_builddir}/server.o
diff --git a/test/test.cpp b/test/test.cpp
new file mode 100644
index 0000000..350684b
--- /dev/null
+++ b/test/test.cpp
@@ -0,0 +1,77 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "manager.hpp"
+#include "../config.h"
+#include <cassert>
+
+constexpr auto SERVICE = "phosphor.inventory.test";
+constexpr auto INTERFACE = IFACE;
+constexpr auto ROOT = "/testing/inventory";
+
+auto server_thread(void *data)
+{
+    auto mgr = static_cast<phosphor::inventory::manager::Manager*>(data);
+
+    mgr->run();
+
+    return static_cast<void *>(nullptr);
+}
+
+void runTests(phosphor::inventory::manager::Manager &mgr)
+{
+    auto b = sdbusplus::bus::new_default();
+
+    // make sure the notify method works
+    {
+        auto m = b.new_method_call(SERVICE, ROOT, INTERFACE, "Notify");
+        m.append("/foo");
+
+        using var = sdbusplus::message::variant<std::string>;
+        using inner = std::map<std::string, var>;
+        using outer = std::map<std::string, inner>;
+
+        inner i = {{"test.property", "a"}};
+        outer o = {{"test.iface", i}};
+
+        m.append(o);
+        auto reply = b.call(m);
+        auto cleanup = sdbusplus::message::message(reply);
+        assert(sd_bus_message_get_errno(reply) == 0);
+    }
+
+    mgr.shutdown();
+}
+
+int main()
+{
+    auto mgr = phosphor::inventory::manager::Manager(
+            sdbusplus::bus::new_system(),
+            SERVICE, ROOT, INTERFACE);
+
+    pthread_t t;
+    {
+        pthread_create(&t, NULL, server_thread, &mgr);
+    }
+
+    runTests(mgr);
+
+    // Wait for server thread to exit.
+    pthread_join(t, NULL);
+
+    return 0;
+}
+
+// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4