PEL: Add repository to save PELs

Create the Repository class that can save PELs in (and later retrieve
them from) the filesystem.  It provides an add() method that can add
a PEL object to the repository.

Now, when the Manager class sees an OpenBMC event log created with the
RAWPEL metadata in the AdditionalData property that points at a file
that contains a PEL, it can save that PEL.  Before the PEL is saved, the
log ID and commit timestamp fields in the PEL will be updated - the log
ID to a unique value, and the timestamp to the current time.

Change-Id: I8dbaddf0f155bcb6d40b933294ada83feb75ce53
diff --git a/extensions/openpower-pels/manager.cpp b/extensions/openpower-pels/manager.cpp
index 3e22336..a009188 100644
--- a/extensions/openpower-pels/manager.cpp
+++ b/extensions/openpower-pels/manager.cpp
@@ -1,6 +1,10 @@
 #include "manager.hpp"
 
 #include "additional_data.hpp"
+#include "pel.hpp"
+
+#include <filesystem>
+#include <fstream>
 
 namespace openpower
 {
@@ -8,6 +12,7 @@
 {
 
 using namespace phosphor::logging;
+namespace fs = std::filesystem;
 
 namespace additional_data
 {
@@ -36,6 +41,55 @@
 
 void Manager::addRawPEL(const std::string& rawPelPath, uint32_t obmcLogID)
 {
+    if (fs::exists(rawPelPath))
+    {
+        std::ifstream file(rawPelPath, std::ios::in | std::ios::binary);
+
+        auto data = std::vector<uint8_t>(std::istreambuf_iterator<char>(file),
+                                         std::istreambuf_iterator<char>());
+        if (file.fail())
+        {
+            log<level::ERR>("Filesystem error reading a raw PEL",
+                            entry("PELFILE=%s", rawPelPath.c_str()),
+                            entry("OBMCLOGID=%d", obmcLogID));
+            // TODO, Decide what to do here. Maybe nothing.
+            return;
+        }
+
+        file.close();
+
+        auto pel = std::make_unique<PEL>(data, obmcLogID);
+        if (pel->valid())
+        {
+            // PELs created by others still need these fields set by us.
+            pel->assignID();
+            pel->setCommitTime();
+
+            try
+            {
+                _repo.add(pel);
+            }
+            catch (std::exception& e)
+            {
+                // Probably a full or r/o filesystem, not much we can do.
+                log<level::ERR>("Unable to add PEL to Repository",
+                                entry("PEL_ID=0x%X", pel->id()));
+            }
+        }
+        else
+        {
+            log<level::ERR>("Invalid PEL found",
+                            entry("PELFILE=%s", rawPelPath.c_str()),
+                            entry("OBMCLOGID=%d", obmcLogID));
+            // TODO, make a whole new OpenBMC event log + PEL
+        }
+    }
+    else
+    {
+        log<level::ERR>("Raw PEL file from BMC event log does not exist",
+                        entry("PELFILE=%s", (rawPelPath).c_str()),
+                        entry("OBMCLOGID=%d", obmcLogID));
+    }
 }
 
 void Manager::erase(uint32_t obmcLogID)
diff --git a/extensions/openpower-pels/manager.hpp b/extensions/openpower-pels/manager.hpp
index e57be98..3134f23 100644
--- a/extensions/openpower-pels/manager.hpp
+++ b/extensions/openpower-pels/manager.hpp
@@ -2,6 +2,8 @@
 
 #include "elog_entry.hpp"
 #include "log_manager.hpp"
+#include "paths.hpp"
+#include "repository.hpp"
 
 namespace openpower
 {
@@ -28,7 +30,8 @@
      *
      * @param[in] logManager - internal::Manager object
      */
-    explicit Manager(internal::Manager& logManager) : _logManager(logManager)
+    explicit Manager(internal::Manager& logManager) :
+        _logManager(logManager), _repo(getPELRepoPath())
     {
     }
 
@@ -96,6 +99,11 @@
      * @brief Reference to phosphor-logging's Manager class
      */
     internal::Manager& _logManager;
+
+    /**
+     * @brief The PEL repository object
+     */
+    Repository _repo;
 };
 
 } // namespace pels
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index 04106fd..def28a1 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -6,4 +6,5 @@
 	extensions/openpower-pels/paths.cpp \
 	extensions/openpower-pels/pel.cpp \
 	extensions/openpower-pels/private_header.cpp \
+	extensions/openpower-pels/repository.cpp \
 	extensions/openpower-pels/user_header.cpp
diff --git a/extensions/openpower-pels/paths.cpp b/extensions/openpower-pels/paths.cpp
index dab73c9..27e7373 100644
--- a/extensions/openpower-pels/paths.cpp
+++ b/extensions/openpower-pels/paths.cpp
@@ -18,6 +18,13 @@
     return logIDPath;
 }
 
+fs::path getPELRepoPath()
+{
+    std::filesystem::path repoPath{EXTENSION_PERSIST_DIR};
+    repoPath /= "pels";
+    return repoPath;
+}
+
 } // namespace pels
 
 } // namespace openpower
diff --git a/extensions/openpower-pels/paths.hpp b/extensions/openpower-pels/paths.hpp
index 334165c..64566d2 100644
--- a/extensions/openpower-pels/paths.hpp
+++ b/extensions/openpower-pels/paths.hpp
@@ -11,5 +11,10 @@
  */
 std::filesystem::path getPELIDFile();
 
+/**
+ * @brief Returns the path to the PEL repository
+ */
+std::filesystem::path getPELRepoPath();
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/repository.cpp b/extensions/openpower-pels/repository.cpp
new file mode 100644
index 0000000..c9f9521
--- /dev/null
+++ b/extensions/openpower-pels/repository.cpp
@@ -0,0 +1,71 @@
+#include "repository.hpp"
+
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/File/error.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error;
+
+Repository::Repository(const std::filesystem::path& basePath) :
+    _logPath(basePath / "logs")
+{
+    if (!fs::exists(_logPath))
+    {
+        fs::create_directories(_logPath);
+    }
+}
+
+std::string Repository::getPELFilename(uint32_t pelID, const BCDTime& time)
+{
+    char name[50];
+    sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", time.yearMSB,
+            time.yearLSB, time.month, time.day, time.hour, time.minutes,
+            time.seconds, time.hundredths, pelID);
+    return std::string{name};
+}
+
+void Repository::add(std::unique_ptr<PEL>& pel)
+{
+    auto path = _logPath / getPELFilename(pel->id(), pel->commitTime());
+    std::ofstream file{path, std::ios::binary};
+
+    if (!file.good())
+    {
+        // If this fails, the filesystem is probably full so it isn't like
+        // we could successfully create yet another error log here.
+        auto e = errno;
+        log<level::ERR>("Failed creating PEL file",
+                        entry("FILE=%s", path.c_str()));
+        fs::remove(path);
+        log<level::ERR>("Unable to open PEL file for writing",
+                        entry("ERRNO=%d", e), entry("PATH=%s", path.c_str()));
+        throw file_error::Open();
+    }
+
+    auto data = pel->data();
+    file.write(reinterpret_cast<const char*>(data.data()), data.size());
+
+    if (file.fail())
+    {
+        // Same note as above about not being able to create an error log
+        // for this case even if we wanted.
+        auto e = errno;
+        log<level::ERR>("Failed writing PEL file",
+                        entry("FILE=%s", path.c_str()));
+        file.close();
+        fs::remove(path);
+        log<level::ERR>("Unable to write PEL file", entry("ERRNO=%d", e),
+                        entry("PATH=%s", path.c_str()));
+        throw file_error::Write();
+    }
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/repository.hpp b/extensions/openpower-pels/repository.hpp
new file mode 100644
index 0000000..5b15506
--- /dev/null
+++ b/extensions/openpower-pels/repository.hpp
@@ -0,0 +1,62 @@
+#pragma once
+#include "bcd_time.hpp"
+#include "pel.hpp"
+
+#include <algorithm>
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @class Repository
+ *
+ * The class handles saving and retrieving PELs on the BMC.
+ */
+class Repository
+{
+  public:
+    Repository() = delete;
+    ~Repository() = default;
+    Repository(const Repository&) = default;
+    Repository& operator=(const Repository&) = default;
+    Repository(Repository&&) = default;
+    Repository& operator=(Repository&&) = default;
+
+    /**
+     * @brief Constructor
+     *
+     * @param[in] basePath - the base filesystem path for the repository
+     */
+    Repository(const std::filesystem::path& basePath);
+
+    /**
+     * @brief Adds a PEL to the repository
+     *
+     * Throws File.Error.Open or File.Error.Write exceptions on failure
+     *
+     * @param[in] pel - the PEL to add
+     */
+    void add(std::unique_ptr<PEL>& pel);
+
+    /**
+     * @brief Generates the filename to use for the PEL ID and BCDTime.
+     *
+     * @param[in] pelID - the PEL ID
+     * @param[in] time - the BCD time
+     *
+     * @return string - A filename string of <BCD_time>_<pelID>
+     */
+    static std::string getPELFilename(uint32_t pelID, const BCDTime& time);
+
+  private:
+    /**
+     * @brief The filesystem path to the PEL logs.
+     */
+    const std::filesystem::path _logPath;
+};
+
+} // namespace pels
+} // namespace openpower