Code Update: ApplyTime software manager support

Get the requested image apply time value provided through the
UpdateService redfish schema. If the apply time value is Immediate, then
BMC reboot will be triggered just after the new image activation. The
default apply time value is OnReset in which the new image remains at
Active state and user has to manualy reboot the BMC for applying the new
image.

Tested: Verified that BMC is getting rebooted if the apply time value is
Immediate. OnReset scenario was also tested in which new image remained at
Active state as code update application did not trigger the BMC reboot.

Signed-off-by: Jayashankar Padath <jayashankar.padath@in.ibm.com>
Change-Id: I5efb51f7f36e196d6113cfca6d37c8c6bef70aa2
diff --git a/Makefile.am b/Makefile.am
index c2478d1..6c3ec16 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,7 +10,8 @@
 	item_updater.hpp \
 	activation.hpp \
 	flash.hpp \
-	item_updater_helper.hpp
+	item_updater_helper.hpp \
+	utils.hpp
 
 bin_PROGRAMS = \
 	phosphor-version-software-manager \
@@ -43,7 +44,8 @@
 	version.cpp \
 	serialize.cpp \
 	item_updater.cpp \
-	item_updater_main.cpp
+	item_updater_main.cpp \
+	utils.cpp
 
 if HAVE_SYSTEMD
 systemdsystemunit_DATA = \
@@ -67,10 +69,10 @@
 if WANT_SIGNATURE_VERIFY_BUILD
 noinst_HEADERS += \
 	image_verify.hpp \
-	utils.hpp
+	openssl_alloc.hpp
 phosphor_image_updater_SOURCES += \
 	image_verify.cpp \
-	utils.cpp
+	openssl_alloc.cpp
 endif
 
 if WANT_SYNC
diff --git a/activation.cpp b/activation.cpp
index 73acc3d..0c18821 100644
--- a/activation.cpp
+++ b/activation.cpp
@@ -4,15 +4,14 @@
 #include "item_updater.hpp"
 #include "serialize.hpp"
 
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/exception.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
 
 #ifdef WANT_SIGNATURE_VERIFY
 #include "image_verify.hpp"
-
-#include <phosphor-logging/elog-errors.hpp>
-#include <phosphor-logging/elog.hpp>
-#include <xyz/openbmc_project/Common/error.hpp>
 #endif
 
 namespace phosphor
@@ -26,10 +25,10 @@
 
 using namespace phosphor::logging;
 using sdbusplus::exception::SdBusError;
-
-#ifdef WANT_SIGNATURE_VERIFY
 using InternalFailure =
     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+#ifdef WANT_SIGNATURE_VERIFY
 namespace control = sdbusplus::xyz::openbmc_project::Control::server;
 #endif
 
@@ -64,7 +63,15 @@
 {
     auto method = this->bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
                                             SYSTEMD_INTERFACE, "Unsubscribe");
-    this->bus.call_noreply(method);
+    try
+    {
+        this->bus.call_noreply(method);
+    }
+    catch (const SdBusError& e)
+    {
+        log<level::ERR>("Error in unsubscribing from systemd signals",
+                        entry("ERROR=%s", e.what()));
+    }
 
     return;
 }
@@ -151,6 +158,13 @@
                 // Create active association
                 parent.createActiveAssociation(path);
 
+                if (Activation::checkApplyTimeImmediate() == true)
+                {
+                    log<level::INFO>("Image Active. ApplyTime is immediate, "
+                                     "rebooting BMC.");
+                    Activation::rebootBmc();
+                }
+
                 return softwareServer::Activation::activation(
                     softwareServer::Activation::Activations::Active);
             }
@@ -307,6 +321,63 @@
     bus.call_noreply(method);
 }
 
+bool Activation::checkApplyTimeImmediate()
+{
+    auto service = utils::getService(bus, applyTimeObjPath, applyTimeIntf);
+    if (service.empty())
+    {
+        log<level::INFO>("Error getting the service name for BMC image "
+                         "ApplyTime. The BMC needs to be manually rebooted to "
+                         "complete the image activation if needed "
+                         "immediately.");
+    }
+    else
+    {
+
+        auto method = bus.new_method_call(service.c_str(), applyTimeObjPath,
+                                          dbusPropIntf, "Get");
+        method.append(applyTimeIntf, applyTimeProp);
+
+        try
+        {
+            auto reply = bus.call(method);
+
+            sdbusplus::message::variant<std::string> result;
+            reply.read(result);
+            auto applyTime =
+                sdbusplus::message::variant_ns::get<std::string>(result);
+            if (applyTime == applyTimeImmediate)
+            {
+                return true;
+            }
+        }
+        catch (const SdBusError& e)
+        {
+            log<level::ERR>("Error in getting ApplyTime",
+                            entry("ERROR=%s", e.what()));
+        }
+    }
+    return false;
+}
+
+void Activation::rebootBmc()
+{
+    auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+                                      SYSTEMD_INTERFACE, "StartUnit");
+    method.append("force-reboot.service", "replace");
+    try
+    {
+        auto reply = bus.call(method);
+    }
+    catch (const SdBusError& e)
+    {
+        log<level::ALERT>("Error in trying to reboot the BMC. "
+                          "The BMC needs to be manually rebooted to complete "
+                          "the image activation.");
+        report<InternalFailure>();
+    }
+}
+
 } // namespace updater
 } // namespace software
 } // namespace phosphor
diff --git a/activation.hpp b/activation.hpp
index 27dfdd4..df8cba8 100644
--- a/activation.hpp
+++ b/activation.hpp
@@ -4,6 +4,7 @@
 
 #include "flash.hpp"
 #include "org/openbmc/Associations/server.hpp"
+#include "utils.hpp"
 #include "xyz/openbmc_project/Software/ActivationProgress/server.hpp"
 #include "xyz/openbmc_project/Software/RedundancyPriority/server.hpp"
 
@@ -39,6 +40,13 @@
 using ActivationProgressInherit = sdbusplus::server::object::object<
     sdbusplus::xyz::openbmc_project::Software::server::ActivationProgress>;
 
+constexpr auto applyTimeImmediate =
+    "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate";
+constexpr auto applyTimeIntf = "xyz.openbmc_project.Software.ApplyTime";
+constexpr auto dbusPropIntf = "org.freedesktop.DBus.Properties";
+constexpr auto applyTimeObjPath = "/xyz/openbmc_project/software/apply_time";
+constexpr auto applyTimeProp = "RequestedApplyTime";
+
 namespace sdbusRule = sdbusplus::bus::match::rules;
 
 class ItemUpdater;
@@ -296,6 +304,20 @@
      */
     void deleteImageManagerObject();
 
+    /**
+     * @brief Determine the configured image apply time value
+     *
+     * @return true if the image apply time value is immediate
+     **/
+    bool checkApplyTimeImmediate();
+
+    /**
+     * @brief Reboot the BMC. Called when ApplyTime is immediate.
+     *
+     * @return none
+     **/
+    void rebootBmc();
+
     /** @brief Persistent sdbusplus DBus bus connection */
     sdbusplus::bus::bus& bus;
 
diff --git a/image_verify.hpp b/image_verify.hpp
index 3a7f911..dcf904d 100644
--- a/image_verify.hpp
+++ b/image_verify.hpp
@@ -1,5 +1,5 @@
 #pragma once
-#include "utils.hpp"
+#include "openssl_alloc.hpp"
 
 #include <openssl/evp.h>
 #include <openssl/pem.h>
diff --git a/openssl_alloc.cpp b/openssl_alloc.cpp
new file mode 100644
index 0000000..ddf278c
--- /dev/null
+++ b/openssl_alloc.cpp
@@ -0,0 +1,29 @@
+#include "openssl_alloc.hpp"
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+#include <string.h>
+
+static void* OPENSSL_zalloc(size_t num)
+{
+    void* ret = OPENSSL_malloc(num);
+
+    if (ret != NULL)
+    {
+        memset(ret, 0, num);
+    }
+    return ret;
+}
+
+EVP_MD_CTX* EVP_MD_CTX_new(void)
+{
+    return (EVP_MD_CTX*)OPENSSL_zalloc(sizeof(EVP_MD_CTX));
+}
+
+void EVP_MD_CTX_free(EVP_MD_CTX* ctx)
+{
+    EVP_MD_CTX_cleanup(ctx);
+    OPENSSL_free(ctx);
+}
+
+#endif
diff --git a/openssl_alloc.hpp b/openssl_alloc.hpp
new file mode 100644
index 0000000..90569bf
--- /dev/null
+++ b/openssl_alloc.hpp
@@ -0,0 +1,15 @@
+#pragma once
+
+// With OpenSSL 1.1.0, some functions were deprecated. Need to abstract them
+// to make the code backward compatible with older OpenSSL veresions.
+// Reference: https://wiki.openssl.org/index.php/OpenSSL_1.1.0_Changes
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+
+#include <openssl/evp.h>
+
+extern "C" {
+EVP_MD_CTX* EVP_MD_CTX_new(void);
+void EVP_MD_CTX_free(EVP_MD_CTX* ctx);
+}
+
+#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
diff --git a/ubi/Makefile.am.include b/ubi/Makefile.am.include
index 123f58d..f5b834e 100644
--- a/ubi/Makefile.am.include
+++ b/ubi/Makefile.am.include
@@ -14,5 +14,6 @@
 	%reldir%/obmc-flash-bmc-updateubootvars@.service \
 	%reldir%/reboot-guard-disable.service \
 	%reldir%/reboot-guard-enable.service \
+	%reldir%/force-reboot.service \
 	%reldir%/usr-local.mount
 endif
diff --git a/ubi/force-reboot.service b/ubi/force-reboot.service
new file mode 100644
index 0000000..73ff274
--- /dev/null
+++ b/ubi/force-reboot.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Force reboot of the BMC
+Requires=reboot-guard-disable.service
+After=reboot-guard-disable.service
+
+[Service]
+Type=oneshot
+RemainAfterExit=no
+ExecStart=/sbin/reboot
diff --git a/utils.cpp b/utils.cpp
index 95fc2e0..d3faed3 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -1,29 +1,44 @@
 #include "utils.hpp"
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#include <phosphor-logging/log.hpp>
 
-#include <string.h>
-
-static void* OPENSSL_zalloc(size_t num)
+namespace utils
 {
-    void* ret = OPENSSL_malloc(num);
 
-    if (ret != NULL)
+using namespace phosphor::logging;
+
+std::string getService(sdbusplus::bus::bus& bus, const std::string& path,
+                       const std::string& interface)
+{
+    auto method = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
+                                      MAPPER_BUSNAME, "GetObject");
+
+    method.append(path);
+    method.append(std::vector<std::string>({interface}));
+
+    std::vector<std::pair<std::string, std::vector<std::string>>> response;
+
+    try
     {
-        memset(ret, 0, num);
+        auto reply = bus.call(method);
+        reply.read(response);
+        if (response.empty())
+        {
+            log<level::ERR>("Error in mapper response for getting service name",
+                            entry("PATH=%s", path.c_str()),
+                            entry("INTERFACE=%s", interface.c_str()));
+            return std::string{};
+        }
     }
-    return ret;
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Error in mapper method call",
+                        entry("ERROR=%s", e.what()),
+                        entry("PATH=%s", path.c_str()),
+                        entry("INTERFACE=%s", interface.c_str()));
+        return std::string{};
+    }
+    return response[0].first;
 }
 
-EVP_MD_CTX* EVP_MD_CTX_new(void)
-{
-    return (EVP_MD_CTX*)OPENSSL_zalloc(sizeof(EVP_MD_CTX));
-}
-
-void EVP_MD_CTX_free(EVP_MD_CTX* ctx)
-{
-    EVP_MD_CTX_cleanup(ctx);
-    OPENSSL_free(ctx);
-}
-
-#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
+} // namespace utils
diff --git a/utils.hpp b/utils.hpp
index 90569bf..67c4998 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -1,15 +1,18 @@
-#pragma once
+#include "config.h"
 
-// With OpenSSL 1.1.0, some functions were deprecated. Need to abstract them
-// to make the code backward compatible with older OpenSSL veresions.
-// Reference: https://wiki.openssl.org/index.php/OpenSSL_1.1.0_Changes
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#include <map>
+#include <sdbusplus/server.hpp>
+#include <string>
 
-#include <openssl/evp.h>
+namespace utils
+{
 
-extern "C" {
-EVP_MD_CTX* EVP_MD_CTX_new(void);
-void EVP_MD_CTX_free(EVP_MD_CTX* ctx);
-}
+/**
+ * @brief Get the bus service
+ *
+ * @return the bus service as a string
+ **/
+std::string getService(sdbusplus::bus::bus& bus, const std::string& path,
+                       const std::string& interface);
 
-#endif // OPENSSL_VERSION_NUMBER < 0x10100000L
+} // namespace utils