Activation: initially support psu update

Initial support for PSU update by starting a systemd unit with PSU
inventory path and image dir as arguments.

Add an example psu-update@.service that shows how the arguments are
passed to systemd unit and expanded to command line arguments.

Tested: Upload a dummy tarball, create a dummy service that only prints
        the arguments, and verify the service is invoked correctly when
        the RequestedActivation is set to Active.

Signed-off-by: Lei YU <mine260309@gmail.com>
Change-Id: I7e122f1cce234caf4951d3e3daad5bee406b507b
diff --git a/src/activation.cpp b/src/activation.cpp
index 8bfed0b..df84879 100644
--- a/src/activation.cpp
+++ b/src/activation.cpp
@@ -1,5 +1,12 @@
+#include "config.h"
+
 #include "activation.hpp"
 
+#include "utils.hpp"
+
+#include <cassert>
+#include <filesystem>
+
 namespace phosphor
 {
 namespace software
@@ -7,19 +14,114 @@
 namespace updater
 {
 
+constexpr auto SYSTEMD_BUSNAME = "org.freedesktop.systemd1";
+constexpr auto SYSTEMD_PATH = "/org/freedesktop/systemd1";
+constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
+
+namespace fs = std::filesystem;
 namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server;
 
+using SoftwareActivation = softwareServer::Activation;
+
+namespace internal
+{
+/** Construct the systemd service name */
+std::string getUpdateService(const std::string& psuInventoryPath,
+                             const std::string& versionId)
+{
+    fs::path imagePath(IMG_DIR);
+    imagePath /= versionId;
+
+    // The systemd unit shall be escaped
+    std::string args = psuInventoryPath;
+    args += "\\x20";
+    args += imagePath;
+    std::replace(args.begin(), args.end(), '/', '-');
+
+    std::string service = PSU_UPDATE_SERVICE;
+    auto p = service.find('@');
+    assert(p != std::string::npos);
+    service.insert(p + 1, args);
+    return service;
+}
+
+} // namespace internal
 auto Activation::activation(Activations value) -> Activations
 {
-    // TODO
-    return softwareServer::Activation::activation(value);
+    if (value == Status::Activating)
+    {
+        startActivation();
+    }
+    else
+    {
+        // TODO
+    }
+
+    return SoftwareActivation::activation(value);
 }
 
 auto Activation::requestedActivation(RequestedActivations value)
     -> RequestedActivations
 {
-    // TODO
-    return softwareServer::Activation::requestedActivation(value);
+    if ((value == SoftwareActivation::RequestedActivations::Active) &&
+        (SoftwareActivation::requestedActivation() !=
+         SoftwareActivation::RequestedActivations::Active))
+    {
+        if ((activation() == Status::Ready) || (activation() == Status::Failed))
+        {
+            activation(Status::Activating);
+        }
+    }
+    return SoftwareActivation::requestedActivation(value);
+}
+
+void Activation::unitStateChange(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);
+
+    if (newStateUnit == psuUpdateUnit)
+    {
+        if (newStateResult == "done")
+        {
+            finishActivation();
+        }
+        if (newStateResult == "failed" || newStateResult == "dependency")
+        {
+            activation(Status::Failed);
+        }
+    }
+}
+
+void Activation::startActivation()
+{
+    // TODO: for now only update one psu, future commits shall handle update
+    // multiple psus
+    auto psuPaths = utils::getPSUInventoryPath(bus);
+    if (psuPaths.empty())
+    {
+        return;
+    }
+
+    psuUpdateUnit = internal::getUpdateService(psuPaths[0], versionId);
+
+    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                      SYSTEMD_INTERFACE, "StartUnit");
+    method.append(psuUpdateUnit, "replace");
+    bus.call_noreply(method);
+}
+
+void Activation::finishActivation()
+{
+    // TODO: delete the interfaces created by phosphor-software-manager
+    // TODO: delete the old software object
+    // TODO: create related associations
+    activation(Status::Active);
 }
 
 } // namespace updater