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/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