BMC: Restore version and activation dbus objects on BMC reboot.

- Read the /media/ dir for active bmc versions. Each active
  version has a /etc/os-release inside /media/ which is used to
  recreate the version and activation objects.

Resolves openbmc/openbmc#2137

Change-Id: I40e97396b0912095868172a5a6566e2189a3446b
Signed-off-by: Saqib Khan <khansa@us.ibm.com>
diff --git a/configure.ac b/configure.ac
index 22eece4..c7730a3 100755
--- a/configure.ac
+++ b/configure.ac
@@ -76,6 +76,12 @@
     [The software version manager interface])
 AC_DEFINE(FILEPATH_IFACE, "xyz.openbmc_project.Common.FilePath",
     [The common file path interface])
+AC_DEFINE(OS_RELEASE_FILE, "/etc/os-release",
+    [The name of the BMC table of contents file])
+AC_DEFINE(MEDIA_DIR, "/media/",
+    [The base dir where all RO partitions are mounted])
+AC_DEFINE(BMC_RO_PREFIX, "/media/ro-",
+    [The prefix path for the versioned read-only bmc partitions])
 AC_DEFINE(PERSIST_DIR, "/var/lib/obmc/phosphor-bmc-code-mgmt/",
     [The dir where activation data is stored in files])
 AC_DEFINE(SYSTEMD_BUSNAME, "org.freedesktop.systemd1",
diff --git a/item_updater.cpp b/item_updater.cpp
index aa072ef..7d7df3f 100644
--- a/item_updater.cpp
+++ b/item_updater.cpp
@@ -145,40 +145,88 @@
 
 void ItemUpdater::processBMCImage()
 {
-    using VersionClass = phosphor::software::manager::Version;
-
-    auto purpose = server::Version::VersionPurpose::BMC;
-    auto version = phosphor::software::manager::Version::getBMCVersion();
-    auto id = phosphor::software::manager::Version::getId(version);
-    auto path =  std::string{SOFTWARE_OBJPATH} + '/' + id;
-
     // Create an association to the BMC inventory item
     AssociationList associations{(std::make_tuple(
                                       ACTIVATION_FWD_ASSOCIATION,
                                       ACTIVATION_REV_ASSOCIATION,
                                       bmcInventoryPath))};
 
-    activations.insert(std::make_pair(
-                           id,
-                           std::make_unique<Activation>(
-                               bus,
-                               path,
-                               *this,
-                               id,
-                               server::Activation::Activations::Active,
-                               associations)));
-    versions.insert(std::make_pair(
-                        id,
-                        std::make_unique<VersionClass>(
+    // Read os-release from folders under /media/ to get
+    // BMC Software Versions.
+    for(const auto& iter : fs::directory_iterator(MEDIA_DIR))
+    {
+        auto activationState = server::Activation::Activations::Active;
+        static const auto BMC_RO_PREFIX_LEN = strlen(BMC_RO_PREFIX);
+
+        // Check if the BMC_RO_PREFIXis the prefix of the iter.path
+        if (0 == iter.path().native().compare(0, BMC_RO_PREFIX_LEN,
+                                              BMC_RO_PREFIX))
+        {
+            auto osRelease = iter.path() / OS_RELEASE_FILE;
+            if (!fs::is_regular_file(osRelease))
+            {
+                log<level::ERR>("Failed to read osRelease\n",
+                                entry("FileName=%s", osRelease.string()));
+                activationState = server::Activation::Activations::Invalid;
+            }
+            auto version =
+                    phosphor::software::manager::Version::
+                            getBMCVersion(osRelease);
+            if (version.empty())
+            {
+                log<level::ERR>("Failed to read version from osRelease",
+                                entry("FILENAME=%s", osRelease.string()));
+                activationState = server::Activation::Activations::Invalid;
+            }
+            // The versionId is extracted from the path
+            // for example /media/ro-2a1022fe
+            auto id = iter.path().native().substr(BMC_RO_PREFIX_LEN);
+            auto purpose = server::Version::VersionPurpose::BMC;
+            auto path = fs::path(SOFTWARE_OBJPATH) / id;
+
+            // Create Activation instance for this version.
+            activations.insert(std::make_pair(
+                                   id,
+                                   std::make_unique<Activation>(
+                                       bus,
+                                       path,
+                                       *this,
+                                       id,
+                                       server::Activation::Activations::Active,
+                                       associations)));
+
+            // If Active, create RedundancyPriority instance for this version.
+            if (activationState == server::Activation::Activations::Active)
+            {
+                uint8_t priority = std::numeric_limits<uint8_t>::max();
+                if (!restoreFromFile(id, priority))
+                {
+                    log<level::ERR>("Unable to restore priority from file.",
+                            entry("VERSIONID=%s", id));
+                }
+                activations.find(id)->second->redundancyPriority =
+                        std::make_unique<RedundancyPriority>(
                              bus,
                              path,
-                             version,
-                             purpose,
-                             "",
-                             std::bind(&ItemUpdater::erase,
+                             *(activations.find(id)->second),
+                             priority);
+            }
+
+            // Create Version instance for this version.
+            versions.insert(std::make_pair(
+                                id,
+                                std::make_unique<
+                                     phosphor::software::manager::Version>(
+                                     bus,
+                                     path,
+                                     version,
+                                     purpose,
+                                     "",
+                                     std::bind(&ItemUpdater::erase,
                                        this,
                                        std::placeholders::_1))));
-
+        }
+    }
     return;
 }
 
@@ -186,6 +234,17 @@
 {
     // Delete ReadOnly partitions
     removeReadOnlyPartition(entryId);
+    removeFile(entryId);
+
+    // Remove the priority environment variable.
+    auto serviceFile = "obmc-flash-bmc-setenv@" + entryId + ".service";
+    auto method = bus.new_method_call(
+            SYSTEMD_BUSNAME,
+            SYSTEMD_PATH,
+            SYSTEMD_INTERFACE,
+            "StartUnit");
+    method.append(serviceFile, "replace");
+    bus.call_noreply(method);
 
     // Removing entry in versions map
     auto it = versions.find(entryId);
@@ -212,7 +271,6 @@
     //       If not, don't continue.
 
     this->activations.erase(entryId);
-    removeFile(entryId);
 }
 
 ItemUpdater::ActivationStatus ItemUpdater::validateSquashFSImage(
diff --git a/serialize.cpp b/serialize.cpp
index 8c97c89..da9e878 100644
--- a/serialize.cpp
+++ b/serialize.cpp
@@ -3,6 +3,7 @@
 #include <cereal/archives/json.hpp>
 #include <fstream>
 #include "serialize.hpp"
+#include <sdbusplus/server.hpp>
 
 namespace phosphor
 {
@@ -15,6 +16,8 @@
 
 void storeToFile(std::string versionId, uint8_t priority)
 {
+    auto bus = sdbusplus::bus::new_default();
+
     if(!fs::is_directory(PERSIST_DIR))
     {
         fs::create_directory(PERSIST_DIR);
@@ -24,17 +27,69 @@
     std::ofstream os(path.c_str());
     cereal::JSONOutputArchive oarchive(os);
     oarchive(cereal::make_nvp("priority", priority));
+
+    std::string serviceFile = "obmc-flash-bmc-setenv@" + versionId + "\\x3d" +
+            std::to_string(priority) + ".service";
+    auto method = bus.new_method_call(
+            SYSTEMD_BUSNAME,
+            SYSTEMD_PATH,
+            SYSTEMD_INTERFACE,
+            "StartUnit");
+    method.append(serviceFile, "replace");
+    bus.call_noreply(method);
 }
 
-void restoreFromFile(std::string versionId, uint8_t& priority)
+bool restoreFromFile(std::string versionId, uint8_t& priority)
 {
     std::string path = PERSIST_DIR + versionId;
     if (fs::exists(path))
     {
         std::ifstream is(path.c_str(), std::ios::in);
-        cereal::JSONInputArchive iarchive(is);
-        iarchive(cereal::make_nvp("priority", priority));
+        try
+        {
+            cereal::JSONInputArchive iarchive(is);
+            iarchive(cereal::make_nvp("priority", priority));
+            return true;
+        }
+        catch(cereal::RapidJSONException& e)
+        {
+            fs::remove(path);
+        }
     }
+
+    // Find the mtd device "u-boot-env" to retrieve the environment variables
+    std::ifstream mtdDevices("/proc/mtd");
+    std::string device, devicePath;
+
+    try
+    {
+        while (std::getline(mtdDevices, device)) {
+            if (device.find("u-boot-env") != std::string::npos)
+            {
+                devicePath = "/dev/" + device.substr(0, device.find(':'));
+                break;
+            }
+        }
+
+        if (!devicePath.empty())
+        {
+            std::ifstream input(devicePath.c_str());
+            std::string envVars;
+            std::getline(input, envVars);
+
+            if (envVars.find(versionId) != std::string::npos)
+            {
+                // Grab the environment variable for this versionId. These
+                // variables follow the format "versionId=priority\0"
+                auto var = envVars.substr(envVars.find(versionId));
+                priority = std::stoi(var.substr(var.find('=') + 1));
+                return true;
+            }
+        }
+    }
+    catch (const std::exception& e){}
+
+    return false;
 }
 
 void removeFile(std::string versionId)
diff --git a/serialize.hpp b/serialize.hpp
index 7b6461f..03eea99 100644
--- a/serialize.hpp
+++ b/serialize.hpp
@@ -21,8 +21,9 @@
 /** @brief Serialization function - restores activation information from file
  *  @param[in] versionId - The version for which to retrieve information.
  *  @param[in] priority - RedundancyPriority reference for that version.
+ *  @return true if restore was successful, false if not
  **/
-void restoreFromFile(std::string versionId, uint8_t& priority);
+bool restoreFromFile(std::string versionId, uint8_t& priority);
 
 /** @brief Removes the serial file for a given version.
  *  @param[in] versionId - The version for which to remove a file, if it exists.
diff --git a/version.cpp b/version.cpp
index c7fab30..35df88f 100644
--- a/version.cpp
+++ b/version.cpp
@@ -72,13 +72,13 @@
     return hexId.str();
 }
 
-std::string Version::getBMCVersion()
+std::string Version::getBMCVersion(const std::string& releaseFilePath)
 {
     std::string versionKey = "VERSION_ID=";
     std::string version{};
     std::ifstream efile;
     std::string line;
-    efile.open("/etc/os-release");
+    efile.open(releaseFilePath);
 
     while (getline(efile, line))
     {
diff --git a/version.hpp b/version.hpp
index ef6747f..cbc2bd7 100755
--- a/version.hpp
+++ b/version.hpp
@@ -73,9 +73,12 @@
         /**
          * @brief Get the active bmc version identifier.
          *
+         * @param[in] releaseFilePath - The Path to file which contains
+         *                              the release version.
+         *
          * @return The version identifier.
          */
-        static std::string getBMCVersion();
+        static std::string getBMCVersion(const std::string& releaseFilePath);
 
         /**
          * @brief Delete the d-bus object and image.