reject Transition request while firmware being flashed

When users request to flash BMC/Host... firmware, the BMC should reject
turning on the host or rebooting BMC requests to protect the system.
As defined in the PDIs [1], the ActivationBlocksTransition interface is
used to prevent power-on during flashing. The phosphor-state-manager
shall look for this interface to make decision should transition request
is executed or not. When BMC rejects the transition request, an error is
thrown as defined in PDIs [2].

[1] https://github.com/openbmc/phosphor-dbus-interfaces/blob/master/yaml/xyz/openbmc_project/Software/ActivationBlocksTransition.interface.yaml
[2] https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/75231

Tested:
  Enable check-fwupdate-before-do-transition option
    1. Request to flash Host's firmware via Redfish or WebUi
    2. Request to turn off the Host.
       $ipmitool power off
    3. BMC turns off the power.
    4. Request to turn on the Host.
       $ipmitool power on
    5. BMC rejects above request. No power action is executed.
    6. Request to reboot BMC.
       $ipmitool mc reset cold
    7. BMC did not reboot.
    8. Request turn on the Host after flashing done.
    9. BMC turns on the Power.
    10. Request to reboot BMC.
    11. BMC reboots.

  Disable check-fwupdate-before-do-transition option
    1. Request to flash Host's firmware via Redfish or WebUi
    2. Request to turn off the Host.
       $ipmitool power off
    3. BMC turns off the power.
    4. Request to turn on the Host.
       $ipmitool power on
    5. BMC does not reject this request, but no power action is handled
    6. When flashing done, BMC turns on the power.

Change-Id: If3998e58d24cfb72d13b01eb5b8c8cc5a39b3c95
Signed-off-by: Thang Tran <thuutran@amperecomputing.com>
diff --git a/bmc_state_manager.cpp b/bmc_state_manager.cpp
index 5376994..faab43f 100644
--- a/bmc_state_manager.cpp
+++ b/bmc_state_manager.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "bmc_state_manager.hpp"
 
 #include "utils.hpp"
@@ -224,6 +226,18 @@
          "{REQUESTED_BMC_TRANSITION}",
          "REQUESTED_BMC_TRANSITION", value);
 
+#ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+    /*
+     * Do not do transition when the any firmware being updated
+     */
+    if ((server::BMC::Transition::Reboot == value) &&
+        (phosphor::state::manager::utils::isFirmwareUpdating(this->bus)))
+    {
+        info("Firmware being updated, reject the transition request");
+        throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
+    }
+#endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+
     executeTransition(value);
     return server::BMC::requestedBMCTransition(value);
 }
diff --git a/chassis_state_manager.cpp b/chassis_state_manager.cpp
index 05cc784..324270b 100644
--- a/chassis_state_manager.cpp
+++ b/chassis_state_manager.cpp
@@ -597,6 +597,19 @@
             BMCNotReady();
     }
 #endif
+
+#ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+    /*
+     * Do not do transition when the any firmware being updated
+     */
+    if ((value != Transition::Off) &&
+        (phosphor::state::manager::utils::isFirmwareUpdating(this->bus)))
+    {
+        info("Firmware being updated, reject the transition request");
+        throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
+    }
+#endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+
     startUnit(systemdTargetTable.find(value)->second);
     return server::Chassis::requestedPowerTransition(value);
 }
diff --git a/host_state_manager.cpp b/host_state_manager.cpp
index 3118b4a..fbb2123 100644
--- a/host_state_manager.cpp
+++ b/host_state_manager.cpp
@@ -424,6 +424,17 @@
     // check of this count will occur
     if (value != server::Host::Transition::Off)
     {
+#ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+        /*
+         * Do not do transition when the any firmware being updated
+         */
+        if (phosphor::state::manager::utils::isFirmwareUpdating(this->bus))
+        {
+            info("Firmware being updated, reject the transition request");
+            throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
+        }
+#endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+
         decrementRebootCount();
     }
 
diff --git a/meson.build b/meson.build
index 6991667..da267b6 100644
--- a/meson.build
+++ b/meson.build
@@ -69,6 +69,9 @@
 conf.set_quoted(
     'CHASSIS_ON_FILE', '/run/openbmc/chassis@{}-on')
 
+conf.set(
+    'CHECK_FWUPDATE_BEFORE_DO_TRANSITION', get_option('check-fwupdate-before-do-transition').enabled())
+
 configure_file(output: 'config.h', configuration: conf)
 
 if(get_option('warm-reboot').allowed())
diff --git a/meson.options b/meson.options
index 66fd2fc..94aa714 100644
--- a/meson.options
+++ b/meson.options
@@ -108,3 +108,8 @@
     value : false,
     description : 'Only allow chassis and host power on operations when BMC is Ready.'
 )
+
+option('check-fwupdate-before-do-transition', type : 'feature',
+    value : 'enabled',
+    description : 'Only do transition request when no firmware being updated'
+)
diff --git a/utils.cpp b/utils.cpp
index 26a1e8b..04ce3a4 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -8,6 +8,7 @@
 #include <xyz/openbmc_project/Dump/Create/client.hpp>
 #include <xyz/openbmc_project/Logging/Create/client.hpp>
 #include <xyz/openbmc_project/ObjectMapper/client.hpp>
+#include <xyz/openbmc_project/Software/ActivationBlocksTransition/client.hpp>
 #include <xyz/openbmc_project/State/BMC/client.hpp>
 
 #include <chrono>
@@ -33,6 +34,8 @@
 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
 
 using ObjectMapper = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>;
+using ActBlockTrans = sdbusplus::client::xyz::openbmc_project::software::
+    ActivationBlocksTransition<>;
 
 void subscribeToSystemdSignals(sdbusplus::bus_t& bus)
 {
@@ -268,6 +271,40 @@
     return false;
 }
 
+#ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+bool isFirmwareUpdating(sdbusplus::bus_t& bus)
+{
+    /*
+     * This method looks for ActivationBlocksTransition interface, if any object
+     * path is including this interface, the Transition action should be
+     * prevented.
+     */
+    auto mapper = bus.new_method_call(
+        ObjectMapper::default_service, ObjectMapper::instance_path,
+        ObjectMapper::interface, "GetSubTreePaths");
+
+    mapper.append("/", 0, std::vector<std::string>({ActBlockTrans::interface}));
+
+    std::vector<std::string> mapperResponse;
+
+    try
+    {
+        auto mapperResponseMsg = bus.call(mapper);
+
+        mapperResponseMsg.read(mapperResponse);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        error("Error in mapper call with root path, interface "
+              "ActivationBlocksTransition, and exception {ERROR}",
+              "ERROR", e);
+        return false;
+    }
+
+    return !mapperResponse.empty();
+}
+#endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+
 } // namespace utils
 } // namespace manager
 } // namespace state
diff --git a/utils.hpp b/utils.hpp
index 0d2431f..54ce58a 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "config.h"
+
 #include <sdbusplus/bus.hpp>
 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
 
@@ -102,6 +104,14 @@
  */
 bool waitBmcReady(sdbusplus::bus_t& bus, std::chrono::seconds timeout);
 
+#ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+/** @brief Determine if any firmware being updated
+ *
+ * @param[in] bus          - The Dbus bus object
+ */
+bool isFirmwareUpdating(sdbusplus::bus_t& bus);
+#endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
+
 } // namespace utils
 } // namespace manager
 } // namespace state