Static layout: Read PNOR VERSION using pflash

Use pflash to read VERSION partition to get the PNOR version
Add code to parse pflash's VERSION partition string.

Tested: Verify that the version and extended version are
        retrieved correctly on Romulus and Palmetto.

Change-Id: Ia053c1683a5a969be0773d251cb88c4c5c9b6c60
Signed-off-by: Lei YU <mine260309@gmail.com>
diff --git a/static/item_updater_static.cpp b/static/item_updater_static.cpp
index 95912a5..5937e13 100644
--- a/static/item_updater_static.cpp
+++ b/static/item_updater_static.cpp
@@ -2,13 +2,112 @@
 
 #include "item_updater_static.hpp"
 
+#include "activation.hpp"
+#include "version.hpp"
+
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string>
+
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using namespace phosphor::logging;
+
+// When you see server:: you know we're referencing our base class
+namespace server = sdbusplus::xyz::openbmc_project::Software::server;
+namespace fs = std::filesystem;
+
+namespace utils
+{
+
+template <typename... Ts>
+std::string concat_string(Ts const&... ts)
+{
+    std::stringstream s;
+    ((s << ts << " "), ...) << std::endl;
+    return s.str();
+}
+
+// Helper function to run pflash command
+template <typename... Ts>
+std::string pflash(Ts const&... ts)
+{
+    std::array<char, 512> buffer;
+    std::string cmd = concat_string("pflash", ts...);
+    std::stringstream result;
+    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd.c_str(), "r"),
+                                                  pclose);
+    if (!pipe)
+    {
+        throw std::runtime_error("popen() failed!");
+    }
+    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
+    {
+        result << buffer.data();
+    }
+    return result.str();
+}
+
+std::string getPNORVersion()
+{
+    // A signed version partition will have an extra 4K header starting with
+    // the magic number 17082011 in big endian:
+    // https://github.com/open-power/skiboot/blob/master/libstb/container.h#L47
+
+    constexpr uint8_t MAGIC[] = {0x17, 0x08, 0x20, 0x11};
+    constexpr auto MAGIC_SIZE = sizeof(MAGIC);
+    static_assert(MAGIC_SIZE == 4);
+
+    auto tmp = fs::temp_directory_path();
+    std::string tmpDir(tmp / "versionXXXXXX");
+    if (!mkdtemp(tmpDir.data()))
+    {
+        log<level::ERR>("Failed to create temp dir");
+        return {};
+    }
+
+    fs::path versionFile = tmpDir;
+    versionFile /= "version";
+
+    pflash("-P VERSION -r", versionFile.string(), "2>&1 > /dev/null");
+    std::ifstream f(versionFile.c_str(), std::ios::in | std::ios::binary);
+    uint8_t magic[MAGIC_SIZE];
+    std::string version;
+
+    f.read(reinterpret_cast<char*>(magic), MAGIC_SIZE);
+    f.seekg(0, std::ios::beg);
+    if (std::memcmp(magic, MAGIC, MAGIC_SIZE) == 0)
+    {
+        // Skip the first 4K header
+        f.ignore(4096);
+    }
+
+    getline(f, version, '\0');
+    f.close();
+
+    // Clear the temp dir
+    std::error_code ec;
+    fs::remove_all(tmpDir, ec);
+    if (ec)
+    {
+        log<level::ERR>("Failed to remove temp dir",
+                        entry("DIR=%s", tmpDir.c_str()),
+                        entry("ERR=%s", ec.message().c_str()));
+    }
+
+    return version;
+}
+
+} // namespace utils
+
 namespace openpower
 {
 namespace software
 {
 namespace updater
 {
-
 std::unique_ptr<Activation> ItemUpdaterStatic::createActivationObject(
     const std::string& path, const std::string& versionId,
     const std::string& extVersion,
@@ -36,6 +135,52 @@
 
 void ItemUpdaterStatic::processPNORImage()
 {
+    auto fullVersion = utils::getPNORVersion();
+
+    const auto& [version, extendedVersion] = Version::getVersions(fullVersion);
+    auto id = Version::getId(version);
+
+    auto activationState = server::Activation::Activations::Active;
+    if (version.empty())
+    {
+        log<level::ERR>("Failed to read version",
+                        entry("VERSION=%s", fullVersion.c_str()));
+        activationState = server::Activation::Activations::Invalid;
+    }
+
+    if (extendedVersion.empty())
+    {
+        log<level::ERR>("Failed to read extendedVersion",
+                        entry("VERSION=%s", fullVersion.c_str()));
+        activationState = server::Activation::Activations::Invalid;
+    }
+
+    auto purpose = server::Version::VersionPurpose::Host;
+    auto path = fs::path(SOFTWARE_OBJPATH) / id;
+    AssociationList associations = {};
+
+    if (activationState == server::Activation::Activations::Active)
+    {
+        // Create an association to the host inventory item
+        associations.emplace_back(std::make_tuple(ACTIVATION_FWD_ASSOCIATION,
+                                                  ACTIVATION_REV_ASSOCIATION,
+                                                  HOST_INVENTORY_PATH));
+
+        // Create an active association since this image is active
+        createActiveAssociation(path);
+    }
+
+    // Create Version instance for this version.
+    auto versionPtr = std::make_unique<Version>(
+        bus, path, *this, id, version, purpose, "",
+        std::bind(&ItemUpdaterStatic::erase, this, std::placeholders::_1));
+    versionPtr->deleteObject = std::make_unique<Delete>(bus, path, *versionPtr);
+    versions.insert(std::make_pair(id, std::move(versionPtr)));
+
+    if (!id.empty())
+    {
+        updateFunctionalAssociation(id);
+    }
 }
 
 void ItemUpdaterStatic::reset()
diff --git a/version.cpp b/version.cpp
index 69ab415..d9ad395 100644
--- a/version.cpp
+++ b/version.cpp
@@ -96,6 +96,45 @@
     return keys;
 }
 
+std::pair<std::string, std::string>
+    Version::getVersions(const std::string& versionPart)
+{
+    // versionPart contains strings like below:
+    // open-power-romulus-v2.2-rc1-48-g268344f-dirty
+    //     buildroot-2018.11.1-7-g5d7cc8c
+    //     skiboot-v6.2
+    std::istringstream iss(versionPart);
+    std::string line;
+    std::string version;
+    std::stringstream ss;
+    std::string extendedVersion;
+
+    if (!std::getline(iss, line))
+    {
+        log<level::ERR>("Unable to read from version",
+                        entry("VERSION=%s", versionPart.c_str()));
+        return {};
+    }
+    version = line;
+
+    while (std::getline(iss, line))
+    {
+        // Each line starts with a tab, let's trim it
+        line.erase(line.begin(),
+                   std::find_if(line.begin(), line.end(),
+                                [](int c) { return !std::isspace(c); }));
+        ss << line << ',';
+    }
+    extendedVersion = ss.str();
+
+    // Erase the last ',', if there is one
+    if (!extendedVersion.empty())
+    {
+        extendedVersion.pop_back();
+    }
+    return {version, extendedVersion};
+}
+
 void Delete::delete_()
 {
     if (parent.eraseCallback)
diff --git a/version.hpp b/version.hpp
index 2b5a55a..831b201 100644
--- a/version.hpp
+++ b/version.hpp
@@ -147,6 +147,16 @@
                  std::map<std::string, std::string> keys);
 
     /**
+     * @brief Get version and extended version from VERSION partition string.
+     *
+     * @param[in] versionPart - The string containing the VERSION partition.
+     *
+     * @return The pair contains the version and extended version.
+     **/
+    static std::pair<std::string, std::string>
+        getVersions(const std::string& versionPart);
+
+    /**
      * @brief Calculate the version id from the version string.
      *
      * @details The version id is a unique 8 hexadecimal digit id