ADD USB Code Update enable/disable

phosphor_usb_code_update is an application that use USB to do FW
update. phosphor_usb_code_update service is not a daemon, but an app
that will be called after inserting a USB flash disk.

This commit creates an object of phosphor_usb_code_update when
phosphor_usb_code_update is not started. We can enable/disable
phosphor_usb_code_update by setting the "Enabled" property to
true/false.

Please configure the “usb-code-update” option in your bb/bbappend to
enable this feature.

For phosphor_usb_code_update, useful rules files is
/lib/udev/rules.d/70-bmc-usb.rules. When usb code update is disabled,
srvcfg_manager creates an empty symlink
/etc/udev/rules.d/70-bmc-usb.rules, which causes
/lib/udev/rules.d/70-bmc-usb.rules inoperative. When usb code update is
enabled, srvcfg_manager deletes /etc/udev/rules.d/70-bmc-usb.rules.

The commits of phosphor_usb_code_update is:
https://gerrit.openbmc-project.xyz/c/openbmc/phosphor-bmc-code-mgmt/+/48742/1

Test:
get "Enabled" property:
busctl get-property xyz.openbmc_project.Control.Service.Manager
/xyz/openbmc_project/control/service/phosphor_2dusb_2dcode_2dupdate
xyz.openbmc_project.Control.Service.Attributes Enabled
b true

set "Enabled" property to false:
busctl set-property xyz.openbmc_project.Control.Service.Manager
/xyz/openbmc_project/control/service/phosphor_2dusb_2dcode_2dupdate
xyz.openbmc_project.Control.Service.Attributes Enabled b false

busctl get-property xyz.openbmc_project.Control.Service.Manager
/xyz/openbmc_project/control/service/phosphor_2dusb_2dcode_2dupdate
xyz.openbmc_project.Control.Service.Attributes Enabled
b false

The "Enabled" property will be persisted,reboot the BMC, then get the
"Enabled" property:
busctl get-property xyz.openbmc_project.Control.Service.Manager
/xyz/openbmc_project/control/service/phosphor_2dusb_2dcode_2dupdate
xyz.openbmc_project.Control.Service.Attributes Enabled
b false

Signed-off-by: Chicago Duan <duanzhijia01@inspur.com>
Change-Id: Iba7ffb541628d563e2c54c3e1c8c4dbe85f1507b
diff --git a/src/srvcfg_manager.cpp b/src/srvcfg_manager.cpp
index b7b891b..9840cc4 100644
--- a/src/srvcfg_manager.cpp
+++ b/src/srvcfg_manager.cpp
@@ -17,6 +17,14 @@
 
 #include <boost/asio/spawn.hpp>
 
+#ifdef USB_CODE_UPDATE
+#include <cereal/archives/json.hpp>
+#include <cereal/types/tuple.hpp>
+#include <cereal/types/unordered_map.hpp>
+
+#include <cstdio>
+#endif
+
 #include <fstream>
 #include <regex>
 
@@ -38,6 +46,109 @@
 static constexpr const char* systemdOverrideUnitBasePath =
     "/etc/systemd/system/";
 
+#ifdef USB_CODE_UPDATE
+static constexpr const char* usbCodeUpdateStateFilePath =
+    "/var/lib/srvcfg_manager";
+static constexpr const char* usbCodeUpdateStateFile =
+    "/var/lib/srvcfg_manager/usb-code-update-state";
+static constexpr const char* emptyUsbCodeUpdateRulesFile =
+    "/etc/udev/rules.d/70-bmc-usb.rules";
+static constexpr const char* usbCodeUpdateObjectPath =
+    "/xyz/openbmc_project/control/service/phosphor_2dusb_2dcode_2dupdate";
+
+using UsbCodeUpdateStateMap = std::unordered_map<std::string, bool>;
+
+void ServiceConfig::setUSBCodeUpdateState(const bool& state)
+{
+    // Enable usb code update
+    if (state)
+    {
+        if (std::filesystem::exists(emptyUsbCodeUpdateRulesFile))
+        {
+            phosphor::logging::log<phosphor::logging::level::INFO>(
+                "Enable usb code update");
+            std::filesystem::remove(emptyUsbCodeUpdateRulesFile);
+        }
+        return;
+    }
+
+    // Disable usb code update
+    if (std::filesystem::exists(emptyUsbCodeUpdateRulesFile))
+    {
+        std::filesystem::remove(emptyUsbCodeUpdateRulesFile);
+    }
+    std::error_code ec;
+    std::filesystem::create_symlink("/dev/null", emptyUsbCodeUpdateRulesFile,
+                                    ec);
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Disable usb code update failed");
+        return;
+    }
+    phosphor::logging::log<phosphor::logging::level::INFO>(
+        "Disable usb code update");
+}
+
+void ServiceConfig::saveUSBCodeUpdateStateToFile(const bool& maskedState,
+                                                 const bool& enabledState)
+{
+    if (!std::filesystem::exists(usbCodeUpdateStateFilePath))
+    {
+        std::filesystem::create_directories(usbCodeUpdateStateFilePath);
+    }
+
+    UsbCodeUpdateStateMap usbCodeUpdateState;
+    usbCodeUpdateState[srvCfgPropMasked] = maskedState;
+    usbCodeUpdateState[srvCfgPropEnabled] = enabledState;
+
+    std::ofstream file(usbCodeUpdateStateFile, std::ios::out);
+    cereal::JSONOutputArchive archive(file);
+    archive(CEREAL_NVP(usbCodeUpdateState));
+}
+
+void ServiceConfig::getUSBCodeUpdateStateFromFile()
+{
+    if (!std::filesystem::exists(usbCodeUpdateStateFile))
+    {
+        phosphor::logging::log<phosphor::logging::level::INFO>(
+            "usb-code-update-state file does not exist");
+
+        unitMaskedState = false;
+        unitEnabledState = true;
+        unitRunningState = true;
+        setUSBCodeUpdateState(unitEnabledState);
+        return;
+    }
+
+    std::ifstream file(usbCodeUpdateStateFile);
+    cereal::JSONInputArchive archive(file);
+    UsbCodeUpdateStateMap usbCodeUpdateState;
+    archive(usbCodeUpdateState);
+
+    auto iterMask = usbCodeUpdateState.find(srvCfgPropMasked);
+    if (iterMask != usbCodeUpdateState.end())
+    {
+        unitMaskedState = iterMask->second;
+        if (unitMaskedState)
+        {
+            unitEnabledState = !unitMaskedState;
+            unitRunningState = !unitMaskedState;
+            setUSBCodeUpdateState(unitEnabledState);
+            return;
+        }
+
+        auto iterEnable = usbCodeUpdateState.find(srvCfgPropEnabled);
+        if (iterEnable != usbCodeUpdateState.end())
+        {
+            unitEnabledState = iterEnable->second;
+            unitRunningState = iterEnable->second;
+            setUSBCodeUpdateState(unitEnabledState);
+        }
+    }
+}
+#endif
+
 void ServiceConfig::updateSocketProperties(
     const boost::container::flat_map<std::string, VariantType>& propertyMap)
 {
@@ -108,6 +219,13 @@
             internalSet = false;
         }
     }
+
+#ifdef USB_CODE_UPDATE
+    if (objPath == usbCodeUpdateObjectPath)
+    {
+        getUSBCodeUpdateStateFromFile();
+    }
+#endif
 }
 
 void ServiceConfig::queryAndUpdateProperties()
@@ -453,6 +571,26 @@
         srvCfgPropMasked, unitMaskedState, [this](const bool& req, bool& res) {
             if (!internalSet)
             {
+#ifdef USB_CODE_UPDATE
+                if (objPath == usbCodeUpdateObjectPath)
+                {
+                    unitMaskedState = req;
+                    unitEnabledState = !unitMaskedState;
+                    unitRunningState = !unitMaskedState;
+                    internalSet = true;
+                    srvCfgIface->set_property(srvCfgPropEnabled,
+                                              unitEnabledState);
+                    srvCfgIface->set_property(srvCfgPropRunning,
+                                              unitRunningState);
+                    srvCfgIface->set_property(srvCfgPropMasked,
+                                              unitMaskedState);
+                    internalSet = false;
+                    setUSBCodeUpdateState(unitEnabledState);
+                    saveUSBCodeUpdateStateToFile(unitMaskedState,
+                                                 unitEnabledState);
+                    return 1;
+                }
+#endif
                 if (req == res)
                 {
                     return 1;
@@ -483,6 +621,30 @@
         [this](const bool& req, bool& res) {
             if (!internalSet)
             {
+#ifdef USB_CODE_UPDATE
+                if (objPath == usbCodeUpdateObjectPath)
+                {
+                    if (unitMaskedState)
+                    { // block updating if masked
+                        phosphor::logging::log<phosphor::logging::level::ERR>(
+                            "Invalid value specified");
+                        return -EINVAL;
+                    }
+                    unitEnabledState = req;
+                    unitRunningState = req;
+                    internalSet = true;
+                    srvCfgIface->set_property(srvCfgPropEnabled,
+                                              unitEnabledState);
+                    srvCfgIface->set_property(srvCfgPropRunning,
+                                              unitRunningState);
+                    internalSet = false;
+                    setUSBCodeUpdateState(unitEnabledState);
+                    saveUSBCodeUpdateStateToFile(unitMaskedState,
+                                                 unitEnabledState);
+                    res = req;
+                    return 1;
+                }
+#endif
                 if (req == res)
                 {
                     return 1;
@@ -510,6 +672,30 @@
         [this](const bool& req, bool& res) {
             if (!internalSet)
             {
+#ifdef USB_CODE_UPDATE
+                if (objPath == usbCodeUpdateObjectPath)
+                {
+                    if (unitMaskedState)
+                    { // block updating if masked
+                        phosphor::logging::log<phosphor::logging::level::ERR>(
+                            "Invalid value specified");
+                        return -EINVAL;
+                    }
+                    unitEnabledState = req;
+                    unitRunningState = req;
+                    internalSet = true;
+                    srvCfgIface->set_property(srvCfgPropEnabled,
+                                              unitEnabledState);
+                    srvCfgIface->set_property(srvCfgPropRunning,
+                                              unitRunningState);
+                    internalSet = false;
+                    setUSBCodeUpdateState(unitEnabledState);
+                    saveUSBCodeUpdateStateToFile(unitMaskedState,
+                                                 unitEnabledState);
+                    res = req;
+                    return 1;
+                }
+#endif
                 if (req == res)
                 {
                     return 1;