PEL: Pass FFDC files into PEL

In the case where an OpenBMC event log was created with the
'createWithFFDCFiles' D-Bus method, there needs to be UserData PEL
sections created with the contents of these files.

This commit passes these files into the PEL constructor, which then does
the creating.  If any of this would cause the PEL size to go over 16KB,
then that section will be trimmed so it fits in the 16KB, and no more
additional sections will be added.

The function that actually reads the FFDC file and creates the UserData
section is stubbed out and will be implemented in a future commit.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Id27802c91326381a4b80fbe78ab62471cefe0286
diff --git a/extensions/openpower-pels/entry_points.cpp b/extensions/openpower-pels/entry_points.cpp
index 6b2c470..34b3499 100644
--- a/extensions/openpower-pels/entry_points.cpp
+++ b/extensions/openpower-pels/entry_points.cpp
@@ -57,8 +57,8 @@
                Entry::Level severity, const AdditionalDataArg& additionalData,
                const AssociationEndpointsArg& assocs, const FFDCArg& ffdc)
 {
-    // Next: pass through ffdc arg
-    manager->create(message, id, timestamp, severity, additionalData, assocs);
+    manager->create(message, id, timestamp, severity, additionalData, assocs,
+                    ffdc);
 }
 
 REGISTER_EXTENSION_FUNCTION(pelCreate);
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
index 2e6a07e..a0c0929 100644
--- a/extensions/openpower-pels/manager.cpp
+++ b/extensions/openpower-pels/manager.cpp
@@ -24,6 +24,7 @@
 #include <filesystem>
 #include <fstream>
 #include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Logging/Create/server.hpp>
 
 namespace openpower
 {
@@ -36,6 +37,8 @@
 
 namespace common_error = sdbusplus::xyz::openbmc_project::Common::Error;
 
+using Create = sdbusplus::xyz::openbmc_project::Logging::server::Create;
+
 namespace additional_data
 {
 constexpr auto rawPEL = "RAWPEL";
@@ -45,7 +48,8 @@
 void Manager::create(const std::string& message, uint32_t obmcLogID,
                      uint64_t timestamp, Entry::Level severity,
                      const std::vector<std::string>& additionalData,
-                     const std::vector<std::string>& associations)
+                     const std::vector<std::string>& associations,
+                     const FFDCEntries& ffdc)
 {
     AdditionalData ad{additionalData};
 
@@ -66,7 +70,7 @@
         else
         {
             createPEL(message, obmcLogID, timestamp, severity, additionalData,
-                      associations);
+                      associations, ffdc);
         }
     }
 }
@@ -217,11 +221,44 @@
     return false;
 }
 
+PelFFDC Manager::convertToPelFFDC(const FFDCEntries& ffdc)
+{
+    PelFFDC pelFFDC;
+
+    std::for_each(ffdc.begin(), ffdc.end(), [&pelFFDC](const auto& f) {
+        PelFFDCfile pf;
+        pf.subType = std::get<ffdcSubtypePos>(f);
+        pf.version = std::get<ffdcVersionPos>(f);
+        pf.fd = std::get<ffdcFDPos>(f);
+
+        switch (std::get<ffdcFormatPos>(f))
+        {
+            case Create::FFDCFormat::JSON:
+                pf.format = UserDataFormat::json;
+                break;
+            case Create::FFDCFormat::CBOR:
+                pf.format = UserDataFormat::cbor;
+                break;
+            case Create::FFDCFormat::Text:
+                pf.format = UserDataFormat::text;
+                break;
+            case Create::FFDCFormat::Custom:
+                pf.format = UserDataFormat::custom;
+                break;
+        }
+
+        pelFFDC.push_back(pf);
+    });
+
+    return pelFFDC;
+}
+
 void Manager::createPEL(const std::string& message, uint32_t obmcLogID,
                         uint64_t timestamp,
                         phosphor::logging::Entry::Level severity,
                         const std::vector<std::string>& additionalData,
-                        const std::vector<std::string>& associations)
+                        const std::vector<std::string>& associations,
+                        const FFDCEntries& ffdc)
 {
     auto entry = _registry.lookup(message, rg::LookupType::name);
     std::string msg;
@@ -230,8 +267,10 @@
     {
         AdditionalData ad{additionalData};
 
+        auto pelFFDC = convertToPelFFDC(ffdc);
+
         auto pel = std::make_unique<openpower::pels::PEL>(
-            *entry, obmcLogID, timestamp, severity, ad, *_dataIface);
+            *entry, obmcLogID, timestamp, severity, ad, pelFFDC, *_dataIface);
 
         _repo.add(pel);
 
diff --git a/extensions/openpower-pels/manager.hpp b/extensions/openpower-pels/manager.hpp
index 86030d4..d25afbd 100644
--- a/extensions/openpower-pels/manager.hpp
+++ b/extensions/openpower-pels/manager.hpp
@@ -7,6 +7,7 @@
 #include "host_notifier.hpp"
 #include "log_manager.hpp"
 #include "paths.hpp"
+#include "pel.hpp"
 #include "registry.hpp"
 #include "repository.hpp"
 
@@ -86,11 +87,14 @@
      * @param[in] severity - the event log severity
      * @param[in] additionalData - the AdditionalData property
      * @param[in] associations - the Associations property
+     * @param[in] ffdc - A vector of FFDC file information
      */
     void create(const std::string& message, uint32_t obmcLogID,
                 uint64_t timestamp, phosphor::logging::Entry::Level severity,
                 const std::vector<std::string>& additionalData,
-                const std::vector<std::string>& associations);
+                const std::vector<std::string>& associations,
+                const phosphor::logging::FFDCEntries& ffdc =
+                    phosphor::logging::FFDCEntries{});
 
     /**
      * @brief Erase a PEL based on its OpenBMC event log ID
@@ -194,11 +198,13 @@
      * @param[in] severity - The event log severity
      * @param[in] additionalData - The AdditionalData property
      * @param[in] associations - The associations property
+     * @param[in] ffdc - A vector of FFDC file information
      */
     void createPEL(const std::string& message, uint32_t obmcLogID,
                    uint64_t timestamp, phosphor::logging::Entry::Level severity,
                    const std::vector<std::string>& additionalData,
-                   const std::vector<std::string>& associations);
+                   const std::vector<std::string>& associations,
+                   const phosphor::logging::FFDCEntries& ffdc);
 
     /**
      * @brief Schedules a close of the file descriptor to occur from
@@ -239,6 +245,16 @@
     void addESELPEL(const std::string& esel, uint32_t obmcLogID);
 
     /**
+     * @brief Converts the D-Bus FFDC method argument into a data
+     *        structure understood by the PEL code.
+     *
+     * @param[in] ffdc - A vector of FFDC file information
+     *
+     * @return PelFFDC - The PEL FFDC data structure
+     */
+    PelFFDC convertToPelFFDC(const phosphor::logging::FFDCEntries& ffdc);
+
+    /**
      * @brief Reference to phosphor-logging's Manager class
      */
     phosphor::logging::internal::Manager& _logManager;
diff --git a/extensions/openpower-pels/pel.cpp b/extensions/openpower-pels/pel.cpp
index 167d28a..d6c404d 100644
--- a/extensions/openpower-pels/pel.cpp
+++ b/extensions/openpower-pels/pel.cpp
@@ -42,7 +42,7 @@
 
 PEL::PEL(const message::Entry& regEntry, uint32_t obmcLogID, uint64_t timestamp,
          phosphor::logging::Entry::Level severity,
-         const AdditionalData& additionalData,
+         const AdditionalData& additionalData, const PelFFDC& ffdcFiles,
          const DataInterfaceBase& dataIface)
 {
     _ph = std::make_unique<PrivateHeader>(regEntry.componentID, obmcLogID,
@@ -87,6 +87,39 @@
         }
     }
 
+    // Add any FFDC files into UserData sections
+    for (const auto& file : ffdcFiles)
+    {
+        ud = util::makeFFDCuserDataSection(regEntry.componentID, file);
+        if (!ud)
+        {
+            log<level::WARNING>(
+                "Could not make PEL FFDC UserData section from file",
+                entry("COMPONENT_ID=0x%02X", regEntry.componentID),
+                entry("SUBTYPE=0x%X", file.subType),
+                entry("VERSION=0x%X", file.version));
+            continue;
+        }
+
+        // Shrink it if necessary
+        if (size() + ud->header().size > _maxPELSize)
+        {
+            if (!ud->shrink(_maxPELSize - size()))
+            {
+                log<level::WARNING>(
+                    "Could not shrink FFDC UserData section",
+                    entry("COMPONENT_ID=0x%02X", regEntry.componentID),
+                    entry("SUBTYPE=0x%X", file.subType),
+                    entry("VERSION=0x%X", file.version));
+
+                // Give up adding FFDC
+                break;
+            }
+        }
+
+        _optionalSections.push_back(std::move(ud));
+    }
+
     _ph->setSectionCount(2 + _optionalSections.size());
 
     checkRulesAndFix();
@@ -441,6 +474,12 @@
     return makeJSONUserDataSection(json);
 }
 
+std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
+                                                  const PelFFDCfile& file)
+{
+    return std::unique_ptr<UserData>();
+}
+
 } // namespace util
 
 } // namespace pels
diff --git a/extensions/openpower-pels/pel.hpp b/extensions/openpower-pels/pel.hpp
index 0475c1c..abcb673 100644
--- a/extensions/openpower-pels/pel.hpp
+++ b/extensions/openpower-pels/pel.hpp
@@ -6,6 +6,7 @@
 #include "registry.hpp"
 #include "src.hpp"
 #include "user_data.hpp"
+#include "user_data_formats.hpp"
 #include "user_header.hpp"
 
 #include <memory>
@@ -16,6 +17,19 @@
 namespace pels
 {
 
+/**
+ * @brief Contains information about an FFDC file.
+ */
+struct PelFFDCfile
+{
+    UserDataFormat format;
+    uint8_t subType;
+    uint8_t version;
+    int fd;
+};
+
+using PelFFDC = std::vector<PelFFDCfile>;
+
 /** @class PEL
  *
  * @brief This class represents a specific event log format referred to as a
@@ -87,11 +101,12 @@
      * @param[in] timestamp - Timestamp from the event log
      * @param[in] severity - Severity from the event log
      * @param[in] additionalData - The AdditionalData contents
+     * @param[in] ffdcFiles - FFCD files that go into UserData sections
      * @param[in] dataIface - The data interface object
      */
     PEL(const openpower::pels::message::Entry& entry, uint32_t obmcLogID,
         uint64_t timestamp, phosphor::logging::Entry::Level severity,
-        const AdditionalData& additionalData,
+        const AdditionalData& additionalData, const PelFFDC& ffdcFiles,
         const DataInterfaceBase& dataIface);
 
     /**
@@ -359,6 +374,16 @@
 std::unique_ptr<UserData>
     makeSysInfoUserDataSection(const AdditionalData& ad,
                                const DataInterfaceBase& dataIface);
+
+/**
+ * @brief Create a UserData section that contains the data in the file
+ *        pointed to by the file descriptor passed in.
+ *
+ * @param[in] componentID - The component ID of the PEL creator
+ * @param[in] file - The FFDC file information
+ */
+std::unique_ptr<UserData> makeFFDCuserDataSection(uint16_t componentID,
+                                                  const PelFFDCfile& file);
 } // namespace util
 
 } // namespace pels
diff --git a/extensions/openpower-pels/user_data_formats.hpp b/extensions/openpower-pels/user_data_formats.hpp
index 53cb5ee..e890451 100644
--- a/extensions/openpower-pels/user_data_formats.hpp
+++ b/extensions/openpower-pels/user_data_formats.hpp
@@ -7,12 +7,17 @@
 
 enum class UserDataFormat
 {
-    json = 1
+    json = 1,
+    cbor = 2,
+    text = 3,
+    custom = 4
 };
 
 enum class UserDataFormatVersion
 {
-    json = 1
+    json = 1,
+    cbor = 1,
+    text = 1
 };
 
 } // namespace pels
diff --git a/test/openpower-pels/pel_test.cpp b/test/openpower-pels/pel_test.cpp
index ea1842d..c5a1571 100644
--- a/test/openpower-pels/pel_test.cpp
+++ b/test/openpower-pels/pel_test.cpp
@@ -26,6 +26,7 @@
 
 namespace fs = std::filesystem;
 using namespace openpower::pels;
+using ::testing::NiceMock;
 using ::testing::Return;
 
 class PELTest : public CleanLogID
@@ -139,10 +140,11 @@
 
     std::vector<std::string> data{"KEY1=VALUE1"};
     AdditionalData ad{data};
-    MockDataInterface dataIface;
+    NiceMock<MockDataInterface> dataIface;
+    PelFFDC ffdc;
 
-    PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, ad,
-            dataIface};
+    PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+            ad,       ffdc, dataIface};
 
     EXPECT_TRUE(pel.valid());
     EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
@@ -193,6 +195,7 @@
     regEntry.actionFlags = 0xC000;
     regEntry.src.type = 0xBD;
     regEntry.src.reasonCode = 0x1234;
+    PelFFDC ffdc;
 
     // Over the 16KB max PEL size
     std::string bigAD{"KEY1="};
@@ -200,10 +203,10 @@
 
     std::vector<std::string> data{bigAD};
     AdditionalData ad{data};
-    MockDataInterface dataIface;
+    NiceMock<MockDataInterface> dataIface;
 
-    PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, ad,
-            dataIface};
+    PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
+            ad,       ffdc, dataIface};
 
     EXPECT_TRUE(pel.valid());
     EXPECT_EQ(pel.size(), 16384);