Persist error d-bus objects

Use Cereal to implement serialization and de-serialization of
properties of error d-bus objects.

Serialize and persist error d-bus objects as they are put on the bus.
De-serialize and restore them (if persistent ones exist) when
phosphor-log-manager starts up.

Change-Id: I1f5df1abbe74bfdb86e3e82a78ff7115e90e2112
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 6ca02d3..29f18d6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,7 +36,8 @@
 	elog_entry.cpp \
 	org.openbmc.Associations.cpp \
 	elog-process-metadata.cpp \
-	elog_meta.cpp
+	elog_meta.cpp \
+	elog_serialize.cpp
 
 # Be sure to build needed files before compiling
 BUILT_SOURCES = \
@@ -64,7 +65,8 @@
 phosphor_log_manager_LDFLAGS = \
 		$(SYSTEMD_LIBS) \
 		$(SDBUSPLUS_LIBS) \
-		$(PHOSPHOR_DBUS_INTERFACES_LIBS)
+		$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+		-lstdc++fs
 phosphor_log_manager_CXXFLAGS = \
 		$(SYSTEMD_CFLAGS) \
 		$(SDBUSPLUS_CFLAGS) \
diff --git a/configure.ac b/configure.ac
index 7055888..977bcb0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -77,6 +77,12 @@
 AS_IF([test "x$CALLOUTS_YAML" == "x"], \
     [CALLOUTS_YAML="callouts-example.yaml"])
 
+AC_ARG_VAR(ERRLOG_PERSIST_PATH, [Path of directory housing persisted errors.])
+AS_IF([test "x$ERRLOG_PERSIST_PATH" == "x"], \
+    [ERRLOG_PERSIST_PATH="/var/lib/phosphor-logging/errors"])
+AC_DEFINE_UNQUOTED([ERRLOG_PERSIST_PATH], ["$ERRLOG_PERSIST_PATH"], \
+    [Path of directory housing persisted errors])
+
 # Compile error metadata handlers if we're asked to do so.
 AC_ARG_ENABLE([metadata-processing],
     AS_HELP_STRING([--enable-metadata-processing], [Compile metadata handlers]),
diff --git a/elog_entry.hpp b/elog_entry.hpp
index 06f6bc2..ffff8af 100644
--- a/elog_entry.hpp
+++ b/elog_entry.hpp
@@ -75,6 +75,24 @@
             this->emit_object_added();
         };
 
+        /** @brief Constructor that puts an "empty" error object on the bus,
+         *         with only the id property populated. Rest of the properties
+         *         to be set by the caller. Caller should emit the added signal.
+         *  @param[in] bus - Bus to attach to.
+         *  @param[in] path - Path to attach at.
+         *  @param[in] id - The error entry id.
+         *  @param[in] parent - The error's parent.
+         */
+        Entry(sdbusplus::bus::bus& bus,
+              const std::string& path,
+              uint32_t entryId,
+              Manager& parent) :
+              EntryIfaces(bus, path.c_str(), true),
+              parent(parent)
+        {
+            id(entryId);
+        };
+
         /** @brief Set resolution status of the error.
          *  @param[in] value - boolean indicating resolution
          *  status (true = resolved)
@@ -89,6 +107,8 @@
             return sdbusplus::xyz::openbmc_project::
                    Logging::server::Entry::resolved(value);
         }
+        using sdbusplus::xyz::openbmc_project::
+              Logging::server::Entry::resolved;
 
         /** @brief Delete this d-bus object.
          */
diff --git a/elog_serialize.cpp b/elog_serialize.cpp
new file mode 100644
index 0000000..0468e11
--- /dev/null
+++ b/elog_serialize.cpp
@@ -0,0 +1,35 @@
+#include <cereal/types/string.hpp>
+#include <cereal/types/vector.hpp>
+#include <cereal/types/tuple.hpp>
+#include <cereal/archives/binary.hpp>
+#include <fstream>
+#include "elog_serialize.hpp"
+
+namespace phosphor
+{
+namespace logging
+{
+
+fs::path serialize(const Entry& e, const fs::path& dir)
+{
+    auto path = dir / std::to_string(e.id());
+    std::ofstream os(path.c_str(), std::ios::binary);
+    cereal::BinaryOutputArchive oarchive(os);
+    oarchive(e);
+    return path;
+}
+
+bool deserialize(const fs::path& path, Entry& e)
+{
+    if (fs::exists(path))
+    {
+        std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+        cereal::BinaryInputArchive iarchive(is);
+        iarchive(e);
+        return true;
+    }
+    return false;
+}
+
+} // namespace logging
+} // namespace phosphor
diff --git a/elog_serialize.hpp b/elog_serialize.hpp
new file mode 100644
index 0000000..d1bcbfb
--- /dev/null
+++ b/elog_serialize.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <string>
+#include <vector>
+#include <experimental/filesystem>
+#include "elog_entry.hpp"
+#include "config.h"
+
+namespace phosphor
+{
+namespace logging
+{
+
+namespace fs = std::experimental::filesystem;
+
+/** @brief Function required by Cereal to perform serialization.
+ *  @tparam Archive - Cereal archive type (binary in our case).
+ *  @param[in] a - reference to Cereal archive.
+ *  @param[in] e - const reference to error entry.
+ */
+template<class Archive>
+void save(Archive& a, const Entry& e)
+{
+    a(e.id(), e.severity(), e.timestamp(),
+      e.message(), e.additionalData(), e.associations(), e.resolved());
+}
+
+/** @brief Function required by Cereal to perform deserialization.
+ *  @tparam Archive - Cereal archive type (binary in our case).
+ *  @param[in] a - reference to Cereal archive.
+ *  @param[in] e - reference to error entry.
+ */
+template<class Archive>
+void load(Archive& a, Entry& e)
+{
+    using namespace
+        sdbusplus::xyz::openbmc_project::Logging::server;
+
+    uint32_t id{};
+    Entry::Level severity{};
+    uint64_t timestamp{};
+    std::string message{};
+    std::vector<std::string> additionalData{};
+    bool resolved{};
+    AssociationList associations{};
+
+    a(id, severity, timestamp, message,
+      additionalData, associations, resolved);
+
+    e.id(id);
+    e.severity(severity);
+    e.timestamp(timestamp);
+    e.message(message);
+    e.additionalData(additionalData);
+    e.sdbusplus::xyz::openbmc_project::
+        Logging::server::Entry::resolved(resolved);
+    e.associations(associations);
+}
+
+/** @brief Serialize and persist error d-bus object
+ *  @param[in] a - const reference to error entry.
+ *  @param[in] dir - pathname of directory where the serialized error will
+ *                   be placed.
+ *  @return fs::path - pathname of persisted error file
+ */
+fs::path serialize(const Entry& e,
+                   const fs::path& dir = fs::path(ERRLOG_PERSIST_PATH));
+
+/** @brief Deserialze a persisted error into a d-bus object
+ *  @param[in] path - pathname of persisted error file
+ *  @param[in] e - reference to error object which is the target of
+ *             deserialization.
+ *  @return bool - true if the deserialization was successful, false otherwise.
+ */
+bool deserialize(const fs::path& path, Entry& e);
+
+} // namespace logging
+} // namespace phosphor
diff --git a/log_manager.cpp b/log_manager.cpp
index 8ab495b..3f61e91 100644
--- a/log_manager.cpp
+++ b/log_manager.cpp
@@ -14,6 +14,7 @@
 #include <phosphor-logging/log.hpp>
 #include "log_manager.hpp"
 #include "elog_meta.hpp"
+#include "elog_serialize.hpp"
 
 using namespace phosphor::logging;
 extern const std::map<metadata::Metadata,
@@ -145,17 +146,18 @@
     {
         reqLevel = levelmap->second;
     }
-    entries.insert(std::make_pair(entryId, std::make_unique<Entry>(
-            busLog,
-            objPath,
-            entryId,
-            ms, // Milliseconds since 1970
-            static_cast<Entry::Level>(reqLevel),
-            std::move(errMsg),
-            std::move(additionalData),
-            std::move(objects),
-            *this)));
-    return;
+    auto e = std::make_unique<Entry>(
+                 busLog,
+                 objPath,
+                 entryId,
+                 ms, // Milliseconds since 1970
+                 static_cast<Entry::Level>(reqLevel),
+                 std::move(errMsg),
+                 std::move(additionalData),
+                 std::move(objects),
+                 *this);
+    serialize(*e);
+    entries.insert(std::make_pair(entryId, std::move(e)));
 }
 
 void Manager::processMetadata(const std::string& errorName,
@@ -188,5 +190,35 @@
     }
 }
 
+void Manager::restore()
+{
+    std::vector<uint32_t> errorIds;
+
+    fs::path dir(ERRLOG_PERSIST_PATH);
+    if (!fs::exists(dir) || fs::is_empty(dir))
+    {
+        return;
+    }
+
+    for(auto& file: fs::directory_iterator(dir))
+    {
+        auto id = file.path().filename().c_str();
+        auto idNum = std::stol(id);
+        auto e = std::make_unique<Entry>(
+                     busLog,
+                     std::string(OBJ_ENTRY) + '/' + id,
+                     idNum,
+                     *this);
+        if (deserialize(file.path(), *e))
+        {
+            e->emit_object_added();
+            entries.insert(std::make_pair(idNum, std::move(e)));
+            errorIds.push_back(idNum);
+        }
+    }
+
+    entryId = *(std::max_element(errorIds.begin(), errorIds.end()));
+}
+
 } // namespace logging
 } // namepsace phosphor
diff --git a/log_manager.hpp b/log_manager.hpp
index 71b6e71..730f63e 100644
--- a/log_manager.hpp
+++ b/log_manager.hpp
@@ -67,6 +67,11 @@
          */
         void erase(uint32_t entryId);
 
+        /** @brief Construct error d-bus objects from their persisted
+         *         representations.
+         */
+        void restore();
+
     private:
         /** @brief Call metadata handler(s), if any. Handlers may create
          *         associations.
diff --git a/log_manager_main.cpp b/log_manager_main.cpp
index a3b26a3..b04c0e3 100644
--- a/log_manager_main.cpp
+++ b/log_manager_main.cpp
@@ -1,5 +1,6 @@
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/manager.hpp>
+#include <experimental/filesystem>
 #include "config.h"
 #include "log_manager.hpp"
 
@@ -12,6 +13,12 @@
 
     phosphor::logging::Manager manager(bus, OBJ_INTERNAL);
 
+    // Create a directory to persist errors.
+    std::experimental::filesystem::create_directories(ERRLOG_PERSIST_PATH);
+
+    // Recreate error d-bus objects from persisted errors.
+    manager.restore();
+
     bus.request_name(BUSNAME_LOGGING);
 
     while(true)