PEL: Add function to generate unique PEL IDs

Create generatePELID() to return a unique 4B PEL ID every time it is
called. It will start at a base value, and then increment by 1 each
time.  It uses a file to save the next value to use.

This will be used by the PEL handling code to create unique values
for the error log ID field in the Private Header section.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I841a8dcc5dc48e2b663004be3dccfb114ba366f2
diff --git a/configure.ac b/configure.ac
index 27175a5..ead0a07 100644
--- a/configure.ac
+++ b/configure.ac
@@ -150,6 +150,13 @@
 AC_ARG_ENABLE([openpower-pel-extension],
     AS_HELP_STRING([--enable-openpower-pel-extension], [Create PELs])
 )
+
+AC_ARG_VAR(EXTENSION_PERSIST_DIR, [Base directory for extension persistent data])
+AS_IF([test "x$EXTENSION_PERSIST_DIR" == "x"], \
+    [EXTENSION_PERSIST_DIR="/var/lib/phosphor-logging/extensions"])
+AC_DEFINE_UNQUOTED([EXTENSION_PERSIST_DIR], ["$EXTENSION_PERSIST_DIR"], \
+    [Base directory for extension persistent data])
+
 AM_CONDITIONAL([ENABLE_PEL_EXTENSION], [test "x$enable_openpower_pel_extension" == "xyes"])
 
 AC_CONFIG_HEADERS([config.h])
diff --git a/extensions/openpower-pels/log_id.cpp b/extensions/openpower-pels/log_id.cpp
new file mode 100644
index 0000000..cbe1247
--- /dev/null
+++ b/extensions/openpower-pels/log_id.cpp
@@ -0,0 +1,97 @@
+#include "log_id.hpp"
+
+#include "paths.hpp"
+
+#include <chrono>
+#include <filesystem>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+
+constexpr uint32_t startingLogID = 1;
+constexpr uint32_t bmcLogIDPrefix = 0x50000000;
+
+namespace detail
+{
+
+uint32_t addLogIDPrefix(uint32_t id)
+{
+    // If redundant BMCs are ever a thing, may need a different prefix.
+    return (id & 0x00FFFFFF) | bmcLogIDPrefix;
+}
+
+uint32_t getTimeBasedLogID()
+{
+    using namespace std::chrono;
+
+    // Use 3 bytes of the nanosecond count since the epoch.
+    uint32_t id =
+        duration_cast<nanoseconds>(system_clock::now().time_since_epoch())
+            .count();
+
+    return addLogIDPrefix(id);
+}
+
+} // namespace detail
+
+uint32_t generatePELID()
+{
+    // Note: there isn't a need to be thread safe.
+
+    static std::string idFilename;
+    if (idFilename.empty())
+    {
+        idFilename = getPELIDFile();
+    }
+
+    uint32_t id = 0;
+
+    if (!fs::exists(idFilename))
+    {
+        auto path = fs::path(idFilename).parent_path();
+        if (!fs::exists(path))
+        {
+            fs::create_directories(path);
+        }
+
+        id = startingLogID;
+    }
+    else
+    {
+        std::ifstream idFile{idFilename};
+        idFile >> id;
+        if (idFile.fail())
+        {
+            // Just make up an ID
+            log<level::ERR>("Unable to read PEL ID File!");
+            return detail::getTimeBasedLogID();
+        }
+    }
+
+    // Wrapping shouldn't be a problem, but check anyway
+    if (id == 0x00FFFFFF)
+    {
+        id = startingLogID;
+    }
+
+    std::ofstream idFile{idFilename};
+    idFile << (id + 1);
+    if (idFile.fail())
+    {
+        // Just make up an ID so we don't reuse one next time
+        log<level::ERR>("Unable to write PEL ID File!");
+        return detail::getTimeBasedLogID();
+    }
+
+    return detail::addLogIDPrefix(id);
+}
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/log_id.hpp b/extensions/openpower-pels/log_id.hpp
new file mode 100644
index 0000000..21f04eb
--- /dev/null
+++ b/extensions/openpower-pels/log_id.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <cstdint>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace detail
+{
+
+/**
+ * @brief Adds the 1 byte log creator prefix to the log ID
+ *
+ * @param[in] id - the ID to add it to
+ *
+ * @return - the full log ID
+ */
+uint32_t addLogIDPrefix(uint32_t id);
+
+/**
+ * @brief Generates a PEL ID based on the current time.
+ *
+ * Used for error scenarios where the normal method doesn't
+ * work in order to get a unique ID still.
+ *
+ * @return A unique log ID.
+ */
+uint32_t getTimeBasedLogID();
+
+} // namespace detail
+
+/**
+ * @brief Generates a unique PEL log entry ID every time
+ *        it is called.
+ *
+ * This ID is used at offset 0x2C in the Private Header
+ * section of a PEL.  For single BMC systems, it must
+ * start with 0x50.
+ *
+ * @return uint32_t - The log ID
+ */
+uint32_t generatePELID();
+
+} // namespace pels
+} // namespace openpower
diff --git a/extensions/openpower-pels/openpower-pels.mk b/extensions/openpower-pels/openpower-pels.mk
index cc933f9..2cc2393 100644
--- a/extensions/openpower-pels/openpower-pels.mk
+++ b/extensions/openpower-pels/openpower-pels.mk
@@ -1,6 +1,8 @@
 phosphor_log_manager_SOURCES += \
 	extensions/openpower-pels/bcd_time.cpp \
 	extensions/openpower-pels/entry_points.cpp \
+	extensions/openpower-pels/log_id.cpp \
 	extensions/openpower-pels/manager.cpp \
+	extensions/openpower-pels/paths.cpp \
 	extensions/openpower-pels/private_header.cpp \
 	extensions/openpower-pels/user_header.cpp
diff --git a/extensions/openpower-pels/paths.cpp b/extensions/openpower-pels/paths.cpp
new file mode 100644
index 0000000..dab73c9
--- /dev/null
+++ b/extensions/openpower-pels/paths.cpp
@@ -0,0 +1,23 @@
+#include "config.h"
+
+#include "paths.hpp"
+
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+namespace fs = std::filesystem;
+
+fs::path getPELIDFile()
+{
+    fs::path logIDPath{EXTENSION_PERSIST_DIR};
+    logIDPath /= fs::path{"pels"} / fs::path{"pelID"};
+    return logIDPath;
+}
+
+} // namespace pels
+
+} // namespace openpower
diff --git a/extensions/openpower-pels/paths.hpp b/extensions/openpower-pels/paths.hpp
new file mode 100644
index 0000000..334165c
--- /dev/null
+++ b/extensions/openpower-pels/paths.hpp
@@ -0,0 +1,15 @@
+#pragma once
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+/**
+ * @brief Returns the path to the PEL ID file
+ */
+std::filesystem::path getPELIDFile();
+
+} // namespace pels
+} // namespace openpower
diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include
index f860db2..0e0257c 100644
--- a/test/openpower-pels/Makefile.include
+++ b/test/openpower-pels/Makefile.include
@@ -3,6 +3,7 @@
 check_PROGRAMS += \
 	additional_data_test \
 	bcd_time_test \
+	log_id_test \
 	private_header_test \
 	section_header_test \
 	stream_test \
@@ -58,3 +59,12 @@
 	$(test_ldadd) \
 	$(pel_objects)
 user_header_test_LDFLAGS = $(test_ldflags)
+
+log_id_test_SOURCES = \
+	%reldir%/log_id_test.cpp %reldir%/paths.cpp
+log_id_test_CPPFLAGS = $(test_cppflags)
+log_id_test_CXXFLAGS = $(test_cxxflags)
+log_id_test_LDADD = \
+	$(test_ldadd) \
+	$(top_builddir)/extensions/openpower-pels/log_id.o
+log_id_test_LDFLAGS = $(test_ldflags)
diff --git a/test/openpower-pels/log_id_test.cpp b/test/openpower-pels/log_id_test.cpp
new file mode 100644
index 0000000..e5f2632
--- /dev/null
+++ b/test/openpower-pels/log_id_test.cpp
@@ -0,0 +1,42 @@
+#include "extensions/openpower-pels/log_id.hpp"
+#include "extensions/openpower-pels/paths.hpp"
+
+#include <arpa/inet.h>
+
+#include <filesystem>
+
+#include <gtest/gtest.h>
+
+using namespace openpower::pels;
+namespace fs = std::filesystem;
+
+TEST(LogIdTest, TimeBasedIDTest)
+{
+    uint32_t lastID = 0;
+    for (int i = 0; i < 10; i++)
+    {
+        auto id = detail::getTimeBasedLogID();
+
+        EXPECT_EQ(id & 0xFF000000, 0x50000000);
+        EXPECT_NE(id, lastID);
+        lastID = id;
+    }
+}
+
+TEST(LogIdTest, IDTest)
+{
+    EXPECT_EQ(generatePELID(), 0x50000001);
+    EXPECT_EQ(generatePELID(), 0x50000002);
+    EXPECT_EQ(generatePELID(), 0x50000003);
+    EXPECT_EQ(generatePELID(), 0x50000004);
+    EXPECT_EQ(generatePELID(), 0x50000005);
+    EXPECT_EQ(generatePELID(), 0x50000006);
+
+    auto backingFile = getPELIDFile();
+    fs::remove(backingFile);
+    EXPECT_EQ(generatePELID(), 0x50000001);
+    EXPECT_EQ(generatePELID(), 0x50000002);
+    EXPECT_EQ(generatePELID(), 0x50000003);
+
+    fs::remove_all(fs::path{backingFile}.parent_path());
+}
diff --git a/test/openpower-pels/paths.cpp b/test/openpower-pels/paths.cpp
new file mode 100644
index 0000000..464b92c
--- /dev/null
+++ b/test/openpower-pels/paths.cpp
@@ -0,0 +1,26 @@
+#include "extensions/openpower-pels/paths.hpp"
+
+#include <filesystem>
+
+namespace openpower
+{
+namespace pels
+{
+
+// Use paths that work in unit tests.
+
+std::filesystem::path getPELIDFile()
+{
+    static std::string idFile;
+
+    if (idFile.empty())
+    {
+        char templ[] = "/tmp/logidtestXXXXXX";
+        std::filesystem::path dir = mkdtemp(templ);
+        idFile = dir / "logid";
+    }
+    return idFile;
+}
+
+} // namespace pels
+} // namespace openpower