Activation: Wait for service files to complete

The Activation process calls a pair of service files upon which
later steps are dependent. These units take some time to run, so
the later steps would fail. This commit subscribes the Activation
object to systemd signals and puts the later steps on hold until
the service files have finished running.

Additionally, the service file used to move the squashfs to flash
is moved into the activation process, so not all images that the
user downloads are saved to flash.

Resolves openbmc/openbmc#1716

Change-Id: Id3ecf6334e069f69c355f0c0e8901a93fd95d496
Signed-off-by: Michael Tritz <mtritz@us.ibm.com>
diff --git a/activation.cpp b/activation.cpp
index eeefbef..fb7d764 100755
--- a/activation.cpp
+++ b/activation.cpp
@@ -12,6 +12,20 @@
 namespace fs = std::experimental::filesystem;
 namespace softwareServer = sdbusplus::xyz::openbmc_project::Software::server;
 
+constexpr auto SYSTEMD_SERVICE   = "org.freedesktop.systemd1";
+constexpr auto SYSTEMD_OBJ_PATH  = "/org/freedesktop/systemd1";
+
+void Activation::subscribeToSystemdSignals()
+{
+    auto method = this->bus.new_method_call(SYSTEMD_SERVICE,
+                                            SYSTEMD_OBJ_PATH,
+                                            SYSTEMD_INTERFACE,
+                                            "Subscribe");
+    this->bus.call_noreply(method);
+
+    return;
+}
+
 auto Activation::activation(Activations value) ->
         Activations
 {
@@ -25,80 +39,113 @@
     {
         softwareServer::Activation::activation(value);
 
-        if (!activationBlocksTransition)
+        if (squashfsLoaded == false && rwVolumesCreated == false)
         {
-            activationBlocksTransition =
-                      std::make_unique<ActivationBlocksTransition>(
-                                bus,
-                                path);
-        }
+            // If the squashfs image has not yet been loaded to pnor and the
+            // RW volumes have not yet been created, we need to start the
+            // service files for each of those actions.
 
-        constexpr auto ubimountService = "obmc-flash-bios-ubimount@";
-        auto ubimountServiceFile = std::string(ubimountService) +
-                                   versionId +
-                                   ".service";
-        auto method = bus.new_method_call(
-                SYSTEMD_BUSNAME,
-                SYSTEMD_PATH,
-                SYSTEMD_INTERFACE,
-                "StartUnit");
-        method.append(ubimountServiceFile,
-                      "replace");
-        bus.call_noreply(method);
-
-        // The ubimount service files attemps to create the RW and Preserved
-        // UBI volumes. If the service fails, the mount directories PNOR_PRSV
-        // and PNOR_RW_PREFIX_<versionid> won't be present. Check for the
-        // existence of those directories to validate the service file was
-        // successful, also for the existence of the RO directory where the
-        // image is supposed to reside.
-        if ((fs::is_directory(PNOR_PRSV)) &&
-            (fs::is_directory(PNOR_RW_PREFIX + versionId)) &&
-            (fs::is_directory(PNOR_RO_PREFIX + versionId)))
-        {
-            if (!fs::is_directory(PNOR_ACTIVE_PATH))
+            if (!activationBlocksTransition)
             {
-                fs::create_directories(PNOR_ACTIVE_PATH);
-            }
-
-            // If the RW or RO active links exist, remove them and create new
-            // ones pointing to the active version.
-            if (fs::is_symlink(PNOR_RO_ACTIVE_PATH))
-            {
-                fs::remove(PNOR_RO_ACTIVE_PATH);
-            }
-            fs::create_directory_symlink(PNOR_RO_PREFIX + versionId,
-                    PNOR_RO_ACTIVE_PATH);
-            if (fs::is_symlink(PNOR_RW_ACTIVE_PATH))
-            {
-                fs::remove(PNOR_RW_ACTIVE_PATH);
-            }
-            fs::create_directory_symlink(PNOR_RW_PREFIX + versionId,
-                    PNOR_RW_ACTIVE_PATH);
-
-            // There is only one preserved directory as it is not tied to a
-            // version, so just create the link if it doesn't exist already.
-            if (!fs::is_symlink(PNOR_PRSV_ACTIVE_PATH))
-            {
-                fs::create_directory_symlink(PNOR_PRSV, PNOR_PRSV_ACTIVE_PATH);
-            }
-
-            // Set Redundancy Priority before setting to Active
-            if (!redundancyPriority)
-            {
-                redundancyPriority =
-                          std::make_unique<RedundancyPriority>(
+                activationBlocksTransition =
+                          std::make_unique<ActivationBlocksTransition>(
                                     bus,
                                     path);
             }
 
-            return softwareServer::Activation::activation(
-                    softwareServer::Activation::Activations::Active);
+            constexpr auto squashfsMountService =
+                    "obmc-flash-bios-squashfsmount@";
+            auto squashfsMountServiceFile = std::string(squashfsMountService) +
+                    versionId + ".service";
+            auto method = bus.new_method_call(
+                    SYSTEMD_BUSNAME,
+                    SYSTEMD_PATH,
+                    SYSTEMD_INTERFACE,
+                    "StartUnit");
+            method.append(squashfsMountServiceFile, "replace");
+            bus.call_noreply(method);
+
+            constexpr auto ubimountService = "obmc-flash-bios-ubimount@";
+            auto ubimountServiceFile = std::string(ubimountService) +
+                   versionId +
+                   ".service";
+            method = bus.new_method_call(
+                    SYSTEMD_BUSNAME,
+                    SYSTEMD_PATH,
+                    SYSTEMD_INTERFACE,
+                    "StartUnit");
+            method.append(ubimountServiceFile, "replace");
+            bus.call_noreply(method);
+
+            return softwareServer::Activation::activation(value);
+        }
+        else if (squashfsLoaded == true && rwVolumesCreated == true)
+        {
+            // Only when the squashfs image is finished loading AND the RW
+            // volumes have been created do we proceed with activation.
+
+            // The ubimount service files attemps to create the RW and Preserved
+            // UBI volumes. If the service fails, the mount dirs PNOR_PRSV
+            // and PNOR_RW_PREFIX_<versionid> won't be present. Check for the
+            // existence of those directories to validate the service file was
+            // successful, also for the existence of the RO directory where the
+            // image is supposed to reside.
+            if ((fs::is_directory(PNOR_PRSV)) &&
+                (fs::is_directory(PNOR_RW_PREFIX + versionId)) &&
+                (fs::is_directory(PNOR_RO_PREFIX + versionId)))
+            {
+                if (!fs::is_directory(PNOR_ACTIVE_PATH))
+                {
+                    fs::create_directories(PNOR_ACTIVE_PATH);
+                }
+
+                // If the RW or RO active links exist, remove them and create new
+                // ones pointing to the active version.
+                if (fs::is_symlink(PNOR_RO_ACTIVE_PATH))
+                {
+                    fs::remove(PNOR_RO_ACTIVE_PATH);
+                }
+                fs::create_directory_symlink(PNOR_RO_PREFIX + versionId,
+                        PNOR_RO_ACTIVE_PATH);
+                if (fs::is_symlink(PNOR_RW_ACTIVE_PATH))
+                {
+                    fs::remove(PNOR_RW_ACTIVE_PATH);
+                }
+                fs::create_directory_symlink(PNOR_RW_PREFIX + versionId,
+                        PNOR_RW_ACTIVE_PATH);
+
+                // There is only one preserved directory as it is not tied to a
+                // version, so just create the link if it doesn't exist already.
+                if (!fs::is_symlink(PNOR_PRSV_ACTIVE_PATH))
+                {
+                    fs::create_directory_symlink(PNOR_PRSV,
+                            PNOR_PRSV_ACTIVE_PATH);
+                }
+
+                // Set Redundancy Priority before setting to Active
+                if (!redundancyPriority)
+                {
+                    redundancyPriority =
+                              std::make_unique<RedundancyPriority>(
+                                        bus,
+                                        path);
+                }
+
+                return softwareServer::Activation::activation(
+                        softwareServer::Activation::Activations::Active);
+            }
+            else
+            {
+                return softwareServer::Activation::activation(
+                        softwareServer::Activation::Activations::Failed);
+            }
         }
         else
         {
-            return softwareServer::Activation::activation(
-                    softwareServer::Activation::Activations::Failed);
+            // If either the squashfs image has not yet been loaded or the RW
+            // volumes have not yet been created, the activation process is
+            // ongoing, so we return "Activating" status.
+            return softwareServer::Activation::activation(value);
         }
     }
     else
@@ -111,6 +158,9 @@
 auto Activation::requestedActivation(RequestedActivations value) ->
         RequestedActivations
 {
+    squashfsLoaded = false;
+    rwVolumesCreated = false;
+
     if ((value == softwareServer::Activation::RequestedActivations::Active) &&
         (softwareServer::Activation::requestedActivation() !=
                   softwareServer::Activation::RequestedActivations::Active))
@@ -133,7 +183,48 @@
     return softwareServer::RedundancyPriority::priority(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);
+
+    auto squashfsMountServiceFile =
+            "obmc-flash-bios-squashfsmount@" + versionId + ".service";
+
+    auto ubimountServiceFile =
+            "obmc-flash-bios-ubimount@" + versionId + ".service";
+
+    if(newStateUnit == squashfsMountServiceFile && newStateResult == "done")
+    {
+       squashfsLoaded = true;
+    }
+
+    if(newStateUnit == ubimountServiceFile && newStateResult == "done")
+    {
+        rwVolumesCreated = true;
+    }
+
+    if(squashfsLoaded && rwVolumesCreated)
+    {
+        Activation::activation(
+                softwareServer::Activation::Activations::Activating);
+    }
+
+    if((newStateUnit == squashfsMountServiceFile ||
+        newStateUnit == ubimountServiceFile) &&
+        (newStateResult == "failed" || newStateResult == "dependency"))
+    {
+        Activation::activation(softwareServer::Activation::Activations::Failed);
+    }
+
+    return;
+}
+
 } // namespace updater
 } // namespace software
 } // namespace openpower
-
diff --git a/activation.hpp b/activation.hpp
index 0e80260..bedef81 100755
--- a/activation.hpp
+++ b/activation.hpp
@@ -21,6 +21,8 @@
 using RedundancyPriorityInherit = sdbusplus::server::object::object<
     sdbusplus::xyz::openbmc_project::Software::server::RedundancyPriority>;
 
+namespace sdbusRule = sdbusplus::bus::match::rules;
+
 /** @class RedundancyPriority
  *  @brief OpenBMC RedundancyPriority implementation
  *  @details A concrete implementation for
@@ -96,8 +98,19 @@
                    ActivationInherit(bus, path.c_str(), true),
                    bus(bus),
                    path(path),
-                   versionId(versionId)
+                   versionId(versionId),
+                   systemdSignals(
+                           bus,
+                           sdbusRule::type::signal() +
+                           sdbusRule::member("JobRemoved") +
+                           sdbusRule::path("/org/freedesktop/systemd1") +
+                           sdbusRule::interface(
+                                   "org.freedesktop.systemd1.Manager"),
+                           std::bind(std::mem_fn(&Activation::unitStateChange),
+                                  this, std::placeholders::_1))
         {
+            // Enable systemd signals
+            subscribeToSystemdSignals();
             // Set Properties.
             extendedVersion(extVersion);
             activation(activationStatus);
@@ -122,6 +135,25 @@
         RequestedActivations requestedActivation(RequestedActivations value)
                 override;
 
+        /** @brief Check if systemd state change is relevant to this object
+         *
+         * Instance specific interface to handle the detected systemd state
+         * change
+         *
+         * @param[in]  msg       - Data associated with subscribed signal
+         *
+         */
+        void unitStateChange(sdbusplus::message::message& msg);
+
+        /**
+         * @brief subscribe to the systemd signals
+         *
+         * This object needs to capture when it's systemd targets complete
+         * so it can keep it's state updated
+         *
+         **/
+        void subscribeToSystemdSignals();
+
         /** @brief Persistent sdbusplus DBus bus connection */
         sdbusplus::bus::bus& bus;
 
@@ -136,9 +168,19 @@
 
         /** @brief Persistent RedundancyPriority dbus object */
         std::unique_ptr<RedundancyPriority> redundancyPriority;
+
+        /** @brief Used to subscribe to dbus systemd signals **/
+        sdbusplus::bus::match_t systemdSignals;
+
+        /** @brief Tracks whether the squashfs image has been loaded as part of
+         * the activation process. **/
+        bool squashfsLoaded = false;
+
+        /** @brief Tracks whether the read-write volumes have been created as
+         * part of the activation process. **/
+        bool rwVolumesCreated = false;
 };
 
 } // namespace updater
 } // namespace software
 } // namespace openpower
-
diff --git a/item_updater.cpp b/item_updater.cpp
index 2caacfe..3fff1a4 100755
--- a/item_updater.cpp
+++ b/item_updater.cpp
@@ -97,23 +97,6 @@
         if (ItemUpdater::validateSquashFSImage(filePath) == 0)
         {
             activationState = server::Activation::Activations::Ready;
-
-            // Load the squashfs image to pnor so that it is available to be
-            // activated when requested.
-            // This is done since the image on the BMC can be removed.
-            constexpr auto squashfsMountService =
-                                "obmc-flash-bios-squashfsmount@";
-            auto squashfsMountServiceFile =
-                                std::string(squashfsMountService) +
-                                versionId +
-                                ".service";
-            auto method = bus.new_method_call(
-                                SYSTEMD_BUSNAME,
-                                SYSTEMD_PATH,
-                                SYSTEMD_INTERFACE,
-                                "StartUnit");
-            method.append(squashfsMountServiceFile, "replace");
-            bus.call_noreply(method);
         }
 
         fs::path manifestPath(filePath);