OpenPOWER: Add support for Hostboot dump.

Hostboot dump is a dump created during the boot failure
on OpenPOWER based systems.
The dump get extracted from Self Boot Engine(SBE) with
the openpower-dump-manager application.
That application will call into phosphor-dump-manager
to create a dump with the data collected from each
SBE in the system.

Role of phosphor-debug-collector hostboot dump manager

 - Create the hostboot dump package directory
 - Create hostboot dump entry
 - Implement notify interface to get notified when the
   collection is completed
 - Once notified collect each file from the predefined
   location and package it into a dump.
 - Update the hostboot dump entry with details once
   dump packaging is completed.

Tested:
- Create a dump entry with busctl
- create a dummy file in search path
- Call notify with busctl

Signed-off-by: Dhruvaraj Subhshchandran <dhruvaraj@in.ibm.com>
Change-Id: I2dab3b7ca976ed33184f770722cca00b1b02c075
diff --git a/dump-extensions/default/default.cpp b/dump-extensions/default/default.cpp
index 0de4e09..00c2bfe 100644
--- a/dump-extensions/default/default.cpp
+++ b/dump-extensions/default/default.cpp
@@ -4,7 +4,8 @@
 {
 namespace dump
 {
-void loadExtensions(sdbusplus::bus_t&, DumpManagerList&)
+void loadExtensions(sdbusplus::bus::bus&, const phosphor::dump::EventPtr&,
+                    DumpManagerList&)
 {}
 } // namespace dump
 } // namespace phosphor
diff --git a/dump-extensions/openpower-dumps/dump-extensions.cpp b/dump-extensions/openpower-dumps/dump-extensions.cpp
index 45864ca..3b91842 100644
--- a/dump-extensions/openpower-dumps/dump-extensions.cpp
+++ b/dump-extensions/openpower-dumps/dump-extensions.cpp
@@ -4,21 +4,52 @@
 
 #include "dump-extensions/openpower-dumps/openpower_dumps_config.h"
 
+#include "com/ibm/Dump/Entry/Hardware/server.hpp"
+#include "com/ibm/Dump/Entry/Hostboot/server.hpp"
+#include "com/ibm/Dump/Entry/SBE/server.hpp"
+#include "dump_manager_hostdump.hpp"
 #include "dump_manager_resource.hpp"
 #include "dump_manager_system.hpp"
+#include "dump_utils.hpp"
+#include "host_dump_entry.hpp"
+
+#include <fmt/core.h>
+
+#include <phosphor-logging/log.hpp>
 
 namespace phosphor
 {
 namespace dump
 {
 
-void loadExtensions(sdbusplus::bus_t& bus, DumpManagerList& dumpList)
+using namespace phosphor::logging;
+void loadExtensions(sdbusplus::bus::bus& bus,
+                    const phosphor::dump::EventPtr& event,
+                    DumpManagerList& dumpList)
 {
-
     dumpList.push_back(std::make_unique<openpower::dump::system::Manager>(
         bus, SYSTEM_DUMP_OBJPATH, SYSTEM_DUMP_OBJ_ENTRY));
     dumpList.push_back(std::make_unique<openpower::dump::resource::Manager>(
         bus, RESOURCE_DUMP_OBJPATH, RESOURCE_DUMP_OBJ_ENTRY));
+
+    try
+    {
+        std::filesystem::create_directories(HOSTBOOT_DUMP_PATH);
+    }
+    catch (std::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format("Failed to create hostboot dump directory({})",
+                        HOSTBOOT_DUMP_PATH)
+                .c_str());
+        throw std::runtime_error("Failed to create hostboot dump directory");
+    }
+    dumpList.push_back(std::make_unique<openpower::dump::hostdump::Manager<
+                           sdbusplus::com::ibm::Dump::Entry::server::Hostboot>>(
+        bus, event, HOSTBOOT_DUMP_OBJPATH, HOSTBOOT_DUMP_OBJ_ENTRY,
+        HOSTBOOT_DUMP_PATH, "hbdump", HOSTBOOT_DUMP_TMP_FILE_DIR,
+        HOSTBOOT_DUMP_MAX_SIZE, HOSTBOOT_DUMP_MIN_SPACE_REQD,
+        HOSTBOOT_DUMP_TOTAL_SIZE));
 }
 } // namespace dump
 } // namespace phosphor
diff --git a/dump-extensions/openpower-dumps/dump_manager_hostdump.hpp b/dump-extensions/openpower-dumps/dump_manager_hostdump.hpp
new file mode 100644
index 0000000..95fe8ed
--- /dev/null
+++ b/dump-extensions/openpower-dumps/dump_manager_hostdump.hpp
@@ -0,0 +1,315 @@
+#pragma once
+
+#include "dump-extensions/openpower-dumps/openpower_dumps_config.h"
+
+#include "dump_manager_bmcstored.hpp"
+#include "dump_utils.hpp"
+#include "host_dump_entry.hpp"
+#include "op_dump_util.hpp"
+#include "watch.hpp"
+#include "xyz/openbmc_project/Common/error.hpp"
+#include "xyz/openbmc_project/Dump/Create/error.hpp"
+#include "xyz/openbmc_project/Dump/NewDump/server.hpp"
+
+#include <fmt/core.h>
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <com/ibm/Dump/Create/server.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <sdeventplus/exception.hpp>
+#include <sdeventplus/source/base.hpp>
+#include <sdeventplus/source/child.hpp>
+#include <xyz/openbmc_project/Dump/Create/server.hpp>
+
+#include <ctime>
+#include <filesystem>
+#include <regex>
+
+namespace openpower
+{
+namespace dump
+{
+namespace hostdump
+{
+
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using namespace phosphor::logging;
+
+constexpr auto INVALID_DUMP_SIZE = 0;
+constexpr auto HOST_DUMP_COMMON_FILENAME_PART =
+    "_([0-9]+)_([0-9]+).([a-zA-Z0-9]+)";
+
+using CreateIface = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Dump::server::Create,
+    sdbusplus::com::ibm::Dump::server::Create,
+    sdbusplus::xyz::openbmc_project::Dump::server::NewDump>;
+
+using UserMap = phosphor::dump::inotify::UserMap;
+
+using Watch = phosphor::dump::inotify::Watch;
+using ::sdeventplus::source::Child;
+
+using originatorTypes = sdbusplus::xyz::openbmc_project::Common::server::
+    OriginatedBy::OriginatorTypes;
+
+/** @class Manager
+ *  @brief Host Dump  manager implementation.
+ *  @details A concrete implementation for the
+ *  xyz.openbmc_project.Dump.Create
+ *  com::ibm::Dump::Create and
+ *  xyz::openbmc_project::Dump::NewDump D-Bus APIs
+ */
+template <typename T>
+class Manager :
+    virtual public CreateIface,
+    public phosphor::dump::bmc_stored::Manager
+{
+  public:
+    Manager() = delete;
+    Manager(const Manager&) = default;
+    Manager& operator=(const Manager&) = delete;
+    Manager(Manager&&) = delete;
+    Manager& operator=(Manager&&) = delete;
+    virtual ~Manager() = default;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] event - Dump manager sd_event loop.
+     *  @param[in] path - Path to attach at.
+     *  @param[in] baseEntryPath - Base path for dump entry.
+     *  @param[in] filePath - Path where the dumps are stored.
+     *  @param[in] dumpNamePrefix - Prefix to the dump filename
+     *  @param[in] dumpTempFileDir - Temporary location of dump files
+     *  @param[in] maxDumpSize - Maximum allowed size of dump file
+     *  @param[in] minDumpSize - Minimum size of a usable dump
+     *  @param[in] allocatedSize - Total allocated space for the dump.
+     */
+    Manager(sdbusplus::bus::bus& bus, const phosphor::dump::EventPtr& event,
+            const char* path, const std::string& baseEntryPath,
+            const char* filePath, const std::string dumpNamePrefix,
+            const std::string dumpTempFileDir, const uint64_t maxDumpSize,
+            const uint64_t minDumpSize, const uint64_t allocatedSize) :
+        CreateIface(bus, path),
+        phosphor::dump::bmc_stored::Manager(
+            bus, event, path, baseEntryPath, filePath,
+            dumpNamePrefix + HOST_DUMP_COMMON_FILENAME_PART, maxDumpSize,
+            minDumpSize, allocatedSize),
+        dumpNamePrefix(dumpNamePrefix), dumpTempFileDir(dumpTempFileDir)
+    {}
+
+    /** @brief Implementation for CreateDump
+     *  Method to create a host dump entry when user requests for a
+     *  new host dump
+     *
+     *  @return object_path - The object path of the new dump entry.
+     */
+    sdbusplus::message::object_path
+        createDump(phosphor::dump::DumpCreateParams params) override
+    {
+        using InvalidArgument =
+            sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+        using Argument = xyz::openbmc_project::Common::InvalidArgument;
+        if (!params.empty())
+        {
+            log<level::ERR>(fmt::format("Dump type({}) accepts no additional "
+                                        "parameters, number of parameters({})",
+                                        dumpNamePrefix, params.size())
+                                .c_str());
+            elog<InvalidArgument>(
+                Argument::ARGUMENT_NAME("NO_PARAMETERS_NEEDED"),
+                Argument::ARGUMENT_VALUE("INVALID_PARAMETERS"));
+        }
+
+        // Check dump policy
+        util::isOPDumpsEnabled();
+
+        auto size = getAllowedSize();
+
+        uint32_t id = ++lastEntryId;
+
+        // Entry Object path.
+        auto objPath =
+            std::filesystem::path(baseEntryPath) / std::to_string(id);
+
+        log<level::INFO>(fmt::format("Create dump type({}) with id({}) "
+                                     "available space: ({}) kilobytes",
+                                     dumpNamePrefix, id, size)
+                             .c_str());
+
+        std::time_t timeStamp = std::time(nullptr);
+        createEntry(id, objPath, timeStamp, 0, std::string(),
+                    phosphor::dump::OperationStatus::InProgress, std::string(),
+                    originatorTypes::Internal);
+
+        return objPath.string();
+    }
+
+    /** @brief Notify the host dump manager about creation of a new dump.
+     *  @param[in] dumpId - Id from the source of the dump.
+     *  @param[in] size - Size of the dump.
+     */
+    void notify(uint32_t dumpId, uint64_t) override
+    {
+        try
+        {
+            captureDump(dumpId);
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>(
+                fmt::format("Failed to package dump({}): id({}) errorMsg({})",
+                            dumpNamePrefix, dumpId, e.what())
+                    .c_str());
+            throw std::runtime_error("Failed to package dump");
+        }
+    }
+    /** @brief Create a  Dump Entry Object
+     *  @param[in] id - Id of the dump
+     *  @param[in] objPath - Object path to attach to
+     *  @param[in] ms - Dump creation timestamp since the epoch.
+     *  @param[in] fileSize - Dump file size in bytes.
+     *  @param[in] file - Name of dump file.
+     *  @param[in] status - status of the dump.
+     *  @param[in] originatorId - Id of the originator of the dump
+     *  @param[in] originatorType - Originator type
+     */
+
+    virtual void createEntry(const uint32_t id, const std::string objPath,
+                             const uint64_t ms, uint64_t fileSize,
+                             const std::filesystem::path& file,
+                             phosphor::dump::OperationStatus status,
+                             std::string originatorId,
+                             originatorTypes originatorType) override
+    {
+        try
+        {
+            entries.insert(std::make_pair(
+                id, std::make_unique<openpower::dump::hostdump::Entry<T>>(
+                        bus, objPath.c_str(), id, ms, fileSize, file, status,
+                        originatorId, originatorType, *this)));
+        }
+        catch (const std::invalid_argument& e)
+        {
+            log<level::ERR>(fmt::format("Error in creating host dump entry, "
+                                        "errormsg({}), OBJECTPATH({}), ID({})",
+                                        e.what(), objPath.c_str(), id)
+                                .c_str());
+            throw std::runtime_error("Error in creating host dump entry");
+        }
+    }
+
+  private:
+    std::string dumpNamePrefix;
+    std::string dumpTempFileDir;
+
+    void captureDump(uint32_t dumpId)
+    {
+        std::string idStr;
+        try
+        {
+            idStr = std::to_string(dumpId);
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>("Dump capture: Error converting idto string");
+            throw std::runtime_error(
+                "Dump capture: Error converting dump id to string");
+        }
+
+        // Get Dump size.
+        // TODO #ibm-openbmc/issues/3061
+        // Dump request will be rejected if there is not enough space for
+        // one complete dump, change this behavior to crate a partial dump
+        // with available space.
+        auto size = getAllowedSize();
+
+        auto dumpTempPath = std::filesystem::path(dumpTempFileDir) / idStr;
+
+        pid_t pid = fork();
+        if (pid == 0)
+        {
+            std::filesystem::path dumpPath(dumpDir);
+            dumpPath /= idStr;
+            execl("/usr/bin/opdreport", "opdreport", "-d", dumpPath.c_str(),
+                  "-i", idStr.c_str(), "-s", std::to_string(size).c_str(), "-q",
+                  "-v", "-p", dumpTempPath.c_str(), "-n",
+                  dumpNamePrefix.c_str(), nullptr);
+
+            // opdreport script execution is failed.
+            auto error = errno;
+            log<level::ERR>(
+                fmt::format(
+                    "Dump capture: Error occurred during "
+                    "opdreport function execution, errno({}), dumpPrefix({}), "
+                    "dumpPath({}), dumpSourcePath({}), allowedSize({})",
+                    error, dumpNamePrefix.c_str(), dumpPath.c_str(),
+                    dumpTempPath.c_str(), size)
+                    .c_str());
+            throw std::runtime_error("Dump capture: Error occured during "
+                                     "opdreport script execution");
+        }
+        else if (pid > 0)
+        {
+            phosphor::dump::Entry* dumpEntry = NULL;
+            auto dumpIt = entries.find(dumpId);
+            if (dumpIt != entries.end())
+            {
+                dumpEntry = dumpIt->second.get();
+            }
+            Child::Callback callback = [this, dumpEntry,
+                                        pid](Child&, const siginfo_t* si) {
+                // Set progress as failed if packaging return error
+                if (si->si_status != 0)
+                {
+                    log<level::ERR>("Dump packaging failed");
+                    if (dumpEntry != nullptr)
+                    {
+                        reinterpret_cast<phosphor::dump::Entry*>(dumpEntry)
+                            ->status(phosphor::dump::OperationStatus::Failed);
+                    }
+                }
+                else
+                {
+                    log<level::INFO>("Dump packaging completed");
+                }
+                this->childPtrMap.erase(pid);
+            };
+            try
+            {
+                childPtrMap.emplace(
+                    pid, std::make_unique<Child>(eventLoop.get(), pid,
+                                                 WEXITED | WSTOPPED,
+                                                 std::move(callback)));
+            }
+            catch (const sdeventplus::SdEventError& ex)
+            {
+                // Failed to add to event loop
+                log<level::ERR>(
+                    fmt::format("Dump capture: Error occurred during "
+                                "the sdeventplus::source::Child ex({})",
+                                ex.what())
+                        .c_str());
+                throw std::runtime_error(
+                    "Dump capture: Error occurred during the "
+                    "sdeventplus::source::Child creation");
+            }
+        }
+        else
+        {
+            auto error = errno;
+            log<level::ERR>(
+                fmt::format(
+                    "Dump capture: Error occurred during fork, errno({})",
+                    error)
+                    .c_str());
+            throw std::runtime_error(
+                "Dump capture: Error occurred during fork");
+        }
+    }
+};
+
+} // namespace hostdump
+} // namespace dump
+} // namespace openpower
diff --git a/dump-extensions/openpower-dumps/host_dump_entry.hpp b/dump-extensions/openpower-dumps/host_dump_entry.hpp
new file mode 100644
index 0000000..fe2b839
--- /dev/null
+++ b/dump-extensions/openpower-dumps/host_dump_entry.hpp
@@ -0,0 +1,75 @@
+#pragma once
+
+#include "bmcstored_dump_entry.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+
+#include <filesystem>
+
+namespace openpower
+{
+namespace dump
+{
+namespace hostdump
+{
+
+using originatorTypes = sdbusplus::xyz::openbmc_project::Common::server::
+    OriginatedBy::OriginatorTypes;
+
+template <typename T>
+using ServerObject = typename sdbusplus::server::object_t<T>;
+
+template <typename T>
+using EntryIfaces = sdbusplus::server::object_t<T>;
+
+/** @class Entry
+ *  @brief Host Dump Entry implementation.
+ *  @details A concrete implementation for the
+ *  host dump type DBus API
+ */
+template <typename T>
+class Entry :
+    virtual public EntryIfaces<T>,
+    virtual public phosphor::dump::bmc_stored::Entry
+{
+  public:
+    Entry() = delete;
+    Entry(const Entry&) = delete;
+    Entry& operator=(const Entry&) = delete;
+    Entry(Entry&&) = delete;
+    Entry& operator=(Entry&&) = delete;
+    ~Entry() = default;
+
+    /** @brief Constructor for the Dump Entry Object
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] objPath - Object path to attach to
+     *  @param[in] dumpId - Dump id.
+     *  @param[in] timeStamp - Dump creation timestamp
+     *             since the epoch.
+     *  @param[in] fileSize - Dump file size in bytes.
+     *  @param[in] file - Name of dump file.
+     *  @param[in] status - status of the dump.
+     *  @param[in] originatorId - Id of the originator of the dump
+     *  @param[in] originatorType - Originator type
+     *  @param[in] parent - The dump entry's parent.
+     */
+    Entry(sdbusplus::bus_t& bus, const std::string& objPath, uint32_t dumpId,
+          uint64_t timeStamp, uint64_t fileSize,
+          const std::filesystem::path& file,
+          phosphor::dump::OperationStatus status, std::string originatorId,
+          originatorTypes originatorType, phosphor::dump::Manager& parent) :
+        EntryIfaces<T>(bus, objPath.c_str(),
+                       EntryIfaces<T>::action::emit_object_added),
+        phosphor::dump::bmc_stored::Entry(bus, objPath.c_str(), dumpId,
+                                          timeStamp, fileSize, file, status,
+                                          originatorId, originatorType, parent)
+    {
+        // Emit deferred signal.
+        this->openpower::dump::hostdump::EntryIfaces<T>::emit_object_added();
+    }
+};
+
+} // namespace hostdump
+} // namespace dump
+} // namespace openpower
diff --git a/dump-extensions/openpower-dumps/meson.build b/dump-extensions/openpower-dumps/meson.build
index c367421..ac971a4 100644
--- a/dump-extensions/openpower-dumps/meson.build
+++ b/dump-extensions/openpower-dumps/meson.build
@@ -19,6 +19,30 @@
 opconf_data.set_quoted('RESOURCE_DUMP_OBJ_ENTRY', get_option('RESOURCE_DUMP_OBJ_ENTRY'),
                       description : 'The resource dump entry D-Bus object path'
                     )
+
+
+opconf_data.set_quoted('HOSTBOOT_DUMP_OBJPATH', get_option('HOSTBOOT_DUMP_OBJPATH'),
+                      description : 'The Hostboot dump manager D-Bus path'
+                    )
+opconf_data.set_quoted('HOSTBOOT_DUMP_OBJ_ENTRY', get_option('HOSTBOOT_DUMP_OBJ_ENTRY'),
+                      description : 'The hostboot dump entry D-Bus object path'
+                    )
+opconf_data.set_quoted('HOSTBOOT_DUMP_TMP_FILE_DIR', get_option('HOSTBOOT_DUMP_TMP_FILE_DIR'),
+                      description : 'Directory where hostboot dump pieces are stored for packaging'
+                    )
+opconf_data.set_quoted('HOSTBOOT_DUMP_PATH', get_option('HOSTBOOT_DUMP_PATH'),
+                     description : 'Directory where Hostboot dumps are placed'
+             )
+opconf_data.set('HOSTBOOT_DUMP_MAX_SIZE', get_option('HOSTBOOT_DUMP_MAX_SIZE'),
+               description : 'Maximum size of one Hostboot dump in kilo bytes'
+             )
+opconf_data.set('HOSTBOOT_DUMP_MIN_SPACE_REQD', get_option('HOSTBOOT_DUMP_MIN_SPACE_REQD'),
+               description : 'Minimum space required for one Hostboot dump in kilo bytes'
+             )
+opconf_data.set('HOSTBOOT_DUMP_TOTAL_SIZE', get_option('HOSTBOOT_DUMP_TOTAL_SIZE'),
+               description : 'Total size of the dump in kilo bytes'
+             )
+
 configure_file(configuration : opconf_data,
                output : 'openpower_dumps_config.h'
               )