Merge "Use s.c_str() in log messages"
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..1719d45
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1,46 @@
+How to use this list:
+    Find the most specific section entry (described below) that matches where
+    your change lives and add the reviewers (R) and maintainers (M) as
+    reviewers. You can use the same method to track down who knows a particular
+    code base best.
+
+    Your change/query may span multiple entries; that is okay.
+
+    If you do not find an entry that describes your request at all, someone
+    forgot to update this list; please at least file an issue or send an email
+    to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+    Section entries are structured according to the following scheme:
+
+    X:  NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+    X:  ...
+    .
+    .
+    .
+
+    Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+    organization; FILE_PATH is a file path within the repository, possibly with
+    wildcards; X is a tag of one of the following types:
+
+    M:  Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+        if omitted from an entry, assume one of the maintainers from the
+        MAINTAINERS entry.
+    R:  Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+        these people are to be added as reviewers for a change matching the repo
+        path.
+    F:  Denotes forked from an external repository; has fields URL.
+
+    Line comments are to be denoted "# SOME COMMENT" (typical shell style
+    comment); it is important to follow the correct syntax and semantics as we
+    may want to use automated tools with this file in the future.
+
+    A change cannot be added to an OpenBMC repository without a MAINTAINER's
+    approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+M:  Deepak Kodihalli <dkodihal@linux.vnet.ibm.com> <dkodihal!>
+M:  Matt Spinler <spinler@us.ibm.com> <mspinler[m]!>
diff --git a/Makefile.am b/Makefile.am
index 5cc1a1f..1b2bbcd 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,6 +3,7 @@
 sbin_PROGRAMS = ibm-log-manager
 
 ibm_log_manager_SOURCES = \
+	callout.cpp \
 	dbus.cpp \
 	main.cpp \
 	manager.cpp \
diff --git a/callout.cpp b/callout.cpp
new file mode 100644
index 0000000..7c0310e
--- /dev/null
+++ b/callout.cpp
@@ -0,0 +1,185 @@
+/** Copyright © 2018 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 <cereal/archives/binary.hpp>
+#include <cereal/types/string.hpp>
+#include <cereal/types/vector.hpp>
+#include <cereal/types/tuple.hpp>
+#include <experimental/filesystem>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include "callout.hpp"
+#include "config.h"
+#include "dbus.hpp"
+
+CEREAL_CLASS_VERSION(ibm::logging::Callout, CALLOUT_CLASS_VERSION);
+
+namespace ibm
+{
+namespace logging
+{
+
+using namespace phosphor::logging;
+
+/**
+ * Function required by Cereal for saving data
+ *
+ * @param[in] archive - the Cereal archive object
+ * @param[in] callout - the object to save
+ * @param[in] version - the version of the persisted data
+ */
+template <class Archive>
+void save(Archive& archive, const Callout& callout, const std::uint32_t version)
+{
+    archive(callout.id(), callout.ts(), callout.path(), callout.buildDate(),
+            callout.manufacturer(), callout.model(), callout.partNumber(),
+            callout.serialNumber());
+}
+
+/**
+ * Function required by Cereal for restoring data into an object
+ *
+ * @param[in] archive - the Cereal archive object
+ * @param[in] callout - the callout object to restore
+ * @param[in] version - the version of the persisted data
+ */
+template <class Archive>
+void load(Archive& archive, Callout& callout, const std::uint32_t version)
+{
+    size_t id;
+    uint64_t timestamp;
+    std::string inventoryPath;
+    std::string build;
+    std::string mfgr;
+    std::string model;
+    std::string pn;
+    std::string sn;
+
+    archive(id, timestamp, inventoryPath, build, mfgr, model, pn, sn);
+
+    callout.id(id);
+    callout.ts(timestamp);
+    callout.path(inventoryPath);
+    callout.buildDate(build);
+    callout.manufacturer(mfgr);
+    callout.model(model);
+    callout.partNumber(pn);
+    callout.serialNumber(sn);
+}
+
+Callout::Callout(sdbusplus::bus::bus& bus, const std::string& objectPath,
+                 size_t id, uint64_t timestamp) :
+    CalloutObject(bus, objectPath.c_str(), true),
+    entryID(id), timestamp(timestamp)
+{
+}
+
+Callout::Callout(sdbusplus::bus::bus& bus, const std::string& objectPath,
+                 const std::string& inventoryPath, size_t id,
+                 uint64_t timestamp, const DbusPropertyMap& properties) :
+    CalloutObject(bus, objectPath.c_str(), true),
+    entryID(id), timestamp(timestamp)
+{
+    path(inventoryPath);
+
+    auto it = properties.find("BuildDate");
+    if (it != properties.end())
+    {
+        buildDate(it->second.get<std::string>());
+    }
+
+    it = properties.find("Manufacturer");
+    if (it != properties.end())
+    {
+        manufacturer(it->second.get<std::string>());
+    }
+
+    it = properties.find("Model");
+    if (it != properties.end())
+    {
+        model(it->second.get<std::string>());
+    }
+
+    it = properties.find("PartNumber");
+    if (it != properties.end())
+    {
+        partNumber(it->second.get<std::string>());
+    }
+
+    it = properties.find("SerialNumber");
+    if (it != properties.end())
+    {
+        serialNumber(it->second.get<std::string>());
+    }
+
+    emit_object_added();
+}
+
+void Callout::serialize(const fs::path& dir)
+{
+    auto path = getFilePath(dir);
+    std::ofstream stream(path.c_str(), std::ios::binary);
+    cereal::BinaryOutputArchive oarchive(stream);
+
+    oarchive(*this);
+}
+
+bool Callout::deserialize(const fs::path& dir)
+{
+    auto path = getFilePath(dir);
+
+    if (!fs::exists(path))
+    {
+        return false;
+    }
+
+    // Save the current ID and timestamp and then use them after
+    // deserialization to check that the data we are restoring
+    // is for the correct error log.
+
+    auto originalID = entryID;
+    auto originalTS = timestamp;
+
+    try
+    {
+        std::ifstream stream(path.c_str(), std::ios::binary);
+        cereal::BinaryInputArchive iarchive(stream);
+
+        iarchive(*this);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(e.what());
+        log<level::ERR>("Failed trying to restore a Callout object",
+                        entry("PATH=%s", path.c_str()));
+        fs::remove(path);
+        return false;
+    }
+
+    if ((entryID != originalID) || (timestamp != originalTS))
+    {
+        log<level::INFO>(
+            "Timestamp or ID mismatch in persisted Callout. Discarding",
+            entry("PATH=%s", path.c_str()), entry("PERSISTED_ID=%lu", entryID),
+            entry("EXPECTED_ID=%lu", originalID),
+            entry("PERSISTED_TS=%llu", timestamp),
+            entry("EXPECTED_TS=%llu", originalTS));
+        fs::remove(path);
+        return false;
+    }
+
+    return true;
+}
+}
+}
diff --git a/callout.hpp b/callout.hpp
new file mode 100644
index 0000000..b1e3b53
--- /dev/null
+++ b/callout.hpp
@@ -0,0 +1,154 @@
+#pragma once
+
+#include <experimental/filesystem>
+#include "dbus.hpp"
+#include "interfaces.hpp"
+
+namespace ibm
+{
+namespace logging
+{
+
+namespace fs = std::experimental::filesystem;
+
+/**
+ *  @class Callout
+ *
+ *  This class provides information about a callout by utilizing the
+ *  xyz.openbmc_project.Inventory.Decorator.Asset and
+ *  xyz.openbmc_project.Common.ObjectPath interfaces.
+ *
+ *  It also has the ability to persist and restore its data.
+ */
+class Callout : public CalloutObject
+{
+  public:
+    Callout() = delete;
+    Callout(const Callout&) = delete;
+    Callout& operator=(const Callout&) = delete;
+    Callout(Callout&&) = default;
+    Callout& operator=(Callout&&) = default;
+    ~Callout() = default;
+
+    /**
+     * Constructor
+     *
+     * Populates the Asset D-Bus properties with data from the property map.
+     *
+     * @param[in] bus - D-Bus object
+     * @param[in] objectPath - object path
+     * @param[in] inventoryPath - inventory path of the callout
+     * @param[in] id - which callout this is
+     * @param[in] timestamp - timestamp when the log was created
+     * @param[in] properties - the properties for the Asset interface.
+     */
+    Callout(sdbusplus::bus::bus& bus, const std::string& objectPath,
+            const std::string& inventoryPath, size_t id, uint64_t timestamp,
+            const DbusPropertyMap& properties);
+    /**
+     * Constructor
+     *
+     * This version is for when the object is being restored and does
+     * not take the properties map.
+     *
+     * @param[in] bus - D-Bus object
+     * @param[in] objectPath - object path
+     * @param[in] id - which callout this is
+     * @param[in] timestamp - timestamp when the log was created
+     * @param[in] properties - the properties for the Asset interface.
+     */
+    Callout(sdbusplus::bus::bus& bus, const std::string& objectPath, size_t id,
+            uint64_t timestamp);
+
+    /**
+     * Returns the callout ID
+     *
+     * @return id - the ID
+     */
+    inline auto id() const
+    {
+        return entryID;
+    }
+
+    /**
+     * Sets the callout ID
+     *
+     * @param[in] id - the ID
+     */
+    inline void id(uint32_t id)
+    {
+        entryID = id;
+    }
+
+    /**
+     * Returns the timestamp
+     *
+     * @return timestamp
+     */
+    inline auto ts() const
+    {
+        return timestamp;
+    }
+
+    /**
+     * Sets the timestamp
+     *
+     * @param[in] ts - the timestamp
+     */
+    inline void ts(uint64_t ts)
+    {
+        timestamp = ts;
+    }
+
+    /**
+     * Serializes the class instance into a file in the
+     * directory passed in.  The filename will match the
+     * ID value passed into the constructor.
+     *
+     * @param[in] - the directory to save the file  in.
+     */
+    void serialize(const fs::path& dir);
+
+    /**
+     * Loads the class members in from a file written by a previous
+     * call to serialize().  The filename it uses is the ID
+     * value passed into the constructor in the directory
+     * passed to this function.
+     *
+     * @param[in] dir - the directory to look for the file in
+     *
+     * @return bool - true if the deserialization was successful,
+     *                false if it wasn't
+     */
+    bool deserialize(const fs::path& dir);
+
+  private:
+    /**
+     * Returns the fully qualified filename to use for the serialization
+     * data.  The file is the ID value, like "0", in the base directory
+     * passed in.
+     *
+     * @param[in] baseDir - the directory the file will be in
+     *
+     * @return path - the filename
+     */
+    inline auto getFilePath(const fs::path& baseDir)
+    {
+        return baseDir / std::to_string(entryID);
+    }
+
+    /**
+     * The unique identifier for the callout, as error logs can have
+     * multiple callouts.  They start at 0.
+     */
+    size_t entryID;
+
+    /**
+     * The timestamp of when the error log was created.
+     * Used for ensuring the callout data is being restored for
+     * the correct error log.
+     */
+    uint64_t timestamp;
+};
+}
+}
diff --git a/configure.ac b/configure.ac
index 07abbe0..2074692 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,6 +22,9 @@
 AC_CHECK_HEADER(nlohmann/json.hpp, ,
                 [AC_MSG_ERROR([Could not find nlohmann/json.hpp... nlohmann/json package required])])
 
+AC_CHECK_HEADER(cereal/archives/binary.hpp, ,
+                [AC_MSG_ERROR([Could not find cereal/archives/binary.hpp... cereal package required])])
+
 # Checks for typedefs, structures, and compiler characteristics.
 AX_CXX_COMPILE_STDCXX_14([noext])
 AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
@@ -73,6 +76,10 @@
           [The xyz logging busname])
 AC_DEFINE(IBM_LOGGING_BUSNAME, "com.ibm.Logging",
           [The IBM log manager DBus busname to own])
+AC_DEFINE(ASSOC_IFACE, "org.openbmc.Associations",
+          [The associations interface])
+AC_DEFINE(ASSET_IFACE, "xyz.openbmc_project.Inventory.Decorator.Asset",
+          [The asset interface])
 
 AC_DEFINE(DEFAULT_POLICY_EID, "None",
           [The default event ID to use])
@@ -83,5 +90,18 @@
 AS_IF([test "x$POLICY_JSON_PATH" == "x"], [POLICY_JSON_PATH="/usr/share/ibm-logging/policy.json"])
 AC_DEFINE_UNQUOTED([POLICY_JSON_PATH], ["$POLICY_JSON_PATH"], [The path to the policy json file on the BMC])
 
+AC_ARG_VAR(ERRLOG_PERSIST_PATH, [Path to save errors in])
+AS_IF([test "x$ERRLOG_PERSIST_PATH" == "x"], \
+    [ERRLOG_PERSIST_PATH="/var/lib/ibm-logging/errors"])
+AC_DEFINE_UNQUOTED([ERRLOG_PERSIST_PATH], ["$ERRLOG_PERSIST_PATH"], \
+    [Path to save errors in])
+
+AC_ARG_VAR(CALLOUT_CLASS_VERSION,
+           [Callout class version to register with Cereal])
+AS_IF([test "x$CALLOUT_CLASS_VERSION" == "x"],
+      [CALLOUT_CLASS_VERSION=1])
+AC_DEFINE_UNQUOTED([CALLOUT_CLASS_VERSION], [$CALLOUT_CLASS_VERSION],
+                   [Callout Class version to register with Cereal])
+
 AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT
diff --git a/dbus.cpp b/dbus.cpp
index 17ad210..06b0078 100644
--- a/dbus.cpp
+++ b/dbus.cpp
@@ -21,6 +21,13 @@
 namespace logging
 {
 
+constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_IFACE = "xyz.openbmc_project.ObjectMapper";
+constexpr auto PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
+
+using namespace phosphor::logging;
+
 ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
                                   const std::string& service,
                                   const std::string& objPath)
@@ -35,8 +42,8 @@
 
     if (reply.is_method_error())
     {
-        using namespace phosphor::logging;
         log<level::ERR>("Failed to get managed objects",
+                        entry("SERVICE=%s", service.c_str()),
                         entry("PATH=%s", objPath.c_str()));
     }
     else
@@ -46,5 +53,81 @@
 
     return interfaces;
 }
+
+DbusPropertyMap getAllProperties(sdbusplus::bus::bus& bus,
+                                 const std::string& service,
+                                 const std::string& objPath,
+                                 const std::string& interface)
+{
+    DbusPropertyMap properties;
+
+    auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+                                      PROPERTY_IFACE, "GetAll");
+    method.append(interface);
+    auto reply = bus.call(method);
+
+    if (reply.is_method_error())
+    {
+        log<level::ERR>("Failed to get all properties",
+                        entry("SERVICE=%s", service.c_str()),
+                        entry("PATH=%s", objPath.c_str()),
+                        entry("INTERFACE=%s", interface.c_str()));
+    }
+    else
+    {
+        reply.read(properties);
+    }
+
+    return properties;
+}
+
+DbusSubtree getSubtree(sdbusplus::bus::bus& bus, const std::string& root,
+                       size_t depth, const std::string& interface)
+{
+    DbusSubtree tree;
+
+    auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, MAPPER_IFACE,
+                                      "GetSubTree");
+    method.append(root);
+    method.append(depth);
+    method.append(std::vector<std::string>({interface}));
+    auto reply = bus.call(method);
+
+    if (reply.is_method_error())
+    {
+        log<level::ERR>("Failed to get subtree", entry("ROOT=%s", root.c_str()),
+                        entry("INTERFACE=%s", interface.c_str()));
+    }
+    else
+    {
+        reply.read(tree);
+    }
+
+    return tree;
+}
+
+DbusService getService(const std::string& objPath, const std::string& interface,
+                       const DbusSubtree& tree)
+{
+    DbusService service;
+
+    auto services = tree.find(objPath);
+    if (services != tree.end())
+    {
+        auto s = std::find_if(services->second.begin(), services->second.end(),
+                              [&interface](const auto& entry) {
+                                  auto i =
+                                      std::find(entry.second.begin(),
+                                                entry.second.end(), interface);
+                                  return i != entry.second.end();
+                              });
+        if (s != services->second.end())
+        {
+            service = s->first;
+        }
+    }
+
+    return service;
+}
 }
 }
diff --git a/dbus.hpp b/dbus.hpp
index 38e62bd..c572d99 100644
--- a/dbus.hpp
+++ b/dbus.hpp
@@ -12,8 +12,18 @@
 
 using DbusInterface = std::string;
 using DbusProperty = std::string;
+using DbusService = std::string;
+using DbusPath = std::string;
+
+static constexpr auto forwardPos = 0;
+static constexpr auto reversePos = 1;
+static constexpr auto endpointPos = 2;
+using AssociationsPropertyType =
+    std::vector<std::tuple<std::string, std::string, std::string>>;
+
 using Value = sdbusplus::message::variant<bool, uint32_t, uint64_t, std::string,
-                                          std::vector<std::string>>;
+                                          std::vector<std::string>,
+                                          AssociationsPropertyType>;
 
 using DbusPropertyMap = std::map<DbusProperty, Value>;
 using DbusInterfaceMap = std::map<DbusInterface, DbusPropertyMap>;
@@ -22,8 +32,71 @@
 using ObjectValueTree =
     std::map<sdbusplus::message::object_path, DbusInterfaceMap>;
 
+using DbusSubtree =
+    std::map<DbusPath, std::map<DbusService, DbusInterfaceList>>;
+
+/**
+ * Returns the managed objects for an object path and service
+ *
+ * Returns an empty map if there are any failures.
+ *
+ * @param[in] bus - the D-Bus object
+ * @param[in] service - the D-Bus service name
+ * @param[in] objPath - the D-Bus object path
+ *
+ * @return ObjectValueTree - A map of object paths to their
+ *                           interfaces and properties.
+ */
 ObjectValueTree getManagedObjects(sdbusplus::bus::bus& bus,
                                   const std::string& service,
                                   const std::string& objPath);
+
+/**
+ * Returns the subtree for a root, depth, and interface.
+ *
+ * Returns an empty map if there are any failures.
+ *
+ * @param[in] bus - the D-Bus object
+ * @param[in] root - the point from which to provide results
+ * @param[in] depth - the number of path elements to descend
+ *
+ * @return DbusSubtree - A map of object paths to their
+ *                       services and interfaces.
+ */
+DbusSubtree getSubtree(sdbusplus::bus::bus& bus, const std::string& root,
+                       size_t depth, const std::string& interface);
+
+/**
+ * Get the D-Bus service name for the object path and interface from
+ * the data returned from a GetSubTree call.
+ *
+ * Returns an empty string if the service can't be found.
+ *
+ * @param[in] objPath - the D-Bus object path
+ * @param[in] interface - the D-Bus interface name
+ * @param[in] tree - the D-Bus GetSubTree response
+ *
+ * @return string - the service name
+ */
+DbusService getService(const std::string& objPath, const std::string& interface,
+                       const DbusSubtree& tree);
+
+/**
+ * Returns all properties on a particular interface on a
+ * particular D-Bus object.
+ *
+ * Returns an empty map if there are any failures.
+ *
+ * @param[in] bus - the D-Bus object
+ * @param[in] service - the D-Bus service name
+ * @param[in] objPath - the D-Bus object path
+ * @param[in] interface - the D-Bus interface name
+ *
+ * @return DbusPropertyMap - The map of property names to values
+ */
+DbusPropertyMap getAllProperties(sdbusplus::bus::bus& bus,
+                                 const std::string& service,
+                                 const std::string& objPath,
+                                 const std::string& interface);
 }
 }
diff --git a/interfaces.hpp b/interfaces.hpp
index 0dfa668..6c7651f 100644
--- a/interfaces.hpp
+++ b/interfaces.hpp
@@ -1,6 +1,8 @@
 #pragma once
 
 #include <com/ibm/Logging/Policy/server.hpp>
+#include <xyz/openbmc_project/Common/ObjectPath/server.hpp>
+#include <xyz/openbmc_project/Inventory/Decorator/Asset/server.hpp>
 
 namespace ibm
 {
@@ -10,11 +12,19 @@
 template <typename... T>
 using ServerObject = typename sdbusplus::server::object::object<T...>;
 
+using ObjectPathInterface =
+    sdbusplus::xyz::openbmc_project::Common::server::ObjectPath;
+
+using CalloutInterface =
+    sdbusplus::xyz::openbmc_project::Inventory::Decorator::server::Asset;
+using CalloutObject = ServerObject<CalloutInterface, ObjectPathInterface>;
+
 using PolicyInterface = sdbusplus::com::ibm::Logging::server::Policy;
 using PolicyObject = ServerObject<PolicyInterface>;
 
 enum class InterfaceType
 {
+    CALLOUT,
     POLICY
 };
 }
diff --git a/manager.cpp b/manager.cpp
index 276a5a5..2503e93 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -13,6 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <phosphor-logging/log.hpp>
+#include "callout.hpp"
 #include "config.h"
 #include "manager.hpp"
 #include "policy_find.hpp"
@@ -22,6 +24,8 @@
 namespace logging
 {
 
+namespace fs = std::experimental::filesystem;
+
 Manager::Manager(sdbusplus::bus::bus& bus) :
     bus(bus),
     addMatch(bus,
@@ -50,26 +54,47 @@
     {
         const auto& interfaces = object.second;
 
-        auto propertyMap = std::find_if(
-            interfaces.begin(), interfaces.end(),
-            [](const auto& i) { return i.first == LOGGING_IFACE; });
+        auto propertyMap = interfaces.find(LOGGING_IFACE);
 
         if (propertyMap != interfaces.end())
         {
-            create(object.first, propertyMap->second);
+            createWithRestore(object.first, interfaces);
         }
     }
 }
 
-void Manager::create(const std::string& objectPath,
-                     const DbusPropertyMap& properties)
+void Manager::createWithRestore(const std::string& objectPath,
+                                const DbusInterfaceMap& interfaces)
 {
+    createObject(objectPath, interfaces);
 
+    restoreCalloutObjects(objectPath, interfaces);
+}
+
+void Manager::create(const std::string& objectPath,
+                     const DbusInterfaceMap& interfaces)
+{
+    createObject(objectPath, interfaces);
+
+    createCalloutObjects(objectPath, interfaces);
+}
+
+void Manager::createObject(const std::string& objectPath,
+                           const DbusInterfaceMap& interfaces)
+{
 #ifdef USE_POLICY_INTERFACE
-    createPolicyInterface(objectPath, properties);
+    auto logInterface = interfaces.find(LOGGING_IFACE);
+    createPolicyInterface(objectPath, logInterface->second);
 #endif
 }
 
+void Manager::erase(EntryID id)
+{
+    fs::remove_all(getSaveDir(id));
+    childEntries.erase(id);
+    entries.erase(id);
+}
+
 void Manager::addInterface(const std::string& objectPath, InterfaceType type,
                            std::experimental::any& object)
 {
@@ -88,6 +113,40 @@
     }
 }
 
+void Manager::addChildInterface(const std::string& objectPath,
+                                InterfaceType type,
+                                std::experimental::any& object)
+{
+    auto id = getEntryID(objectPath);
+    auto entry = childEntries.find(id);
+
+    // childEntries is:
+    // A map of error log entry IDs to:
+    //  a map of interface types to:
+    //    a vector of interface objects
+
+    if (entry == childEntries.end())
+    {
+        ObjectList objects{object};
+        InterfaceMapMulti interfaces;
+        interfaces.emplace(type, std::move(objects));
+        childEntries.emplace(id, std::move(interfaces));
+    }
+    else
+    {
+        auto i = entry->second.find(type);
+        if (i == entry->second.end())
+        {
+            ObjectList objects{objects};
+            entry->second.emplace(type, objects);
+        }
+        else
+        {
+            i->second.emplace_back(object);
+        }
+    }
+}
+
 #ifdef USE_POLICY_INTERFACE
 void Manager::createPolicyInterface(const std::string& objectPath,
                                     const DbusPropertyMap& properties)
@@ -107,6 +166,115 @@
 }
 #endif
 
+void Manager::createCalloutObjects(const std::string& objectPath,
+                                   const DbusInterfaceMap& interfaces)
+{
+    // Use the associations property in the org.openbmc.Associations
+    // interface to find any callouts.  Then grab all properties on
+    // the Asset interface for that object in the inventory to use
+    // in our callout objects.
+
+    auto associations = interfaces.find(ASSOC_IFACE);
+    if (associations == interfaces.end())
+    {
+        return;
+    }
+
+    const auto& properties = associations->second;
+    auto assocProperty = properties.find("associations");
+    auto assocValue = assocProperty->second.get<AssociationsPropertyType>();
+
+    auto id = getEntryID(objectPath);
+    auto calloutNum = 0;
+    DbusSubtree subtree;
+
+    for (const auto& association : assocValue)
+    {
+        if (std::get<forwardPos>(association) != "callout")
+        {
+            continue;
+        }
+
+        auto callout = std::get<endpointPos>(association);
+
+        if (subtree.empty())
+        {
+            subtree = getSubtree(bus, "/", 0, ASSET_IFACE);
+            if (subtree.empty())
+            {
+                break;
+            }
+        }
+
+        auto service = getService(callout, ASSET_IFACE, subtree);
+        if (service.empty())
+        {
+            continue;
+        }
+
+        auto properties = getAllProperties(bus, service, callout, ASSET_IFACE);
+        if (properties.empty())
+        {
+            continue;
+        }
+
+        auto calloutPath = getCalloutObjectPath(objectPath, calloutNum);
+
+        auto object =
+            std::make_shared<Callout>(bus, calloutPath, callout, calloutNum,
+                                      getLogTimestamp(interfaces), properties);
+
+        auto dir = getCalloutSaveDir(id);
+        if (!fs::exists(dir))
+        {
+            fs::create_directories(dir);
+        }
+        object->serialize(dir);
+
+        std::experimental::any anyObject = object;
+        addChildInterface(objectPath, InterfaceType::CALLOUT, anyObject);
+        calloutNum++;
+    }
+}
+
+void Manager::restoreCalloutObjects(const std::string& objectPath,
+                                    const DbusInterfaceMap& interfaces)
+{
+    auto saveDir = getCalloutSaveDir(getEntryID(objectPath));
+
+    if (!fs::exists(saveDir))
+    {
+        return;
+    }
+
+    size_t id;
+    for (auto& f : fs::directory_iterator(saveDir))
+    {
+        try
+        {
+            id = std::stoul(f.path().filename());
+        }
+        catch (std::exception& e)
+        {
+            using namespace phosphor::logging;
+            log<level::ERR>("Invalid IBM logging callout save file. Deleting",
+                            entry("FILE=%s", f.path().c_str()));
+            fs::remove(f.path());
+            continue;
+        }
+
+        auto path = getCalloutObjectPath(objectPath, id);
+        auto callout = std::make_shared<Callout>(bus, path, id,
+                                                 getLogTimestamp(interfaces));
+        if (callout->deserialize(saveDir))
+        {
+            callout->emit_object_added();
+            std::experimental::any anyObject = callout;
+            addChildInterface(objectPath, InterfaceType::CALLOUT, anyObject);
+        }
+    }
+}
+
 void Manager::interfaceAdded(sdbusplus::message::message& msg)
 {
     sdbusplus::message::object_path path;
@@ -116,16 +284,43 @@
 
     // Find the Logging.Entry interface with all of its properties
     // to pass to create().
-    auto propertyMap =
-        std::find_if(interfaces.begin(), interfaces.end(),
-                     [](const auto& i) { return i.first == LOGGING_IFACE; });
-
-    if (propertyMap != interfaces.end())
+    if (interfaces.find(LOGGING_IFACE) != interfaces.end())
     {
-        create(path, propertyMap->second);
+        create(path, interfaces);
     }
 }
 
+uint64_t Manager::getLogTimestamp(const DbusInterfaceMap& interfaces)
+{
+    auto interface = interfaces.find(LOGGING_IFACE);
+    if (interface != interfaces.end())
+    {
+        auto property = interface->second.find("Timestamp");
+        if (property != interface->second.end())
+        {
+            return property->second.get<uint64_t>();
+        }
+    }
+
+    return 0;
+}
+
+fs::path Manager::getSaveDir(EntryID id)
+{
+    return fs::path{ERRLOG_PERSIST_PATH} / std::to_string(id);
+}
+
+fs::path Manager::getCalloutSaveDir(EntryID id)
+{
+    return getSaveDir(id) / "callouts";
+}
+
+std::string Manager::getCalloutObjectPath(const std::string& objectPath,
+                                          uint32_t calloutNum)
+{
+    return fs::path{objectPath} / "callouts" / std::to_string(calloutNum);
+}
+
 void Manager::interfaceRemoved(sdbusplus::message::message& msg)
 {
     sdbusplus::message::object_path path;
@@ -140,13 +335,7 @@
 
     if (i != interfaces.end())
     {
-        auto id = getEntryID(path);
-
-        auto entry = entries.find(id);
-        if (entry != entries.end())
-        {
-            entries.erase(entry);
-        }
+        erase(getEntryID(path));
     }
 }
 }
diff --git a/manager.hpp b/manager.hpp
index ca5e16b..39cf958 100644
--- a/manager.hpp
+++ b/manager.hpp
@@ -43,6 +43,22 @@
     explicit Manager(sdbusplus::bus::bus& bus);
 
   private:
+    using EntryID = uint32_t;
+    using InterfaceMap = std::map<InterfaceType, std::experimental::any>;
+    using EntryMap = std::map<EntryID, InterfaceMap>;
+
+    using ObjectList = std::vector<std::experimental::any>;
+    using InterfaceMapMulti = std::map<InterfaceType, ObjectList>;
+    using EntryMapMulti = std::map<EntryID, InterfaceMapMulti>;
+
+    /**
+     * Deletes the entry and any child entries with
+     * the specified ID.
+     *
+     * @param[in] id - the entry ID
+     */
+    void erase(EntryID id);
+
     /**
      * The callback for an interfaces added signal
      *
@@ -70,14 +86,83 @@
     void createAll();
 
     /**
-     * Creates the IBM interface(s) for a single error log.
+     * Creates the IBM interface(s) for a single new error log.
+     *
+     * Any interfaces that require serialization will be created
+     * and serialized here.
      *
      * @param[in] objectPath - object path of the error log
-     * @param[in] properties - the xyz.openbmc_project.Logging.Entry
-     *                         properties
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log
      */
     void create(const std::string& objectPath,
-                const DbusPropertyMap& properties);
+                const DbusInterfaceMap& interfaces);
+
+    /**
+     * Creates the IBM interface(s) for a single error log after
+     * the application is restarted.
+     *
+     * Interfaces that were persisted will be restored from their
+     * previously saved filesystem data.
+     *
+     * @param[in] objectPath - object path of the error log
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log
+     */
+    void createWithRestore(const std::string& objectPath,
+                           const DbusInterfaceMap& interfaces);
+
+    /**
+     * Creates the IBM interfaces for a single error log that
+     * do not persist across app restarts.
+     *
+     * @param[in] objectPath - object path of the error log
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log
+     */
+    void createObject(const std::string& objectPath,
+                      const DbusInterfaceMap& interfaces);
+
+    /**
+     * Returns the error log timestamp property value from
+     * the passed in map of all interfaces and property names/values
+     * on an error log D-Bus object.
+     *
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log.
+     *
+     * @return uint64_t - the timestamp
+     */
+    uint64_t getLogTimestamp(const DbusInterfaceMap& interfaces);
+
+    /**
+     * Returns the filesystem directory to use for persisting
+     * information about a particular error log.
+     *
+     * @param[in] id - the error log ID
+     * @return path - the directory path
+     */
+    std::experimental::filesystem::path getSaveDir(EntryID id);
+
+    /**
+     * Returns the directory to use to save the callout information in
+     *
+     * @param[in] id - the error log ID
+     *
+     * @return path - the directory path
+     */
+    std::experimental::filesystem::path getCalloutSaveDir(EntryID id);
+
+    /**
+     * Returns the D-Bus object path to use for a callout D-Bus object.
+     *
+     * @param[in] objectPath - the object path for the error log
+     * @param[in] calloutNum - the callout instance number
+     *
+     * @return path - the object path to use for a callout object
+     */
+    std::string getCalloutObjectPath(const std::string& objectPath,
+                                     uint32_t calloutNum);
 
     /**
      * Creates the IBM policy interface for a single error log
@@ -93,6 +178,37 @@
 #endif
 
     /**
+     * Creates D-Bus objects for any callouts in an error log
+     * that map to an inventory object with an Asset interface.
+     *
+     * The created object will also host the Asset interface.
+     *
+     * A callout object path would look like:
+     * /xyz/openbmc_project/logging/entry/5/callouts/0.
+     *
+     * Any objects created are serialized so the asset information
+     * can always be restored.
+     *
+     * @param[in] objectPath - object path of the error log
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log.
+     */
+    void createCalloutObjects(const std::string& objectPath,
+                              const DbusInterfaceMap& interfaces);
+
+    /**
+     * Restores callout objects for a particular error log that
+     * have previously been saved by reading their data out of
+     * the filesystem using Cereal.
+     *
+     * @param[in] objectPath - object path of the error log
+     * @param[in] interfaces - map of all interfaces and properties
+     *                         on a phosphor-logging error log.
+     */
+    void restoreCalloutObjects(const std::string& objectPath,
+                               const DbusInterfaceMap& interfaces);
+
+    /**
      * Returns the entry ID for a log
      *
      * @param[in] objectPath - the object path of the log
@@ -116,6 +232,21 @@
                       std::experimental::any& object);
 
     /**
+     * Adds an interface to a child object, which is an object that
+     * relates to the main ...logging/entry/X object but has a different path.
+     * The object is stored in the childEntries map.
+     *
+     * There can be multiple instances of a child object per type per
+     * logging object.
+     *
+     * @param[in] objectPath - the object path of the log
+     * @param[in] type - the interface type being added.
+     * @param[in] object - the interface object
+     */
+    void addChildInterface(const std::string& objectPath, InterfaceType type,
+                           std::experimental::any& object);
+
+    /**
      * The sdbusplus bus object
      */
     sdbusplus::bus::bus& bus;
@@ -130,16 +261,23 @@
      */
     sdbusplus::bus::match_t removeMatch;
 
-    using EntryID = uint32_t;
-    using InterfaceMap = std::map<InterfaceType, std::experimental::any>;
-    using EntryMap = std::map<EntryID, InterfaceMap>;
-
     /**
      * A map of the error log IDs to their IBM interface objects.
      * There may be multiple interfaces per ID.
      */
     EntryMap entries;
 
+    /**
+     * A map of the error log IDs to their interface objects which
+     * are children of the logging objects.
+     *
+     * These objects have the same lifespan as their parent objects.
+     *
+     * There may be multiple interfaces per ID, and also multiple
+     * interface instances per interface type.
+     */
+    EntryMapMulti childEntries;
+
 #ifdef USE_POLICY_INTERFACE
     /**
      * The class the wraps the IBM error logging policy table.
diff --git a/test/Makefile.am b/test/Makefile.am
index a699696..c1cce92 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -2,16 +2,45 @@
 
 TESTS = $(check_PROGRAMS)
 
-check_PROGRAMS = test_policy
-test_policy_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS)
+check_PROGRAMS = test_policy test_callout
 
-test_policy_CXXFLAGS = $(PTHREAD_CFLAGS) $(SDBUSPLUS_CFLAGS)
-test_policy_LDFLAGS = -lgtest_main -lgtest \
-	$(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS)
+test_cppflags = \
+	-Igtest \
+	$(GTEST_CPPFLAGS) \
+	$(AM_CPPFLAGS)
 
+test_cxxflags = \
+	$(PTHREAD_CFLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+	$(IBM_DBUS_INTERFACES_CFLAGS) \
+	$(SDBUSPLUS_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
+
+test_ldflags = \
+	-lgtest_main -lgtest \
+	-lstdc++fs \
+	$(PTHREAD_LIBS) \
+	$(OESDK_TESTCASE_FLAGS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	$(IBM_DBUS_INTERFACES_LIBS) \
+	$(SDBUSPLUS_LIBS)
+
+test_policy_CPPFLAGS = $(test_cppflags)
+test_policy_CXXFLAGS = $(test_cxxflags)
+test_policy_LDFLAGS = $(test_ldflags)
 test_policy_SOURCES = test_policy.cpp
 test_policy_LDADD = \
 	$(top_builddir)/policy_table.o \
-	$(top_builddir)/policy_find.o \
-	-lstdc++fs \
-	$(SDBUSPLUS_LIBS)
+	$(top_builddir)/policy_find.o
+
+test_callout_CPPFLAGS = $(test_cppflags)
+test_callout_CXXFLAGS = $(test_cxxflags)
+test_callout_LDFLAGS = $(test_ldflags)
+test_callout_SOURCES = test_callout.cpp
+
+#TODO: remove ObjectPath when it merges in phosphor-dbus-interfaces
+test_callout_LDADD = \
+	$(top_builddir)/xyz/openbmc_project/Common/ObjectPath/server.o \
+	$(top_builddir)/callout.o
+
+
diff --git a/test/test_callout.cpp b/test/test_callout.cpp
new file mode 100644
index 0000000..8d656cc
--- /dev/null
+++ b/test/test_callout.cpp
@@ -0,0 +1,92 @@
+/**
+ * Copyright © 2018 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 <fstream>
+#include <gtest/gtest.h>
+#include <experimental/filesystem>
+#include "callout.hpp"
+#include "dbus.hpp"
+
+using namespace ibm::logging;
+
+class CalloutTest : public ::testing::Test
+{
+  protected:
+    virtual void SetUp()
+    {
+        char dir[] = {"./calloutsXXXXXX"};
+
+        persistDir = mkdtemp(dir);
+    }
+
+    virtual void TearDown()
+    {
+        fs::remove_all(persistDir);
+    }
+
+    fs::path persistDir;
+};
+
+TEST_F(CalloutTest, TestPersist)
+{
+    using namespace std::literals::string_literals;
+
+    auto bus = sdbusplus::bus::new_default();
+    std::string objectPath{"/callout/path/0"};
+    std::string calloutPath{"/some/inventory/object"};
+    size_t id = 0;
+    uint64_t ts = 5;
+
+    DbusPropertyMap assetProps{{"BuildDate"s, Value{"Date42"s}},
+                               {"Manufacturer"s, Value{"Mfg42"s}},
+                               {"Model"s, Value{"Model42"s}},
+                               {"PartNumber"s, Value{"PN42"s}},
+                               {"SerialNumber"s, Value{"SN42"s}}};
+    {
+        auto callout = std::make_unique<Callout>(bus, objectPath, calloutPath,
+                                                 id, ts, assetProps);
+        callout->serialize(persistDir);
+
+        ASSERT_EQ(fs::exists(persistDir / std::to_string(id)), true);
+    }
+
+    // Test object restoration
+    {
+        auto callout = std::make_unique<Callout>(bus, objectPath, id, ts);
+
+        ASSERT_EQ(callout->deserialize(persistDir), true);
+
+        ASSERT_EQ(callout->id(), id);
+        ASSERT_EQ(callout->ts(), ts);
+        ASSERT_EQ(callout->path(), calloutPath);
+        ASSERT_EQ(callout->buildDate(),
+                  assetProps["BuildDate"].get<std::string>());
+        ASSERT_EQ(callout->manufacturer(),
+                  assetProps["Manufacturer"].get<std::string>());
+        ASSERT_EQ(callout->model(), assetProps["Model"].get<std::string>());
+        ASSERT_EQ(callout->partNumber(),
+                  assetProps["PartNumber"].get<std::string>());
+        ASSERT_EQ(callout->serialNumber(),
+                  assetProps["SerialNumber"].get<std::string>());
+    }
+
+    // Test a serialization failure due to a bad timestamp
+    {
+        auto callout = std::make_unique<Callout>(bus, objectPath, id, ts + 1);
+
+        ASSERT_EQ(callout->deserialize(persistDir), false);
+        ASSERT_EQ(fs::exists(persistDir / std::to_string(id)), false);
+    }
+}