From bfa8b769aa57a3e25299640d36dd16d3037d5859 Mon Sep 17 00:00:00 2001
From: roly <Rolyli.Li@luxshare-ict.com>
Date: Mon, 6 Jan 2025 18:11:52 +0800
Subject: [PATCH] Sypport applyoption and updatetarget parameter

---
 activation.cpp               |  11 +-
 activation.hpp               |  38 ++++
 image_async_main.cpp         | 360 +++++++++++++++++++++++++++++++++++
 item_updater.cpp             |  26 ++-
 meson.build                  |   9 +
 meson_options.txt            |   5 +
 obmc-async-update.service.in |  11 ++
 static/flash.cpp             |  77 ++++++--
 8 files changed, 513 insertions(+), 24 deletions(-)
 create mode 100755 image_async_main.cpp
 create mode 100755 obmc-async-update.service.in

diff --git a/activation.cpp b/activation.cpp
index f866461..7fb4038 100644
--- a/activation.cpp
+++ b/activation.cpp
@@ -175,14 +175,14 @@ auto Activation::activation(Activations value) -> Activations
 
 #else // STATIC_LAYOUT
 
-        if (parent.runningImageSlot == 0)
-        {
+        // if (parent.runningImageSlot == 0)
+        // {
             // On primary, update it as before
             onFlashWriteSuccess();
             return softwareServer::Activation::activation(
                 softwareServer::Activation::Activations::Active);
-        }
-        // On secondary, wait for the service to complete
+        // }
+        // // On secondary, wait for the service to complete
 #endif
     }
     else
@@ -198,6 +198,7 @@ void Activation::onFlashWriteSuccess()
 
     activationBlocksTransition.reset(nullptr);
     activationProgress.reset(nullptr);
+    applyOptions.reset();
 
     rwVolumeCreated = false;
     roVolumeCreated = false;
@@ -291,6 +292,8 @@ auto Activation::requestedActivation(RequestedActivations value)
          softwareServer::Activation::RequestedActivations::Active))
     {
         if ((softwareServer::Activation::activation() ==
+             softwareServer::Activation::Activations::AddQueue) ||
+            (softwareServer::Activation::activation() ==
              softwareServer::Activation::Activations::Ready) ||
             (softwareServer::Activation::activation() ==
              softwareServer::Activation::Activations::Failed))
diff --git a/activation.hpp b/activation.hpp
index 7d10472..0c6d8b5 100644
--- a/activation.hpp
+++ b/activation.hpp
@@ -11,6 +11,8 @@
 #include <xyz/openbmc_project/Association/Definitions/server.hpp>
 #include <xyz/openbmc_project/Software/Activation/server.hpp>
 #include <xyz/openbmc_project/Software/ActivationBlocksTransition/server.hpp>
+#include <xyz/openbmc_project/Software/ApplyOptions/server.hpp>
+#include <xyz/openbmc_project/Software/UpdateTarget/server.hpp>
 
 #ifdef WANT_SIGNATURE_VERIFY
 #include <filesystem>
@@ -39,6 +41,10 @@ using RedundancyPriorityInherit = sdbusplus::server::object_t<
     sdbusplus::xyz::openbmc_project::Software::server::RedundancyPriority>;
 using ActivationProgressInherit = sdbusplus::server::object_t<
     sdbusplus::xyz::openbmc_project::Software::server::ActivationProgress>;
+using UpdateTargetInherit = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Software::server::UpdateTarget>;
+using ApplyOptionsInherit = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Software::server::ApplyOptions>;
 
 constexpr auto applyTimeImmediate =
     "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
@@ -166,6 +172,32 @@ class ActivationProgress : public ActivationProgressInherit
     }
 };
 
+class UpdateTarget : public UpdateTargetInherit
+{
+  public:
+    /** @brief Constructs UpdateTarget
+     *
+     * @param[in] bus    - The Dbus bus object
+     * @param[in] path   - The Dbus object path
+     */
+    UpdateTarget(sdbusplus::bus::bus& bus, const std::string& path) :
+        UpdateTargetInherit(bus, path.c_str(), action::emit_interface_added)
+    {}
+};
+
+class ApplyOptions : public ApplyOptionsInherit
+{
+  public:
+    /** @brief Constructs ApplyOptions
+     *
+     * @param[in] bus    - The Dbus bus object
+     * @param[in] path   - The Dbus object path
+     */
+    ApplyOptions(sdbusplus::bus::bus& bus, const std::string& path) :
+        ApplyOptionsInherit(bus, path.c_str(), action::emit_interface_added)
+    {}
+};
+
 /** @class Activation
  *  @brief OpenBMC activation software management implementation.
  *  @details A concrete implementation for
@@ -328,6 +360,12 @@ class Activation : public ActivationInherit, public Flash
     /** @brief Persistent ActivationProgress dbus object */
     std::unique_ptr<ActivationProgress> activationProgress;
 
+    /** @brief Persistent UpdateTarget dbus object */
+    std::unique_ptr<UpdateTarget> updateTarget;
+
+    /** @brief Persistent ApplyOptions dbus object */
+    std::unique_ptr<ApplyOptions> applyOptions;
+
     /** @brief Used to subscribe to dbus systemd signals **/
     sdbusplus::bus::match_t systemdSignals;
 
diff --git a/image_async_main.cpp b/image_async_main.cpp
new file mode 100755
index 0000000..bd17564
--- /dev/null
+++ b/image_async_main.cpp
@@ -0,0 +1,360 @@
+#include <boost/asio.hpp>
+#include <boost/chrono.hpp>
+#include <boost/container/flat_map.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/server.hpp>
+
+#include <deque>
+#include <exception>
+#include <filesystem>
+#include <iostream>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+using namespace phosphor::logging;
+using DbusVariant = std::variant<std::vector<std::string>, std::string, bool>;
+
+static boost::asio::io_context ioCtx;
+static std::shared_ptr<sdbusplus::asio::connection> conn;
+static std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
+static std::shared_ptr<sdbusplus::bus::match::match> match;
+
+static boost::asio::steady_timer checkTimer(ioCtx);
+static boost::asio::steady_timer updateTimer(ioCtx);
+static std::string status{"Idle"};
+
+static std::deque<std::pair<std::string, std::string>> components;
+static bool powerStatus = false;
+static std::unique_ptr<sdbusplus::bus::match_t> powerMatch = nullptr;
+
+static std::string handlerStart();
+
+namespace power
+{
+const static constexpr char* busname = "xyz.openbmc_project.State.Chassis";
+const static constexpr char* interface = "xyz.openbmc_project.State.Chassis";
+const static constexpr char* path = "/xyz/openbmc_project/state/chassis0";
+const static constexpr char* property = "CurrentPowerState";
+} // namespace power
+
+namespace properties
+{
+constexpr const char* interface = "org.freedesktop.DBus.Properties";
+constexpr const char* get = "Get";
+constexpr const char* set = "Set";
+} // namespace properties
+
+static void
+    getPowerStatus(const std::shared_ptr<sdbusplus::asio::connection>& conn,
+                   size_t retries = 2)
+{
+    conn->async_method_call(
+        [conn, retries](boost::system::error_code ec,
+                        const std::variant<std::string>& state) {
+        if (ec)
+        {
+            if (retries != 0U)
+            {
+                auto timer = std::make_shared<boost::asio::steady_timer>(
+                    conn->get_io_context());
+                timer->expires_after(std::chrono::seconds(15));
+                timer->async_wait(
+                    [timer, conn, retries](boost::system::error_code) {
+                    getPowerStatus(conn, retries - 1);
+                });
+                return;
+            }
+
+            // we commonly come up before power control, we'll capture the
+            // property change later
+            lg2::error("error getting power status {ERROR}", "ERROR",
+                       ec.message());
+            return;
+        }
+        powerStatus = std::get<std::string>(state).ends_with(".On");
+    },
+        power::busname, power::path, properties::interface, properties::get,
+        power::interface, power::property);
+}
+
+void setupPowerMonitor(const std::shared_ptr<sdbusplus::asio::connection>& conn)
+{
+    // create a match for powergood changes, first time do a method call to
+    // cache the correct value
+    if (powerMatch)
+    {
+        return;
+    }
+
+    powerMatch = std::make_unique<sdbusplus::bus::match_t>(
+        static_cast<sdbusplus::bus_t&>(*conn),
+        "type='signal',interface='" + std::string(properties::interface) +
+            "',path='" + std::string(power::path) + "',arg0='" +
+            std::string(power::interface) + "'",
+        [](sdbusplus::message_t& message) {
+        std::string objectName;
+        std::map<std::string, std::variant<std::string>> values;
+        message.read(objectName, values);
+        auto findState = values.find(power::property);
+        if (findState != values.end())
+        {
+            powerStatus =
+                std::get<std::string>(findState->second).ends_with(".Running");
+        }
+    });
+
+    getPowerStatus(conn);
+}
+
+/**
+ *  @brief remove image form bmc filesystem.
+ *  @param[in]  imageID - old image.
+ *  @details if image type is same as in queue, remove old image.
+ *
+ */
+void removeExistImage(const std::string& imageID)
+{
+    auto oldImage = "/xyz/openbmc_project/software/" + imageID;
+    conn->async_method_call([](const boost::system::error_code& /*ec*/) {},
+                            "xyz.openbmc_project.Software.BMC.Updater",
+                            oldImage, "xyz.openbmc_project.Object.Delete",
+                            "Delete");
+}
+
+/**
+ *  @brief Async AddQueue method implementation for dbus.
+ *  @param[in]  versionPurpose - VersionPurpose as unique key.
+ *  @param[in]  imageID - get from bmc.
+ *  @details REST API
+ * https://<IP>/xyz/openbmc_project/Software/AsyncUpdate/action/AddQueue for for
+ * web and update-script.
+ *
+ */
+static void handlerAddQueue(std::string& versionPurpose, std::string& imageID)
+{
+    // remove same image from bmc filesystem.
+    // position is same as before in queue
+    bool existImage = false;
+    for (auto& item : components)
+    {
+        if (item.first == versionPurpose)
+        {
+            removeExistImage(item.second);
+            item.second = imageID;
+            existImage = true;
+            break;
+        }
+    }
+    if (!existImage)
+    {
+        components.push_back(std::make_pair(versionPurpose, imageID));
+    }
+    // if Running, keep Running status.
+    if (status == "Idle")
+    {
+        status = "Waiting";
+        interface->set_property("AsyncUpdateStatus", status);
+    }
+    // set the status of async upgrade to add queue
+    auto objectPath = "/xyz/openbmc_project/software/" + imageID;
+    try
+    {
+        conn->async_method_call(
+            [](const boost::system::error_code& /*ec*/) {},
+            "xyz.openbmc_project.Software.BMC.Updater", objectPath,
+            "org.freedesktop.DBus.Properties", "Set",
+            "xyz.openbmc_project.Software.Activation", "Activation",
+            DbusVariant(
+                "xyz.openbmc_project.Software.Activation.Activations.AddQueue"));
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        lg2::error("Can not to set async upgrade status: {ERROR}", "ERROR", e);
+    }
+}
+
+/**
+ *  @brief Get power status.
+ *  @return if true means power is off
+ *  false power on
+ *
+ */
+static bool isPowerOff()
+{
+    return powerStatus == false;
+}
+
+/**
+ *  @brief start update.
+ */
+static void activateImage()
+{
+    status = "Running";
+    interface->set_property("AsyncUpdateStatus", status);
+    auto imagePair = components.front();
+    auto versionId = imagePair.first;
+    auto image = imagePair.second;
+    auto currImage = "/xyz/openbmc_project/software/" + image;
+    try
+    {
+        conn->async_method_call(
+            [](const boost::system::error_code& /*ec*/) {},
+            "xyz.openbmc_project.Software.BMC.Updater", currImage,
+            "org.freedesktop.DBus.Properties", "Set",
+            "xyz.openbmc_project.Software.Activation", "RequestedActivation",
+            DbusVariant(
+                "xyz.openbmc_project.Software.Activation.RequestedActivations.Active"));
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        lg2::error("Can not activate image: {ERROR}", "ERROR", e);
+        return;
+    }
+}
+
+/**
+ *  @brief stop update.
+ */
+static void interruptImage(std::string& image)
+{
+    auto currImage = "/xyz/openbmc_project/software/" + image;
+    try
+    {
+        conn->async_method_call(
+            [](const boost::system::error_code& /*ec*/) {},
+            "xyz.openbmc_project.Software.BMC.Updater", currImage,
+            "org.freedesktop.DBus.Properties", "Set",
+            "xyz.openbmc_project.Software.Activation", "Activation",
+            DbusVariant(
+                "xyz.openbmc_project.Software.Activation.Activations.Interrupted"));
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        lg2::error("Can not interrupt image: {IMAGE} error: {ERROR}", "IMAGE",
+                   image, "ERROR", e);
+        return;
+    }
+}
+
+/**
+ *  @brief dbus method callback.
+ */
+static std::string handlerStart()
+{
+    // when queue is empty return idle status
+    if (components.empty())
+    {
+        if (status == "Running")
+        {
+            status = "Idle";
+        }
+        interface->set_property("AsyncUpdateStatus", status);
+        return status;
+    }
+    // queue is not empty start async update ,use timer to check power status
+    checkTimer.expires_after(boost::asio::chrono::seconds(5));
+    checkTimer.async_wait([&](const boost::system::error_code& ec) {
+        if (ec)
+        {
+            return;
+        }
+        if (!isPowerOff())
+        {
+            if (status == "Running")
+            {
+                lg2::error("power is on, stop async update for queued images.");
+                for (auto& item : components)
+                {
+                    lg2::error(
+                        "stop queued image async update, imageID: {IMAGE}, versionID: {VERSION}",
+                        "IMAGE", item.second, "VERSION", item.first);
+                    interruptImage(item.second);
+                }
+                components.clear();
+
+                status = "Idle";
+                interface->set_property("AsyncUpdateStatus", status);
+                return;
+            }
+            // power is on recheck power status
+            handlerStart();
+            return;
+        }
+        // power is off start async update
+        activateImage();
+    });
+    return status;
+}
+
+/**
+ *  @brief dbus match callback.
+ */
+void matchJobs(sdbusplus::message::message& msg)
+{
+    uint32_t newStateID{};
+    sdbusplus::message::object_path newStateObjPath;
+    std::string newStateUnit{};
+    std::string newStateResult{};
+    // Read the msg and populate each variable
+    msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
+    // normal update will be ignore
+    if (status == "Running")
+    {
+        // if newStateUnit exist image ID, means it is a service job
+        if (newStateUnit.find(components.front().second) != std::string::npos)
+        {
+            if (newStateResult == "done")
+            {
+                lg2::info("async update image finish successfully ({SERVICE})",
+                          "SERVICE", newStateUnit);
+            }
+            else if (newStateResult == "failed")
+            {
+                lg2::error("async update image failed ({SERVICE})", "SERVICE",
+                           newStateUnit);
+            }
+            // when finish, remove image from queue
+            components.pop_front();
+            // start next
+            handlerStart();
+        }
+    }
+}
+
+int main(int /*argc*/, char** /*argv*/)
+{
+    const auto BUS_NAME = "xyz.openbmc_project.Software.AsyncUpdate";
+    const auto OBJECT_PATH = "/xyz/openbmc_project/Software/AsyncUpdate";
+    const auto INTERFACE_NAME = "xyz.openbmc_project.Software.AsyncUpdate";
+    conn = std::make_shared<sdbusplus::asio::connection>(ioCtx);
+    conn->request_name(BUS_NAME);
+
+    setupPowerMonitor(conn);
+    match = std::make_shared<sdbusplus::bus::match::match>(
+        *conn,
+        "type='signal',member='JobRemoved',path='/org/freedesktop/systemd1',interface='org.freedesktop.systemd1.Manager'",
+        matchJobs);
+    sdbusplus::asio::object_server hostServer =
+        sdbusplus::asio::object_server(conn);
+    interface = hostServer.add_interface(OBJECT_PATH, INTERFACE_NAME);
+    interface->register_property(
+        "AsyncUpdateStatus", status,
+        [&](const std::string& requested, std::string& resp) -> int {
+        status = requested;
+        resp = requested;
+        return 1;
+    });
+    interface->register_method("Start", handlerStart);
+    interface->register_method("AddQueue", handlerAddQueue);
+    interface->initialize();
+    ioCtx.run();
+    return 0;
+}
diff --git a/item_updater.cpp b/item_updater.cpp
index 36bf5b3..a09f704 100644
--- a/item_updater.cpp
+++ b/item_updater.cpp
@@ -179,10 +179,15 @@ void ItemUpdater::createActivation(sdbusplus::message_t& msg)
                                                                   *versionPtr);
         versions.insert(std::make_pair(versionId, std::move(versionPtr)));
 
-        activations.insert(std::make_pair(
-            versionId,
-            std::make_unique<Activation>(bus, path, *this, versionId,
-                                         activationState, associations)));
+        if (version == "Mismatch" || version == "Invalid")
+        {
+            activationState = server::Activation::Activations::Invalid;
+        }
+        auto activationPtr = std::make_unique<Activation>(
+            bus, path, *this, versionId, activationState, associations);
+        activationPtr->updateTarget = std::make_unique<UpdateTarget>(bus, path);
+        activationPtr->applyOptions = std::make_unique<ApplyOptions>(bus, path);
+        activations.insert(std::make_pair(versionId, std::move(activationPtr)));
     }
     return;
 }
@@ -768,12 +773,21 @@ void ItemUpdater::resetUbootEnvVars()
 void ItemUpdater::freeSpace([[maybe_unused]] const Activation& caller)
 {
 #ifdef BMC_STATIC_DUAL_IMAGE
-    // For the golden image case, always remove the version on the primary side
+    // For dual image case, erase the slot depends on the updateTarget
+    auto targetSlot = UpdateTarget::TargetSlot::Primary;
+    if (caller.updateTarget)
+    {
+        targetSlot = caller.updateTarget->updateTargetSlot();
+    }
+    auto priorityToErase = targetSlot == UpdateTarget::TargetSlot::Primary ? 0
+                                                                           : 1;
+
     std::string versionIDtoErase;
     for (const auto& iter : activations)
     {
         if (iter.second->redundancyPriority &&
-            iter.second->redundancyPriority.get()->priority() == 0)
+            iter.second->redundancyPriority.get()->priority() ==
+                priorityToErase)
         {
             versionIDtoErase = iter.second->versionId;
             break;
diff --git a/meson.build b/meson.build
index 68f1f91..99d3645 100644
--- a/meson.build
+++ b/meson.build
@@ -247,6 +247,15 @@ if get_option('sync-bmc-files').enabled()
     )
 endif
 
+if get_option('async-update').allowed()
+        executable(
+        'obmc-async-update',
+        'image_async_main.cpp',
+        dependencies: deps,
+        install: true
+    )
+    unit_files += 'obmc-async-update.service.in'
+endif
 if (get_option('verify-signature').enabled() or \
     get_option('verify-full-signature').enabled())
     image_updater_sources += files(
diff --git a/meson_options.txt b/meson_options.txt
index 2109025..53c61fd 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -40,6 +40,11 @@ option(
     description: 'Automatic flash side switch on boot',
 )
 
+option(
+    'async-update', type: 'feature', value: 'enabled',
+    description: 'Enable Async update.',
+)
+
 # Variables
 option(
     'active-bmc-max-allowed', type: 'integer',
diff --git a/obmc-async-update.service.in b/obmc-async-update.service.in
new file mode 100755
index 0000000..d586a55
--- /dev/null
+++ b/obmc-async-update.service.in
@@ -0,0 +1,11 @@
+[Unit]
+Description=BMC async update
+
+[Service]
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.Software.AsyncUpdate
+ExecStart=/usr/bin/obmc-async-update
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/static/flash.cpp b/static/flash.cpp
index 74316d1..812b5de 100644
--- a/static/flash.cpp
+++ b/static/flash.cpp
@@ -31,20 +31,20 @@ using namespace phosphor::software::image;
 
 void Activation::flashWrite()
 {
-#ifdef BMC_STATIC_DUAL_IMAGE
-    if (parent.runningImageSlot != 0)
-    {
-        // It's running on the secondary chip, update the primary one
-        info("Flashing primary flash from secondary, id: {ID}", "ID",
-             versionId);
-        auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
-                                          SYSTEMD_INTERFACE, "StartUnit");
-        auto serviceFile = FLASH_ALT_SERVICE_TMPL + versionId + ".service";
-        method.append(serviceFile, "replace");
-        bus.call_noreply(method);
-        return;
-    }
-#endif
+// #ifdef BMC_STATIC_DUAL_IMAGE
+//     if (parent.runningImageSlot != 0)
+//     {
+//         // It's running on the secondary chip, update the primary one
+//         info("Flashing primary flash from secondary, id: {ID}", "ID",
+//              versionId);
+//         auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+//                                           SYSTEMD_INTERFACE, "StartUnit");
+//         auto serviceFile = FLASH_ALT_SERVICE_TMPL + versionId + ".service";
+//         method.append(serviceFile, "replace");
+//         bus.call_noreply(method);
+//         return;
+//     }
+// #endif
     // For static layout code update, just put images in /run/initramfs.
     // It expects user to trigger a reboot and an updater script will program
     // the image to flash during reboot.
@@ -57,6 +57,55 @@ void Activation::flashWrite()
         fs::copy_file(uploadDir / versionId / bmcImage, toPath / bmcImage,
                       fs::copy_options::overwrite_existing, ec);
     }
+
+    std::string old_prefix = "image-"; 
+    std::string new_prefix = "image-alt-";
+    auto targetSlot = UpdateTarget::TargetSlot::Primary;
+    if (updateTarget)
+    {
+        targetSlot = updateTarget->updateTargetSlot();
+    }
+
+    if (targetSlot == UpdateTarget::TargetSlot::Both)
+    {
+        for (const auto& entry : fs::directory_iterator(toPath)) 
+        { 
+            if (entry.is_regular_file()) 
+            { 
+                std::string old_name = entry.path().filename().string(); 
+                if (old_name.find(old_prefix) == 0 && old_name.find(new_prefix) == std::string::npos) 
+                { 
+                    std::string new_name = new_prefix + old_name.substr(old_prefix.length()); 
+                    fs::copy_file(entry.path(), entry.path().parent_path() / new_name); 
+                }
+            }
+        }
+    }
+    else if (targetSlot == UpdateTarget::TargetSlot::Secondary)
+    {
+        for (const auto& entry : fs::directory_iterator(toPath)) 
+        { 
+            if (entry.is_regular_file()) 
+            { 
+                std::string old_name = entry.path().filename().string(); 
+                if (old_name.find(old_prefix) == 0 && old_name.find(new_prefix) == std::string::npos)  
+                { 
+                    std::string new_name = new_prefix + old_name.substr(old_prefix.length()); 
+                    fs::rename(entry.path(), entry.path().parent_path() / new_name); 
+                }
+            }
+        }
+    }
+
+    auto clearConfig = ApplyOptions::ConfigManagement::Keep;
+    if (applyOptions)
+    {
+        clearConfig = applyOptions->clearConfig();
+    }
+    if (clearConfig == ApplyOptions::ConfigManagement::Clear)
+    {
+        utils::execute("/sbin/fw_setenv", "openbmconce", "factory-reset");
+    }
 }
 
 void Activation::onStateChanges([[maybe_unused]] sdbusplus::message_t& msg)
-- 
2.25.1

