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/inc/srvcfg_manager.hpp b/inc/srvcfg_manager.hpp
index 4036623..f94e4ea 100644
--- a/inc/srvcfg_manager.hpp
+++ b/inc/srvcfg_manager.hpp
@@ -67,6 +67,13 @@
     void restartUnitConfig(boost::asio::yield_context yield);
     void startServiceRestartTimer();
 
+#ifdef USB_CODE_UPDATE
+    void saveUSBCodeUpdateStateToFile(const bool& maskedState,
+                                      const bool& enabledState);
+    void getUSBCodeUpdateStateFromFile();
+    void setUSBCodeUpdateState(const bool& state);
+#endif
+
   private:
     sdbusplus::asio::object_server& server;
     std::shared_ptr<sdbusplus::asio::dbus_interface> srvCfgIface;
diff --git a/meson.build b/meson.build
index ed8e7d1..4112b07 100644
--- a/meson.build
+++ b/meson.build
@@ -33,6 +33,10 @@
         dependency('systemd'),
 ]
 
+if(get_option('usb-code-update').enabled())
+    add_project_arguments('-DUSB_CODE_UPDATE', language : 'cpp')
+endif
+
 executable('phosphor-srvcfg-manager',
            'src/main.cpp',
            'src/srvcfg_manager.cpp',
diff --git a/meson_options.txt b/meson_options.txt
new file mode 100644
index 0000000..b89faa8
--- /dev/null
+++ b/meson_options.txt
@@ -0,0 +1,4 @@
+option(
+    'usb-code-update', type: 'feature',
+    description: 'Enable USB code update enable/disable feature.',
+)
diff --git a/src/main.cpp b/src/main.cpp
index 2d7a4b0..c4f7e71 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -212,6 +212,15 @@
         archive(CEREAL_NVP(unitsToMonitor));
     }
 
+#ifdef USB_CODE_UPDATE
+    unitsToMonitor.emplace(
+        "phosphor_2dusb_2dcode_2dupdate",
+        std::make_tuple(
+            "phosphor_usb_code_update", "",
+            "/org/freedesktop/systemd1/unit/usb_2dcode_2dupdate_2eservice",
+            ""));
+#endif
+
     // create objects for needed services
     for (auto& it : unitsToMonitor)
     {
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;