start-update-interface: implement update manager

Implement the update manager module for the new daemon inheriting from
the Update D-Bus interface. Link with new interface APIs from
item_updater and software utils. New daemon will continue to exist at
runtime alongwith old flow, until old flow is deprecated and removed.
This change is based on -
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/65738
https://gerrit.openbmc.org/c/openbmc/docs/+/65739

Change-Id: I0ecbbb8fc5340de7f66f8870ae389b405a2debee
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/activation.cpp b/activation.cpp
index 56ce157..a09b580 100644
--- a/activation.cpp
+++ b/activation.cpp
@@ -196,8 +196,11 @@
             std::make_unique<RedundancyPriority>(bus, path, *this, 0);
     }
 
-    // Remove version object from image manager
-    Activation::deleteImageManagerObject();
+    if (!parent.useUpdateDBusInterface)
+    {
+        // Remove version object from image manager
+        Activation::deleteImageManagerObject();
+    }
 
     // Create active association
     parent.createActiveAssociation(path);
@@ -216,6 +219,9 @@
         info("BMC image ready; need reboot to get activated.");
     }
 
+    // Create Update Object for this version.
+    parent.createUpdateObject(versionId, path);
+
     activation(softwareServer::Activation::Activations::Active);
 }
 
diff --git a/item_updater.cpp b/item_updater.cpp
index 247e1d3..356a2ac 100644
--- a/item_updater.cpp
+++ b/item_updater.cpp
@@ -231,6 +231,18 @@
     return true;
 }
 
+void ItemUpdater::createUpdateObject(const std::string& id,
+                                     const std::string& path)
+{
+    if (updateManagers.find(id) != updateManagers.end())
+    {
+        error("UpdateManager object already exists for id: {ID}", "ID", id);
+        return;
+    }
+    updateManagers.insert(
+        std::make_pair(id, std::make_unique<UpdateManager>(ctx, path, *this)));
+}
+
 void ItemUpdater::processBMCImage()
 {
     using VersionClass = phosphor::software::manager::Version;
@@ -391,6 +403,12 @@
                 id, std::make_unique<Activation>(
                         bus, path, *this, id, activationState, associations)));
 
+            // Create Update object for this version.
+            if (useUpdateDBusInterface)
+            {
+                createUpdateObject(id, path);
+            }
+
 #ifdef BMC_STATIC_DUAL_IMAGE
             uint8_t priority;
             if ((functional && (runningImageSlot == 0)) ||
@@ -517,6 +535,13 @@
         this->versions.erase(entryId);
     }
 
+    // Removing entry in updateManagers map
+    auto updateManagerIt = updateManagers.find(entryId);
+    if (updateManagerIt != updateManagers.end())
+    {
+        updateManagers.erase(entryId);
+    }
+
     return;
 }
 
@@ -945,6 +970,11 @@
     biosVersion->deleteObject =
         std::make_unique<phosphor::software::manager::Delete>(
             bus, path, *biosVersion);
+
+    if (useUpdateDBusInterface)
+    {
+        createUpdateObject(versionId, path);
+    }
 }
 #endif
 
diff --git a/item_updater.hpp b/item_updater.hpp
index 1f3c0f1..e1a8d3f 100644
--- a/item_updater.hpp
+++ b/item_updater.hpp
@@ -3,6 +3,7 @@
 #include "activation.hpp"
 #include "item_updater_helper.hpp"
 #include "msl_verify.hpp"
+#include "update_manager.hpp"
 #include "version.hpp"
 #include "xyz/openbmc_project/Collection/DeleteAll/server.hpp"
 
@@ -37,6 +38,7 @@
 using VersionClass = phosphor::software::manager::Version;
 using AssociationList =
     std::vector<std::tuple<std::string, std::string, std::string>>;
+using UpdateManager = phosphor::software::update::Manager;
 
 /** @class MinimumVersion
  *  @brief OpenBMC MinimumVersion implementation.
@@ -80,8 +82,8 @@
                 bool useUpdateDBusInterface = true) :
         ItemUpdaterInherit(ctx.get_bus(), path.c_str(),
                            ItemUpdaterInherit::action::defer_emit),
-        useUpdateDBusInterface(useUpdateDBusInterface), bus(ctx.get_bus()),
-        helper(bus)
+        useUpdateDBusInterface(useUpdateDBusInterface), ctx(ctx),
+        bus(ctx.get_bus()), helper(bus)
     {
         if (!useUpdateDBusInterface)
         {
@@ -171,6 +173,13 @@
                                 ActivationIntf::Activations status);
 
     /**
+     * @brief Create the Update object
+     * @param[in] id - The unique identifier for the update.
+     * @param[in] path - The object path for the update object.
+     */
+    void createUpdateObject(const std::string& id, const std::string& path);
+
+    /**
      * @brief Erase specified entry D-Bus object
      *        if Action property is not set to Active
      *
@@ -302,6 +311,9 @@
      */
     void createFunctionalAssociation(const std::string& path);
 
+    /** @brief D-Bus context */
+    sdbusplus::async::context& ctx;
+
     /** @brief Persistent sdbusplus D-Bus bus connection. */
     sdbusplus::bus_t& bus;
 
@@ -344,6 +356,9 @@
     /** @brief Persistent MinimumVersion D-Bus object */
     std::unique_ptr<MinimumVersion> minimumVersionObject;
 
+    /** @brief Persistent map of Update D-Bus objects and their SwIds */
+    std::map<std::string, std::unique_ptr<UpdateManager>> updateManagers;
+
 #ifdef HOST_BIOS_UPGRADE
     /** @brief Create the BIOS object without knowing the version.
      *
diff --git a/meson.build b/meson.build
index 82326d8..e4808d0 100644
--- a/meson.build
+++ b/meson.build
@@ -249,19 +249,14 @@
 )
 
 software_common_sources = files(
-    'software_utils.cpp'
-)
-
-executable(
-    'phosphor-image-updater',
-    image_updater_sources,
-    software_common_sources,
-    'item_updater_main.cpp',
-    dependencies: [deps, ssl, boost_dep],
-    install: true
+    'software_utils.cpp',
 )
 
 if get_option('software-update-dbus-interface').allowed()
+    image_updater_sources += files(
+        'update_manager.cpp'
+    )
+
     executable(
         'phosphor-software-manager',
         'software_manager.cpp',
@@ -276,6 +271,15 @@
 endif
 
 executable(
+    'phosphor-image-updater',
+    image_updater_sources,
+    software_common_sources,
+    'item_updater_main.cpp',
+    dependencies: [deps, ssl, boost_dep],
+    install: true
+)
+
+executable(
     'phosphor-version-software-manager',
     'image_manager.cpp',
     'image_manager_main.cpp',
diff --git a/software_manager.cpp b/software_manager.cpp
index 15f7dbe..3ee9f1a 100644
--- a/software_manager.cpp
+++ b/software_manager.cpp
@@ -1,27 +1,24 @@
-#include "config.h"
+#include "item_updater.hpp"
 
 #include <phosphor-logging/lg2.hpp>
 #include <sdbusplus/async.hpp>
-#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+using ItemUpdaterIntf = phosphor::software::updater::ItemUpdater;
 
 PHOSPHOR_LOG2_USING;
 
 int main()
 {
     info("Creating Software Manager");
-
     auto path = std::string(SOFTWARE_OBJPATH) + "/bmc";
     sdbusplus::async::context ctx;
-    sdbusplus::server::manager_t manager{ctx, path.c_str()};
+    sdbusplus::server::manager_t manager{ctx, SOFTWARE_OBJPATH};
 
-    // NOLINTNEXTLINE(readability-static-accessed-through-instance)
-    ctx.spawn([=](sdbusplus::async::context& ctx) -> sdbusplus::async::task<> {
-        constexpr auto serviceName = "xyz.openbmc_project.Software.Manager";
-        ctx.request_name(serviceName);
-        co_return;
-    }(ctx));
+    constexpr auto serviceName = "xyz.openbmc_project.Software.Manager";
+
+    ItemUpdaterIntf itemUpdater{ctx, path};
+    ctx.request_name(serviceName);
 
     ctx.run();
-
     return 0;
 }
diff --git a/update_manager.cpp b/update_manager.cpp
new file mode 100644
index 0000000..9d90c95
--- /dev/null
+++ b/update_manager.cpp
@@ -0,0 +1,189 @@
+#include "update_manager.hpp"
+
+#include "item_updater.hpp"
+#include "software_utils.hpp"
+#include "version.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <filesystem>
+
+PHOSPHOR_LOG2_USING;
+
+namespace phosphor::software::update
+{
+
+namespace fs = std::filesystem;
+namespace softwareUtils = phosphor::software::utils;
+using namespace phosphor::logging;
+using Version = phosphor::software::manager::Version;
+using ActivationIntf = phosphor::software::updater::Activation;
+
+void Manager::processImageFailed(sdbusplus::message::unix_fd image,
+                                 std::string& id)
+{
+    close(image);
+    updateInProgress = false;
+    itemUpdater.updateActivationStatus(id,
+                                       ActivationIntf::Activations::Invalid);
+}
+
+// NOLINTNEXTLINE(readability-static-accessed-through-instance)
+auto Manager::processImage(sdbusplus::message::unix_fd image,
+                           ApplyTimeIntf::RequestedApplyTimes applyTime,
+                           std::string id,
+                           std::string objPath) -> sdbusplus::async::task<>
+{
+    debug("Processing image {FD}", "FD", image.fd);
+    fs::path tmpDirPath(std::string{IMG_UPLOAD_DIR});
+    tmpDirPath /= "imageXXXXXX";
+    auto tmpDir = tmpDirPath.string();
+    // Create a tmp dir to copy tarball.
+    if (!mkdtemp(tmpDir.data()))
+    {
+        error("Error ({ERRNO}) occurred during mkdtemp", "ERRNO", errno);
+        processImageFailed(image, id);
+        co_return;
+    }
+
+    std::error_code ec;
+    tmpDirPath = tmpDir;
+    softwareUtils::RemovablePath tmpDirToRemove(tmpDirPath);
+
+    // Untar tarball into the tmp dir
+    if (!softwareUtils::unTar(image, tmpDirPath.string()))
+    {
+        error("Error occurred during untar");
+        processImageFailed(image, id);
+        co_return;
+    }
+
+    fs::path manifestPath = tmpDirPath;
+    manifestPath /= MANIFEST_FILE_NAME;
+
+    // Get version
+    auto version = Version::getValue(manifestPath.string(), "version");
+    if (version.empty())
+    {
+        error("Unable to read version from manifest file");
+        processImageFailed(image, id);
+        co_return;
+    }
+
+    // Get running machine name
+    std::string currMachine = Version::getBMCMachine(OS_RELEASE_FILE);
+    if (currMachine.empty())
+    {
+        auto path = OS_RELEASE_FILE;
+        error("Failed to read machine name from osRelease: {PATH}", "PATH",
+              path);
+        processImageFailed(image, id);
+        co_return;
+    }
+
+    // Get machine name for image to be upgraded
+    std::string machineStr =
+        Version::getValue(manifestPath.string(), "MachineName");
+    if (!machineStr.empty())
+    {
+        if (machineStr != currMachine)
+        {
+            error(
+                "BMC upgrade: Machine name doesn't match: {CURRENT_MACHINE} vs {NEW_MACHINE}",
+                "CURRENT_MACHINE", currMachine, "NEW_MACHINE", machineStr);
+            processImageFailed(image, id);
+            co_return;
+        }
+    }
+    else
+    {
+        warning("No machine name in Manifest file");
+    }
+
+    // Get purpose
+    auto purposeString = Version::getValue(manifestPath.string(), "purpose");
+    if (purposeString.empty())
+    {
+        error("Unable to read purpose from manifest file");
+        processImageFailed(image, id);
+        co_return;
+    }
+    auto convertedPurpose =
+        sdbusplus::message::convert_from_string<Version::VersionPurpose>(
+            purposeString);
+    if (!convertedPurpose)
+    {
+        warning(
+            "Failed to convert manifest purpose ({PURPOSE}) to enum; setting to Unknown.",
+            "PURPOSE", purposeString);
+    }
+    auto purpose = convertedPurpose.value_or(Version::VersionPurpose::Unknown);
+
+    // Get ExtendedVersion
+    std::string extendedVersion =
+        Version::getValue(manifestPath.string(), "ExtendedVersion");
+
+    // Get CompatibleNames
+    std::vector<std::string> compatibleNames =
+        Version::getRepeatedValues(manifestPath.string(), "CompatibleName");
+
+    // Rename IMG_UPLOAD_DIR/imageXXXXXX to IMG_UPLOAD_DIR/id as Manifest
+    // parsing succedded.
+    fs::path imageDirPath = std::string{IMG_UPLOAD_DIR};
+    imageDirPath /= id;
+    fs::rename(tmpDirPath, imageDirPath, ec);
+    tmpDirToRemove.path.clear();
+
+    auto filePath = imageDirPath.string();
+    // Create Version object
+    auto state = itemUpdater.verifyAndCreateObjects(
+        id, objPath, version, purpose, extendedVersion, filePath,
+        compatibleNames);
+    if (state != ActivationIntf::Activations::Ready)
+    {
+        error("Software image is invalid");
+        processImageFailed(image, id);
+        co_return;
+    }
+    if (applyTime == ApplyTimeIntf::RequestedApplyTimes::Immediate ||
+        applyTime == ApplyTimeIntf::RequestedApplyTimes::OnReset)
+    {
+        itemUpdater.requestActivation(id);
+    }
+
+    updateInProgress = false;
+    close(image);
+    co_return;
+}
+
+sdbusplus::message::object_path
+    Manager::startUpdate(sdbusplus::message::unix_fd image,
+                         ApplyTimeIntf::RequestedApplyTimes applyTime)
+{
+    info("Starting update for image {FD}", "FD", static_cast<int>(image));
+    using sdbusplus::xyz::openbmc_project::Common::Error::Unavailable;
+    if (updateInProgress)
+    {
+        error("Failed to start as update is already in progress");
+        report<Unavailable>();
+        return sdbusplus::message::object_path();
+    }
+    updateInProgress = true;
+
+    auto id = Version::getId(std::to_string(randomGen()));
+    auto objPath = std::string{SOFTWARE_OBJPATH} + '/' + id;
+
+    // Create Activation Object
+    itemUpdater.createActivationWithApplyTime(id, objPath, applyTime);
+
+    int newFd = dup(image);
+    ctx.spawn(processImage(newFd, applyTime, id, objPath));
+
+    return sdbusplus::message::object_path(objPath);
+}
+
+} // namespace phosphor::software::update
diff --git a/update_manager.hpp b/update_manager.hpp
new file mode 100644
index 0000000..5214335
--- /dev/null
+++ b/update_manager.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include "config.h"
+
+#include <sdbusplus/async.hpp>
+#include <xyz/openbmc_project/Software/Update/server.hpp>
+
+#include <random>
+#include <string>
+#include <tuple>
+
+namespace phosphor::software::updater
+{
+class ItemUpdater;
+}
+
+namespace phosphor::software::update
+{
+
+using UpdateIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Software::server::Update>;
+using ItemUpdaterIntf = phosphor::software::updater::ItemUpdater;
+
+using ApplyTimeIntf =
+    sdbusplus::common::xyz::openbmc_project::software::ApplyTime;
+
+/** @class Manager
+ *  @brief Processes the image file from update D-Bus interface.
+ *  @details The update manager class handles software updates and manages
+ * software info through version and activation objects.
+ */
+class Manager : public UpdateIntf
+{
+  public:
+    /** @brief Constructs Manager Class
+     *
+     * @param[in] bus - The Dbus bus object
+     */
+    explicit Manager(sdbusplus::async::context& ctx, const std::string& path,
+                     ItemUpdaterIntf& itemUpdater) :
+        UpdateIntf(ctx.get_bus(), path.c_str(), UpdateIntf::action::defer_emit),
+        ctx(ctx), itemUpdater(itemUpdater)
+    {
+        emit_object_added();
+    }
+
+  private:
+    /** @brief Implementation for StartUpdate
+     *  Start a firware update to be performed asynchronously.
+     */
+    sdbusplus::message::object_path
+        startUpdate(sdbusplus::message::unix_fd image,
+                    ApplyTimeIntf::RequestedApplyTimes applyTime) override;
+
+    /* @brief Process the image supplied via image fd */
+    auto processImage(sdbusplus::message::unix_fd image,
+                      ApplyTimeIntf::RequestedApplyTimes applyTime,
+                      std::string id,
+                      std::string objPath) -> sdbusplus::async::task<>;
+
+    /* @brief The handler for the image processing failure  */
+    void processImageFailed(sdbusplus::message::unix_fd image, std::string& id);
+
+    /** @brief The random generator for the software id */
+    std::mt19937 randomGen{static_cast<unsigned>(
+        std::chrono::system_clock::now().time_since_epoch().count())};
+
+    /** @brief D-Bus context */
+    sdbusplus::async::context& ctx;
+    /** @brief item_updater reference */
+    ItemUpdaterIntf& itemUpdater;
+    /** @brief State whether update is in progress */
+    bool updateInProgress = false;
+};
+
+} // namespace phosphor::software::update