PEL: Add PEL D-Bus methods

Implement the org.open_power.Logging.PEL D-Bus interface on
/xyz/openbmc_project/logging.

It provides the following methods:
* getPEL - Return a unix FD to the PEL data based on the PEL id.
* getPELFromOBMCID - Return PEL data in a vector based on the
                     corresponding OpenBMC event log id.
* hostAck - Called when the host has sent the PEL up to the OS,
            which is the final step in the reporting process.
* hostReject - Called when the host has an issue with a PEL, either:
  - The host doesn't have any more room for PELs at this moment.
  - The PEL was malformed.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I633ae9e26d8336973363a1a207e8fd493f7ff7d2
diff --git a/configure.ac b/configure.ac
index 657424d..ae9d6e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -169,7 +169,7 @@
             [],
             [AC_MSG_ERROR([Could not find fifo_map.hpp])])
 
-        AX_PKG_CHECK_MODULES([LIBPLDM], [libpldm])]
+       AX_PKG_CHECK_MODULES([LIBPLDM], [libpldm])]
 )
 
 AC_ARG_ENABLE([dont-send-pels-to-host],
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
index b9c7d94..aec5053 100644
--- a/extensions/openpower-pels/manager.cpp
+++ b/extensions/openpower-pels/manager.cpp
@@ -20,6 +20,7 @@
 
 #include <filesystem>
 #include <fstream>
+#include <xyz/openbmc_project/Common/error.hpp>
 
 namespace openpower
 {
@@ -29,6 +30,8 @@
 using namespace phosphor::logging;
 namespace fs = std::filesystem;
 
+namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
+
 namespace additional_data
 {
 constexpr auto rawPEL = "RAWPEL";
@@ -73,7 +76,7 @@
 
         file.close();
 
-        auto pel = std::make_unique<PEL>(data, obmcLogID);
+        auto pel = std::make_unique<openpower::pels::PEL>(data, obmcLogID);
         if (pel->valid())
         {
             // PELs created by others still need these fields set by us.
@@ -132,8 +135,8 @@
     {
         AdditionalData ad{additionalData};
 
-        auto pel = std::make_unique<PEL>(*entry, obmcLogID, timestamp, severity,
-                                         ad, *_dataIface);
+        auto pel = std::make_unique<openpower::pels::PEL>(
+            *entry, obmcLogID, timestamp, severity, ad, *_dataIface);
 
         _repo.add(pel);
 
@@ -160,5 +163,86 @@
     }
 }
 
+sdbusplus::message::unix_fd Manager::getPEL(uint32_t pelID)
+{
+    Repository::LogID id{Repository::LogID::Pel(pelID)};
+    std::optional<int> fd;
+
+    try
+    {
+        fd = _repo.getPELFD(id);
+    }
+    catch (std::exception& e)
+    {
+        throw common_error::InternalFailure();
+    }
+
+    if (!fd)
+    {
+        throw common_error::InvalidArgument();
+    }
+
+    return *fd;
+}
+
+std::vector<uint8_t> Manager::getPELFromOBMCID(uint32_t obmcLogID)
+{
+    Repository::LogID id{Repository::LogID::Obmc(obmcLogID)};
+    std::optional<std::vector<uint8_t>> data;
+
+    try
+    {
+        data = _repo.getPELData(id);
+    }
+    catch (std::exception& e)
+    {
+        throw common_error::InternalFailure();
+    }
+
+    if (!data)
+    {
+        throw common_error::InvalidArgument();
+    }
+
+    return *data;
+}
+
+void Manager::hostAck(uint32_t pelID)
+{
+    Repository::LogID id{Repository::LogID::Pel(pelID)};
+
+    if (!_repo.hasPEL(id))
+    {
+        throw common_error::InvalidArgument();
+    }
+
+    if (_hostNotifier)
+    {
+        _hostNotifier->ackPEL(pelID);
+    }
+}
+
+void Manager::hostReject(uint32_t pelID, RejectionReason reason)
+{
+    Repository::LogID id{Repository::LogID::Pel(pelID)};
+
+    if (!_repo.hasPEL(id))
+    {
+        throw common_error::InvalidArgument();
+    }
+
+    if (_hostNotifier)
+    {
+        if (reason == RejectionReason::BadPEL)
+        {
+            _hostNotifier->setBadPEL(pelID);
+        }
+        else if (reason == RejectionReason::HostFull)
+        {
+            _hostNotifier->setHostFull(pelID);
+        }
+    }
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/manager.hpp b/extensions/openpower-pels/manager.hpp
index 731dd16..7f1fcda 100644
--- a/extensions/openpower-pels/manager.hpp
+++ b/extensions/openpower-pels/manager.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "config.h"
+
 #include "data_interface.hpp"
 #include "host_notifier.hpp"
 #include "log_manager.hpp"
@@ -7,15 +9,21 @@
 #include "registry.hpp"
 #include "repository.hpp"
 
+#include <org/open_power/Logging/PEL/server.hpp>
+#include <sdbusplus/server.hpp>
+
 namespace openpower
 {
 namespace pels
 {
 
+using PELInterface = sdbusplus::server::object::object<
+    sdbusplus::org::open_power::Logging::server::PEL>;
+
 /**
  * @brief PEL manager object
  */
-class Manager
+class Manager : public PELInterface
 {
   public:
     Manager() = delete;
@@ -33,8 +41,8 @@
      */
     Manager(phosphor::logging::internal::Manager& logManager,
             std::unique_ptr<DataInterfaceBase> dataIface) :
-        _logManager(logManager),
-        _repo(getPELRepoPath()),
+        PELInterface(logManager.getBus(), OBJ_LOGGING),
+        _logManager(logManager), _repo(getPELRepoPath()),
         _registry(getMessageRegistryPath() / message::registryFileName),
         _dataIface(std::move(dataIface))
     {
@@ -91,6 +99,57 @@
      */
     bool isDeleteProhibited(uint32_t obmcLogID);
 
+    /**
+     * @brief Return a file descriptor to the raw PEL data
+     *
+     * Throws InvalidArgument if the PEL ID isn't found,
+     * and InternalFailure if anything else fails.
+     *
+     * @param[in] pelID - The PEL ID to get the data for
+     *
+     * @return unix_fd - File descriptor to the file that contains the PEL
+     */
+    sdbusplus::message::unix_fd getPEL(uint32_t pelID) override;
+
+    /**
+     * @brief Returns data for the PEL corresponding to an OpenBMC
+     *        event log.
+     *
+     * @param[in] obmcLogID - The OpenBMC event log ID
+     *
+     * @return vector<uint8_t> - The raw PEL data
+     */
+    std::vector<uint8_t> getPELFromOBMCID(uint32_t obmcLogID) override;
+
+    /**
+     * @brief The D-Bus method called when a host successfully processes
+     *        a PEL.
+     *
+     * This D-Bus method is called from the PLDM daemon when they get an
+     * 'Ack PEL' PLDM message from the host, which indicates the host
+     * firmware successfully sent it to the OS and this code doesn't need
+     * to send it to the host again.
+     *
+     * @param[in] pelID - The PEL ID
+     */
+    void hostAck(uint32_t pelID) override;
+
+    /**
+     * @brief D-Bus method called when the host rejects a PEL.
+     *
+     * This D-Bus method is called from the PLDM daemon when they get an
+     * 'Ack PEL' PLDM message from the host with a payload that says
+     * something when wrong.
+     *
+     * The choices are either:
+     *  * Host Full - The host's staging area is full - try again later
+     *  * Malrformed PEL - The host received an invalid PEL
+     *
+     * @param[in] pelID - The PEL ID
+     * @param[in] reason - One of the above two reasons
+     */
+    void hostReject(uint32_t pelID, RejectionReason reason) override;
+
   private:
     /**
      * @brief Adds a received raw PEL to the PEL repository
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index 1cb310f..6905dc3 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -141,7 +141,9 @@
 pel_manager_test_SOURCES = \
 	%reldir%/pel_manager_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp
 pel_manager_test_CPPFLAGS = $(test_cppflags)
-pel_manager_test_CXXFLAGS = $(test_cxxflags) $(SDEVENTPLUS_CFLAGS)
+pel_manager_test_CXXFLAGS = \
+	$(test_cxxflags) \
+	$(SDEVENTPLUS_CFLAGS)
 pel_manager_test_LDADD = \
 	$(test_ldadd) \
 	$(pel_objects) \
@@ -149,7 +151,9 @@
 	$(top_builddir)/extensions/openpower-pels/host_notifier.o \
 	$(top_builddir)/extensions/openpower-pels/manager.o \
 	$(top_builddir)/extensions/openpower-pels/repository.o
-pel_manager_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS)
+pel_manager_test_LDFLAGS = \
+	$(test_ldflags) \
+	$(SDEVENTPLUS_LIBS)
 
 registry_test_SOURCES = \
 	%reldir%/registry_test.cpp %reldir%/paths.cpp
diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp
index a84ea7a..3036f30 100644
--- a/test/openpower-pels/pel_manager_test.cpp
+++ b/test/openpower-pels/pel_manager_test.cpp
@@ -19,6 +19,7 @@
 
 #include <fstream>
 #include <regex>
+#include <xyz/openbmc_project/Common/error.hpp>
 
 #include <gtest/gtest.h>
 
@@ -166,3 +167,85 @@
     pelFile = findAnyPELInRepo();
     EXPECT_FALSE(pelFile);
 }
+
+TEST_F(ManagerTest, TestDBusMethods)
+{
+    auto bus = sdbusplus::bus::new_default();
+    phosphor::logging::internal::Manager logManager(bus, "logging_path");
+    std::unique_ptr<DataInterfaceBase> dataIface =
+        std::make_unique<DataInterface>(bus);
+
+    Manager manager{logManager, std::move(dataIface)};
+
+    // Create a PEL, write it to a file, and pass that filename into
+    // the create function so there's one in the repo.
+    auto data = pelDataFactory(TestPELType::pelSimple);
+
+    fs::path pelFilename = makeTempDir() / "rawpel";
+    std::ofstream pelFile{pelFilename};
+    pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
+    pelFile.close();
+
+    std::string adItem = "RAWPEL=" + pelFilename.string();
+    std::vector<std::string> additionalData{adItem};
+    std::vector<std::string> associations;
+
+    manager.create("error message", 42, 0,
+                   phosphor::logging::Entry::Level::Error, additionalData,
+                   associations);
+
+    // getPELFromOBMCID
+    auto newData = manager.getPELFromOBMCID(42);
+    EXPECT_EQ(newData.size(), data.size());
+
+    // Read the PEL to get the ID for later
+    PEL pel{newData};
+    auto id = pel.id();
+
+    EXPECT_THROW(
+        manager.getPELFromOBMCID(id + 1),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+    // getPEL
+    auto unixfd = manager.getPEL(id);
+
+    // Get the size
+    struct stat s;
+    int r = fstat(unixfd, &s);
+    ASSERT_EQ(r, 0);
+    auto size = s.st_size;
+
+    // Open the FD and check the contents
+    FILE* fp = fdopen(unixfd, "r");
+    ASSERT_NE(fp, nullptr);
+
+    std::vector<uint8_t> fdData;
+    fdData.resize(size);
+    r = fread(fdData.data(), 1, size, fp);
+    EXPECT_EQ(r, size);
+
+    EXPECT_EQ(newData, fdData);
+
+    fclose(fp);
+
+    EXPECT_THROW(
+        manager.getPEL(id + 1),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+    // hostAck
+    manager.hostAck(id);
+
+    EXPECT_THROW(
+        manager.hostAck(id + 1),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+    // hostReject
+    manager.hostReject(id, Manager::RejectionReason::BadPEL);
+    manager.hostReject(id, Manager::RejectionReason::HostFull);
+
+    EXPECT_THROW(
+        manager.hostReject(id + 1, Manager::RejectionReason::BadPEL),
+        sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
+
+    fs::remove_all(pelFilename.parent_path());
+}