inventory: implement serialization

Use Cereal to serialize and persist inventory items to the filesystem.
Serialize inventory objects as and when they're created/updated via the
notify() API.

Create a template API to perform serialization on the sdbusplus
inventory interface type.

An inventory item path /foo/bar/baz implementing interfaces iface1 and
iface2 would be stored in paths /foo/bar/baz/iface1 and
/foo/bar/baz/iface2.

Change-Id: I9a175185eac1740d6f2ca86a3ee13457edfc8ea9
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 769592d..9c44d68 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,7 +20,8 @@
 libmanagercommon_la_LIBADD = \
 	$(SDBUSPLUS_LIBS) \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
-	$(PHOSPHOR_LOGGING_LIBS)
+	$(PHOSPHOR_LOGGING_LIBS) \
+	-lstdc++fs
 libmanagercommon_la_CXXFLAGS = \
 	$(SDBUSPLUS_CFLAGS) \
 	$(PHOSPHOR_DBUS_INTERACES_CFLAGS) \
diff --git a/configure.ac b/configure.ac
index 2746d71..27a6082 100644
--- a/configure.ac
+++ b/configure.ac
@@ -76,6 +76,12 @@
 AC_DEFINE_UNQUOTED([INVENTORY_ROOT], ["$INVENTORY_ROOT"], [The DBus inventory namespace root.])
 AC_DEFINE_UNQUOTED([IFACE], ["$IFACE"], [The manager DBus interface.])
 
+AC_ARG_VAR(PIM_PERSIST_PATH, [Path of directory housing persisted inventory.])
+AS_IF([test "x$PIM_PERSIST_PATH" == "x"], \
+    [PIM_PERSIST_PATH="/var/lib/phosphor-inventory-manager"])
+AC_DEFINE_UNQUOTED([PIM_PERSIST_PATH], ["$PIM_PERSIST_PATH"], \
+    [Path of directory housing persisted inventory])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile.extra],
     [${srcdir}/generate_makefile.sh $yaml > Makefile.extra],
diff --git a/generated.mako.cpp b/generated.mako.cpp
index 7a9a38e..7eeb8c4 100644
--- a/generated.mako.cpp
+++ b/generated.mako.cpp
@@ -7,6 +7,7 @@
 % for i in interfaces:
 #include <${i.header()}>
 % endfor
+#include "gen_serialization.hpp"
 
 namespace phosphor
 {
@@ -27,7 +28,10 @@
                     ${i.namespace()}>>::make,
             MakeInterface<
                 ServerObject<
-                    ${i.namespace()}>>::assign
+                    ${i.namespace()}>>::assign,
+            MakeInterface<
+                ServerObject<
+                    ${i.namespace()}>>::serialize
         )
     },
 % endfor
diff --git a/manager.cpp b/manager.cpp
index 97d629b..86b1757 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -200,6 +200,8 @@
                 auto& assign = std::get<AssignerType>(opsit->second);
                 assign(ifaceit->second, refaceit->second);
             }
+            auto& serialize = std::get<SerializerType>(opsit->second);
+            serialize(path, ifaceit->first, refaceit->second);
         }
         catch (const InterfaceError& e)
         {
diff --git a/manager.hpp b/manager.hpp
index 4f146cb..a94d5ea 100644
--- a/manager.hpp
+++ b/manager.hpp
@@ -9,6 +9,7 @@
 #include "events.hpp"
 #include "functor.hpp"
 #include "types.hpp"
+#include "serialize.hpp"
 
 namespace phosphor
 {
@@ -85,6 +86,14 @@
                 p.first, convertVariant<PropertiesVariantType<T>>(p.second));
         }
     }
+
+    static void serialize(const std::string& path, const std::string& iface,
+                          const any_ns::any& holder)
+    {
+        const auto& object =
+            *any_ns::any_cast<const std::shared_ptr<T> &>(holder);
+        cereal::serialize(path, iface, object);
+    }
 };
 
 /** @class Manager
@@ -190,7 +199,10 @@
                           decltype(MakeInterface<int>::make) >;
         using AssignerType = std::add_pointer_t <
                              decltype(MakeInterface<int>::assign) >;
-        using Makers = std::map<std::string, std::tuple<MakerType, AssignerType>>;
+        using SerializerType = std::add_pointer_t <
+                               decltype(MakeInterface<int>::serialize) >;
+        using Makers = std::map<std::string,
+                           std::tuple<MakerType, AssignerType, SerializerType>>;
 
         /** @brief Provides weak references to interface holders.
          *
diff --git a/serialize.hpp b/serialize.hpp
new file mode 100644
index 0000000..281f61c
--- /dev/null
+++ b/serialize.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <cereal/archives/json.hpp>
+#include <experimental/filesystem>
+#include <fstream>
+#include "config.h"
+
+namespace cereal
+{
+
+namespace fs = std::experimental::filesystem;
+
+using Path = std::string;
+using Interface = std::string;
+
+/** @brief Serialize inventory item
+ *
+ *  @param[in] path - DBus object path
+ *  @param[in] iface - Inventory interface name
+ *  @param[in] object - Object to be serialized
+ */
+template <typename T>
+inline void serialize(const Path& path, const Interface& iface, const T& object)
+{
+    fs::path p(PIM_PERSIST_PATH);
+    p /= path;
+    fs::create_directories(p);
+    p /= iface;
+    std::ofstream os(p, std::ios::binary);
+    cereal::JSONOutputArchive oarchive(os);
+    oarchive(object);
+}
+
+} // namespace cereal