PEL: Add repo function to get a PEL FD

Add Repository::getPELFD() to return a file descriptor to a PEL data
file based on its ID.

This will be used by a future D-Bus method to return the descriptor to
the PLDM daemon.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I1110a514e30a9387d9672e42862139b988717c53
diff --git a/extensions/openpower-pels/repository.cpp b/extensions/openpower-pels/repository.cpp
index f90a628..3bf3cb0 100644
--- a/extensions/openpower-pels/repository.cpp
+++ b/extensions/openpower-pels/repository.cpp
@@ -197,6 +197,29 @@
     return std::nullopt;
 }
 
+std::optional<sdbusplus::message::unix_fd> Repository::getPELFD(const LogID& id)
+{
+    auto pel = findPEL(id);
+    if (pel != _pelAttributes.end())
+    {
+        FILE* fp = fopen(pel->second.path.c_str(), "rb");
+
+        if (fp == nullptr)
+        {
+            auto e = errno;
+            log<level::ERR>("Unable to open PEL File", entry("ERRNO=%d", e),
+                            entry("PATH=%s", pel->second.path.c_str()));
+            throw file_error::Open();
+        }
+
+        // Must leave the file open here.  It will be closed by sdbusplus
+        // when it sends it back over D-Bus.
+
+        return fileno(fp);
+    }
+    return std::nullopt;
+}
+
 void Repository::for_each(ForEachFunc func) const
 {
     for (const auto& [id, attributes] : _pelAttributes)
diff --git a/extensions/openpower-pels/repository.hpp b/extensions/openpower-pels/repository.hpp
index b6ac545..e55808f 100644
--- a/extensions/openpower-pels/repository.hpp
+++ b/extensions/openpower-pels/repository.hpp
@@ -169,6 +169,16 @@
      */
     std::optional<std::vector<uint8_t>> getPELData(const LogID& id);
 
+    /**
+     * @brief Get a file descriptor to the PEL data
+     *
+     * @param[in] id - The ID to get the FD for
+     *
+     * @return std::optional<sdbusplus::message::unix_fd> -
+     *         The FD, or an empty optional object.
+     */
+    std::optional<sdbusplus::message::unix_fd> getPELFD(const LogID& id);
+
     using ForEachFunc = std::function<bool(const PEL&)>;
 
     /**
diff --git a/test/openpower-pels/repository_test.cpp b/test/openpower-pels/repository_test.cpp
index 5c4db47..446e10e 100644
--- a/test/openpower-pels/repository_test.cpp
+++ b/test/openpower-pels/repository_test.cpp
@@ -386,3 +386,51 @@
         EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked);
     }
 }
+
+TEST_F(RepositoryTest, TestGetPELFD)
+{
+    Repository repo{repoPath};
+
+    auto data = pelDataFactory(TestPELType::pelSimple);
+    auto pel = std::make_unique<PEL>(data);
+    pel->setCommitTime();
+    pel->assignID();
+
+    repo.add(pel);
+
+    using ID = Repository::LogID;
+    ID id{ID::Pel(pel->id())};
+
+    auto fd = repo.getPELFD(id);
+
+    EXPECT_TRUE(fd);
+
+    // Get the size
+    struct stat s;
+    int r = fstat(*fd, &s);
+    ASSERT_EQ(r, 0);
+
+    auto size = s.st_size;
+
+    // Read the PEL data out of the FD
+    FILE* fp = fdopen(*fd, "r");
+    ASSERT_NE(fp, nullptr);
+
+    std::vector<uint8_t> newData;
+    newData.resize(size);
+    r = fread(newData.data(), 1, size, fp);
+    EXPECT_EQ(r, size);
+
+    PEL newPEL{newData};
+
+    EXPECT_TRUE(newPEL.valid());
+    EXPECT_EQ(newPEL.id(), pel->id());
+
+    fclose(fp);
+
+    // Call getPELFD again, this time with a bad ID
+    id.pelID.id = 42;
+    fd = repo.getPELFD(id);
+
+    EXPECT_FALSE(fd);
+}