Add timestamp to BIOS POST Codes log and DeleteAll interface

Added GetPostCodesWithTimeStamp methods to PostCode interface.
GetPostCodesWithTimeStamp logs timestamp in microseconds since epoch
together with each post code.

Added DeleteAll method to remove existing logs and
reset currentBootCycleIndex to 1.

Tested:
Build with changes in phosphor-dbus-interfaces.
Invoked GetPostCodesWithTimeStamp with busctl and observed expected format:
("US since Epoch":, Post Code)
"1580923291420015": 1,
"1580923291428076": 2,

Invoke DeleteAll and verify GetPostCodes returns no code afterwards,
and CurrentBootiCycleCount is reset to 1.

Cycled host power more than 100 times and observed that CurrentBootCycleCount
increments until it reached MaxBootCycleNum.

Verified GetPostCodes and GetPostCodesWithTimeStamp respond correctly with
index 1 being the most recent boot cycle, CurrentBootCycleCount being
the oldest boot cycle.

Change-Id: I4f222fce0eba38c65f468d8bd186079e0883da6e
Signed-off-by: ZhikuiRen <zhikui.ren@intel.com>
diff --git a/inc/post_code.hpp b/inc/post_code.hpp
index 3c4ac4a..e03358f 100644
--- a/inc/post_code.hpp
+++ b/inc/post_code.hpp
@@ -20,11 +20,14 @@
 #include <cereal/access.hpp>
 #include <cereal/archives/json.hpp>
 #include <cereal/cereal.hpp>
+#include <cereal/types/map.hpp>
 #include <cereal/types/vector.hpp>
+#include <chrono>
 #include <experimental/filesystem>
 #include <fstream>
 #include <iostream>
 #include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 #include <xyz/openbmc_project/State/Boot/PostCode/server.hpp>
 #include <xyz/openbmc_project/State/Host/server.hpp>
@@ -36,6 +39,8 @@
 const static constexpr char *PropertiesIntf = "org.freedesktop.DBus.Properties";
 const static constexpr char *PostCodeListPath =
     "/var/lib/phosphor-post-code-manager/";
+const static constexpr char *CurrentBootCycleCountName =
+    "CurrentBootCycleCount";
 const static constexpr char *CurrentBootCycleIndexName =
     "CurrentBootCycleIndex";
 const static constexpr char *HostStatePath = "/xyz/openbmc_project/state/host0";
@@ -53,11 +58,13 @@
 
 using post_code =
     sdbusplus::xyz::openbmc_project::State::Boot::server::PostCode;
+using delete_all =
+    sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll;
 
-struct PostCode : sdbusplus::server::object_t<post_code>
+struct PostCode : sdbusplus::server::object_t<post_code, delete_all>
 {
     PostCode(sdbusplus::bus::bus &bus, const char *path, EventPtr &event) :
-        sdbusplus::server::object_t<post_code>(bus, path), bus(bus),
+        sdbusplus::server::object_t<post_code, delete_all>(bus, path), bus(bus),
         propertiesChangedSignalRaw(
             bus,
             sdbusplus::bus::match::rules::type::signal() +
@@ -103,17 +110,17 @@
                         if (currentHostState ==
                             StateServer::Host::HostState::Off)
                         {
-                            if (this->currentBootCycleIndex() >=
-                                this->maxBootCycleNum())
+                            if (this->postCodes.empty())
                             {
-                                this->currentBootCycleIndex(1);
+                                std::cerr << "HostState changed to OFF. Empty "
+                                             "postcode log, keep boot cycle at "
+                                          << this->currentBootCycleIndex
+                                          << std::endl;
                             }
                             else
                             {
-                                this->currentBootCycleIndex(
-                                    this->currentBootCycleIndex() + 1);
+                                this->postCodes.clear();
                             }
-                            this->postCodes.clear();
                         }
                     }
                 }
@@ -129,33 +136,41 @@
         deserialize(
             fs::path(strPostCodeListPath + strCurrentBootCycleIndexName),
             index);
-        currentBootCycleIndex(index);
+        currentBootCycleIndex = index;
+        strCurrentBootCycleCountName = CurrentBootCycleCountName;
+        uint16_t count = 0;
+        deserialize(
+            fs::path(strPostCodeListPath + strCurrentBootCycleCountName),
+            count);
+        currentBootCycleCount(count);
         maxBootCycleNum(MaxPostCodeCycles);
-        if (currentBootCycleIndex() >= maxBootCycleNum())
-        {
-            currentBootCycleIndex(1);
-        }
-        else
-        {
-            currentBootCycleIndex(currentBootCycleIndex() + 1);
-        }
     }
     ~PostCode()
     {
     }
 
     std::vector<uint64_t> getPostCodes(uint16_t index) override;
+    std::map<uint64_t, uint64_t>
+        getPostCodesWithTimeStamp(uint16_t index) override;
+    void deleteAll() override;
 
   private:
+    void incrBootCycle();
+    uint16_t getBootNum(const uint16_t index) const;
+
     sdbusplus::bus::bus &bus;
-    std::vector<uint64_t> postCodes;
+    std::chrono::time_point<std::chrono::steady_clock> firstPostCodeTimeSteady;
+    uint64_t firstPostCodeUsSinceEpoch;
+    std::map<uint64_t, uint64_t> postCodes;
     std::string strPostCodeListPath;
     std::string strCurrentBootCycleIndexName;
+    uint16_t currentBootCycleIndex;
+    std::string strCurrentBootCycleCountName;
     void savePostCodes(uint64_t code);
     sdbusplus::bus::match_t propertiesChangedSignalRaw;
     sdbusplus::bus::match_t propertiesChangedSignalCurrentHostState;
     fs::path serialize(const std::string &path);
     bool deserialize(const fs::path &path, uint16_t &index);
     bool deserializePostCodes(const fs::path &path,
-                              std::vector<uint64_t> &codes);
+                              std::map<uint64_t, uint64_t> &codes);
 };
diff --git a/src/post_code.cpp b/src/post_code.cpp
index 8a94911..67a9d8f 100644
--- a/src/post_code.cpp
+++ b/src/post_code.cpp
@@ -14,20 +14,82 @@
 // limitations under the License.
 */
 #include "post_code.hpp"
+
+#include "iomanip"
+
+void PostCode::deleteAll()
+{
+    auto dir = fs::path(PostCodeListPath);
+    std::uintmax_t n = fs::remove_all(dir);
+    std::cerr << "clearPostCodes deleted " << n << " files in "
+              << PostCodeListPath << std::endl;
+    fs::create_directories(dir);
+    postCodes.clear();
+    currentBootCycleIndex = 1;
+    currentBootCycleCount(1);
+}
+
 std::vector<uint64_t> PostCode::getPostCodes(uint16_t index)
 {
-    std::vector<uint64_t> codes;
+    std::vector<uint64_t> codesVec;
+    if (1 == index && !postCodes.empty())
+    {
+        for (auto& code : postCodes)
+            codesVec.push_back(code.second);
+    }
+    else
+    {
+        uint16_t bootNum = getBootNum(index);
 
-    if (currentBootCycleIndex() == index)
+        decltype(postCodes) codes;
+        deserializePostCodes(
+            fs::path(strPostCodeListPath + std::to_string(bootNum)), codes);
+        for (std::pair<uint64_t, uint64_t> code : codes)
+            codesVec.push_back(code.second);
+    }
+    return codesVec;
+}
+
+std::map<uint64_t, uint64_t> PostCode::getPostCodesWithTimeStamp(uint16_t index)
+{
+    if (1 == index && !postCodes.empty())
+    {
         return postCodes;
-    deserializePostCodes(fs::path(strPostCodeListPath + std::to_string(index)),
-                         codes);
+    }
+
+    uint16_t bootNum = getBootNum(index);
+    decltype(postCodes) codes;
+    deserializePostCodes(
+        fs::path(strPostCodeListPath + std::to_string(bootNum)), codes);
     return codes;
 }
+
 void PostCode::savePostCodes(uint64_t code)
 {
-    postCodes.push_back(code);
+    // steady_clock is a monotonic clock that is guaranteed to never be adjusted
+    auto postCodeTimeSteady = std::chrono::steady_clock::now();
+    uint64_t tsUS = std::chrono::duration_cast<std::chrono::microseconds>(
+                        std::chrono::system_clock::now().time_since_epoch())
+                        .count();
+
+    if (postCodes.empty())
+    {
+        firstPostCodeTimeSteady = postCodeTimeSteady;
+        firstPostCodeUsSinceEpoch = tsUS; // uS since epoch for 1st post code
+        incrBootCycle();
+    }
+    else
+    {
+        // calculating tsUS so it is monotonic within the same boot
+        tsUS = firstPostCodeUsSinceEpoch +
+               std::chrono::duration_cast<std::chrono::microseconds>(
+                   postCodeTimeSteady - firstPostCodeTimeSteady)
+                   .count();
+    }
+
+    postCodes.insert(std::make_pair(tsUS, code));
     serialize(fs::path(PostCodeListPath));
+
     return;
 }
 
@@ -35,15 +97,19 @@
 {
     try
     {
-        uint16_t index = currentBootCycleIndex();
-        fs::path fullPath(path + strCurrentBootCycleIndexName);
-        std::ofstream os(fullPath.c_str(), std::ios::binary);
-        cereal::JSONOutputArchive oarchive(os);
-        oarchive(index);
+        fs::path idxPath(path + strCurrentBootCycleIndexName);
+        std::ofstream osIdx(idxPath.c_str(), std::ios::binary);
+        cereal::JSONOutputArchive idxArchive(osIdx);
+        idxArchive(currentBootCycleIndex);
+
+        uint16_t count = currentBootCycleCount();
+        fs::path cntPath(path + strCurrentBootCycleCountName);
+        std::ofstream osCnt(cntPath.c_str(), std::ios::binary);
+        cereal::JSONOutputArchive cntArchive(osCnt);
+        cntArchive(count);
 
         std::ofstream osPostCodes(
-            (path + std::to_string(currentBootCycleIndex())).c_str(),
-            std::ios::binary);
+            (path + std::to_string(currentBootCycleIndex)));
         cereal::JSONOutputArchive oarchivePostCodes(osPostCodes);
         oarchivePostCodes(postCodes);
     }
@@ -87,7 +153,7 @@
 }
 
 bool PostCode::deserializePostCodes(const fs::path& path,
-                                    std::vector<uint64_t>& codes)
+                                    std::map<uint64_t, uint64_t>& codes)
 {
     try
     {
@@ -109,6 +175,36 @@
     {
         return false;
     }
-
     return false;
 }
+
+void PostCode::incrBootCycle()
+{
+    if (currentBootCycleIndex >= maxBootCycleNum())
+    {
+        currentBootCycleIndex = 1;
+    }
+    else
+    {
+        currentBootCycleIndex++;
+    }
+    currentBootCycleCount(std::min(
+        maxBootCycleNum(), static_cast<uint16_t>(currentBootCycleCount() + 1)));
+}
+
+uint16_t PostCode::getBootNum(const uint16_t index) const
+{
+    // bootNum assumes the oldest archive is boot number 1
+    // and the current boot number equals bootCycleCount
+    // map bootNum back to bootIndex that was used to archive postcode
+    uint16_t bootNum = currentBootCycleIndex;
+    if (index > bootNum) // need to wrap around
+    {
+        bootNum = (maxBootCycleNum() + currentBootCycleIndex) - index + 1;
+    }
+    else
+    {
+        bootNum = currentBootCycleIndex - index + 1;
+    }
+    return bootNum;
+}
\ No newline at end of file