Implement Software.Extended Version

- Populate the ExtendedVersion object from MANIFEST
- Create an ExtendedVersion object

Change-Id: I13e352d4cb8f4a73259a940f341a736fe7e9305f
Signed-off-by: Chanh Nguyen <chanh@amperemail.onmicrosoft.com>
diff --git a/image_manager.cpp b/image_manager.cpp
index 36ec0bc..dae8fdf 100644
--- a/image_manager.cpp
+++ b/image_manager.cpp
@@ -171,6 +171,10 @@
                         " Setting to Unknown.");
     }
 
+    // Get ExtendedVersion
+    std::string extendedVersion =
+        Version::getValue(manifestPath.string(), "ExtendedVersion");
+
     // Compute id
     auto id = Version::getId(version);
 
@@ -200,7 +204,8 @@
     {
         // Create Version object
         auto versionPtr = std::make_unique<Version>(
-            bus, objPath, version, purpose, imageDirPath.string(),
+            bus, objPath, version, purpose, extendedVersion,
+            imageDirPath.string(),
             std::bind(&Manager::erase, this, std::placeholders::_1));
         versionPtr->deleteObject =
             std::make_unique<phosphor::software::manager::Delete>(bus, objPath,
diff --git a/item_updater.cpp b/item_updater.cpp
index d831217..e8196af 100644
--- a/item_updater.cpp
+++ b/item_updater.cpp
@@ -5,6 +5,7 @@
 #include "images.hpp"
 #include "serialize.hpp"
 #include "version.hpp"
+#include "xyz/openbmc_project/Software/ExtendedVersion/server.hpp"
 #include "xyz/openbmc_project/Software/Version/server.hpp"
 
 #include <phosphor-logging/elog-errors.hpp>
@@ -45,6 +46,7 @@
 
     sdbusplus::message::object_path objPath;
     auto purpose = VersionPurpose::Unknown;
+    std::string extendedVersion;
     std::string version;
     std::map<std::string, std::map<std::string, std::variant<std::string>>>
         interfaces;
@@ -87,6 +89,16 @@
                 }
             }
         }
+        else if (intf.first == EXTENDED_VERSION_IFACE)
+        {
+            for (const auto& property : intf.second)
+            {
+                if (property.first == "ExtendedVersion")
+                {
+                    extendedVersion = std::get<std::string>(property.second);
+                }
+            }
+        }
     }
     if (version.empty() || filePath.empty() ||
         purpose == VersionPurpose::Unknown)
@@ -132,7 +144,7 @@
                                          activationState, associations)));
 
         auto versionPtr = std::make_unique<VersionClass>(
-            bus, path, version, purpose, filePath,
+            bus, path, version, purpose, extendedVersion, filePath,
             std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
         versionPtr->deleteObject =
             std::make_unique<phosphor::software::manager::Delete>(bus, path,
@@ -221,6 +233,10 @@
             auto purpose = server::Version::VersionPurpose::BMC;
             restorePurpose(id, purpose);
 
+            // Read os-release from /etc/ to get the BMC extended version
+            std::string extendedVersion =
+                VersionClass::getBMCExtendedVersion(osRelease);
+
             auto path = fs::path(SOFTWARE_OBJPATH) / id;
 
             // Create functional association if this is the functional
@@ -249,7 +265,7 @@
 
             // Create Version instance for this version.
             auto versionPtr = std::make_unique<VersionClass>(
-                bus, path, version, purpose, "",
+                bus, path, version, purpose, extendedVersion, "",
                 std::bind(&ItemUpdater::erase, this, std::placeholders::_1));
             auto isVersionFunctional = versionPtr->isFunctional();
             if (!isVersionFunctional)
diff --git a/meson.build b/meson.build
index 0a7a6a6..0e8a3c0 100644
--- a/meson.build
+++ b/meson.build
@@ -25,6 +25,7 @@
 conf.set_quoted('SYSTEMD_INTERFACE', 'org.freedesktop.systemd1.Manager')
 conf.set_quoted('VERSION_BUSNAME', 'xyz.openbmc_project.Software.Version')
 conf.set_quoted('VERSION_IFACE', 'xyz.openbmc_project.Software.Version')
+conf.set_quoted('EXTENDED_VERSION_IFACE', 'xyz.openbmc_project.Software.ExtendedVersion')
 
 # Names of the forward and reverse associations
 conf.set_quoted('ACTIVATION_FWD_ASSOCIATION', 'inventory')
diff --git a/test/utest.cpp b/test/utest.cpp
index 027ed0e..97d2ea4 100644
--- a/test/utest.cpp
+++ b/test/utest.cpp
@@ -125,6 +125,21 @@
     EXPECT_EQ(Version::getId(version), hexId);
 }
 
+TEST_F(VersionTest, TestGetExtendedVersion)
+{
+    auto releasePath = _directory + "/" + "os-release";
+    auto ExtendedVersion = "9.88.1-test-ExtendedVersion";
+
+    std::ofstream file;
+    file.open(releasePath, std::ofstream::out);
+    ASSERT_TRUE(file.is_open());
+
+    file << "EXTENDED_VERSION=" << ExtendedVersion << "\n";
+    file.close();
+
+    EXPECT_EQ(Version::getBMCExtendedVersion(releasePath), ExtendedVersion);
+}
+
 class SignatureTest : public testing::Test
 {
     static constexpr auto opensslCmd = "openssl dgst -sha256 -sign ";
diff --git a/version.cpp b/version.cpp
index 18f3f4f..04f40c4 100644
--- a/version.cpp
+++ b/version.cpp
@@ -127,6 +127,30 @@
     return machine;
 }
 
+std::string Version::getBMCExtendedVersion(const std::string& releaseFilePath)
+{
+    std::string extendedVersionKey = "EXTENDED_VERSION=";
+    std::string extendedVersionValue{};
+    std::string extendedVersion{};
+    std::ifstream efile(releaseFilePath);
+    std::string line;
+
+    while (getline(efile, line))
+    {
+        if (line.substr(0, extendedVersionKey.size())
+                .find(extendedVersionKey) != std::string::npos)
+        {
+            extendedVersionValue = line.substr(extendedVersionKey.size());
+            std::size_t pos = extendedVersionValue.find_first_of('"') + 1;
+            extendedVersion = extendedVersionValue.substr(
+                pos, extendedVersionValue.find_last_of('"') - pos);
+            break;
+        }
+    }
+
+    return extendedVersion;
+}
+
 std::string Version::getBMCVersion(const std::string& releaseFilePath)
 {
     std::string versionKey = "VERSION_ID=";
diff --git a/version.hpp b/version.hpp
index 9cf76da..a93709f 100644
--- a/version.hpp
+++ b/version.hpp
@@ -2,6 +2,7 @@
 
 #include "xyz/openbmc_project/Common/FilePath/server.hpp"
 #include "xyz/openbmc_project/Object/Delete/server.hpp"
+#include "xyz/openbmc_project/Software/ExtendedVersion/server.hpp"
 #include "xyz/openbmc_project/Software/Version/server.hpp"
 
 #include <sdbusplus/bus.hpp>
@@ -19,6 +20,7 @@
 typedef std::function<void(std::string)> eraseFunc;
 
 using VersionInherit = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Software::server::ExtendedVersion,
     sdbusplus::xyz::openbmc_project::Software::server::Version,
     sdbusplus::xyz::openbmc_project::Common::server::FilePath>;
 using DeleteInherit = sdbusplus::server::object::object<
@@ -70,16 +72,19 @@
      * @param[in] objPath        - The D-Bus object path
      * @param[in] versionString  - The version string
      * @param[in] versionPurpose - The version purpose
+     * @param[in] extVersion     - The extended version
      * @param[in] filePath       - The image filesystem path
      * @param[in] callback       - The eraseFunc callback
      */
     Version(sdbusplus::bus::bus& bus, const std::string& objPath,
             const std::string& versionString, VersionPurpose versionPurpose,
-            const std::string& filePath, eraseFunc callback) :
+            std::string& extVersion, const std::string& filePath,
+            eraseFunc callback) :
         VersionInherit(bus, (objPath).c_str(), true),
         eraseCallback(callback), versionStr(versionString)
     {
         // Set properties.
+        extendedVersion(extVersion);
         purpose(versionPurpose);
         version(versionString);
         path(filePath);
@@ -118,6 +123,17 @@
     static std::string getBMCMachine(const std::string& releaseFilePath);
 
     /**
+     * @brief Get the BMC Extended Version string.
+     *
+     * @param[in] releaseFilePath - The path to the file which contains
+     *                              the release machine string.
+     *
+     * @return The extended version string.
+     */
+    static std::string
+        getBMCExtendedVersion(const std::string& releaseFilePath);
+
+    /**
      * @brief Get the active BMC version string.
      *
      * @param[in] releaseFilePath - The path to the file which contains