updateservice: add start update D-Bus interface

Add the start update D-Bus interface based flow for multi-form content
path. This involves mapping the TargetURI to the corresponding
serviceName and objectPath which hosts the specific D-Bus interface.
As per discussion with Redfish community both ResourceURI and
FirmwareInventory Redfish URI can be used as TargetURI. Current
implementation already allows /redfish/v1/Managers/<bmc>, hence support
for this specific ResourceURI has been preserved. New implementation
adds FirmwareInventory Redfish URI for TargetURI as default option.
https://redfishforum.com/thread/1054.
For more details on design refer to -
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/65738
https://gerrit.openbmc.org/c/openbmc/docs/+/65739

Tested: Redfish Validator and Build passes.
multipart form data update request with Resource URI as target
```
curl  -k -H "X-Auth-Token: $token"  -H "Content-Type:multipart/form-data" \
    -X POST  -F UpdateParameters="{\"Targets\":[\"/redfish/v1/Managers/bmc\"],\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
    -F "UpdateFile=@obmc-phosphor-image-romulus-20240425222313.static.mtd.all.tar;type=application/octet-stream" \
    https://${bmc}/redfish/v1/UpdateService/update
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/0",
  "@odata.type": "#Task.v1_4_3.Task",
  "Id": "0",
  "TaskState": "Running",
  "TaskStatus": "OK"
}
```

multipart form data update request with Firmware Inventory URI as target
```
curl  -k -H "X-Auth-Token: $token"  -H "Content-Type:multipart/form-data"  \
    -X POST  -F UpdateParameters="{\"Targets\":[\"/redfish/v1/Managers/bmc\"],\"@Redfish.OperationApplyTime\":\"Immediate\"};type=application/json" \
    -F "UpdateFile=@obmc-phosphor-image-romulus-20240509003505.static.mtd.all.tar;type=application/octet-stream" \
    https://${bmc}/redfish/v1/UpdateService/update
{
  "@odata.id": "/redfish/v1/TaskService/Tasks/1",
  "@odata.type": "#Task.v1_4_3.Task",
  "Id": "1",
  "TaskState": "Running",
  "TaskStatus": "OK"
}
```

Change-Id: Id46de79d3af8834630a793678a6fc0e859295afe
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
index f3a87c3..89c4c7d 100644
--- a/redfish-core/lib/update_service.hpp
+++ b/redfish-core/lib/update_service.hpp
@@ -32,6 +32,8 @@
 #include "utils/json_utils.hpp"
 #include "utils/sw_utils.hpp"
 
+#include <sys/mman.h>
+
 #include <boost/system/error_code.hpp>
 #include <boost/url/format.hpp>
 #include <sdbusplus/asio/property.hpp>
@@ -39,12 +41,15 @@
 #include <sdbusplus/unpack_properties.hpp>
 
 #include <array>
+#include <cstddef>
 #include <filesystem>
 #include <functional>
+#include <iterator>
 #include <memory>
 #include <optional>
 #include <string>
 #include <string_view>
+#include <unordered_map>
 #include <vector>
 
 namespace redfish
@@ -62,6 +67,41 @@
 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
 
+struct MemoryFileDescriptor
+{
+    int fd = -1;
+
+    explicit MemoryFileDescriptor(const std::string& filename) :
+        fd(memfd_create(filename.c_str(), 0))
+    {}
+
+    MemoryFileDescriptor(const MemoryFileDescriptor&) = default;
+    MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd)
+    {
+        other.fd = -1;
+    }
+    MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete;
+    MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default;
+
+    ~MemoryFileDescriptor()
+    {
+        if (fd != -1)
+        {
+            close(fd);
+        }
+    }
+
+    bool rewind() const
+    {
+        if (lseek(fd, 0, SEEK_SET) == -1)
+        {
+            BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd");
+            return false;
+        }
+        return true;
+    }
+};
+
 inline void cleanUp()
 {
     fwUpdateInProgress = false;
@@ -660,6 +700,31 @@
     }
 }
 
+// Convert the Request Apply Time to the D-Bus value
+inline bool convertApplyTime(crow::Response& res, const std::string& applyTime,
+                             std::string& applyTimeNewVal)
+{
+    if (applyTime == "Immediate")
+    {
+        applyTimeNewVal =
+            "xyz.openbmc_project.Software.Update.ApplyTimes.Immediate";
+    }
+    else if (applyTime == "OnReset")
+    {
+        applyTimeNewVal =
+            "xyz.openbmc_project.Software.Update.ApplyTimes.OnReset";
+    }
+    else
+    {
+        BMCWEB_LOG_WARNING(
+            "ApplyTime value {} is not in the list of acceptable values",
+            applyTime);
+        messages::propertyValueNotInList(res, applyTime, "ApplyTime");
+        return false;
+    }
+    return true;
+}
+
 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                          const std::string& applyTime)
 {
@@ -694,9 +759,36 @@
 {
     std::optional<std::string> applyTime;
     std::string uploadData;
-    std::vector<boost::urls::url> targets;
+    std::vector<std::string> targets;
 };
 
+inline std::optional<std::string>
+    processUrl(boost::system::result<boost::urls::url_view>& url)
+{
+    if (!url)
+    {
+        return std::nullopt;
+    }
+    if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers",
+                                       BMCWEB_REDFISH_MANAGER_URI_NAME))
+    {
+        return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME));
+    }
+    if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
+    {
+        return std::nullopt;
+    }
+    std::string firmwareId;
+    if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService",
+                                        "FirmwareInventory",
+                                        std::ref(firmwareId)))
+    {
+        return std::nullopt;
+    }
+
+    return std::make_optional(firmwareId);
+}
+
 inline std::optional<MultiPartUpdateParameters>
     extractMultipartUpdateParameters(
         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
@@ -756,14 +848,15 @@
                     const std::string& target = tempTargets[urlIndex];
                     boost::system::result<boost::urls::url_view> url =
                         boost::urls::parse_origin_form(target);
-                    if (!url)
+                    auto res = processUrl(url);
+                    if (!res.has_value())
                     {
                         messages::propertyValueFormatError(
                             asyncResp->res, target,
                             std::format("Targets/{}", urlIndex));
                         return std::nullopt;
                     }
-                    multiRet.targets.emplace_back(*url);
+                    multiRet.targets.emplace_back(res.value());
                 }
                 if (multiRet.targets.size() != 1)
                 {
@@ -771,14 +864,6 @@
                         asyncResp->res, multiRet.targets, "Targets");
                     return std::nullopt;
                 }
-                if (multiRet.targets[0].path() !=
-                    std::format("/redfish/v1/Managers/{}",
-                                BMCWEB_REDFISH_MANAGER_URI_NAME))
-                {
-                    messages::propertyValueNotInList(
-                        asyncResp->res, multiRet.targets[0], "Targets/0");
-                    return std::nullopt;
-                }
             }
             else if (param.second == "UpdateFile")
             {
@@ -802,6 +887,184 @@
 }
 
 inline void
+    handleStartUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                      task::Payload payload, const std::string& objectPath,
+                      const boost::system::error_code& ec,
+                      const sdbusplus::message::object_path& retPath)
+{
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR("error_code = {}", ec);
+        BMCWEB_LOG_ERROR("error msg = {}", ec.message());
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    BMCWEB_LOG_INFO("Call to StartUpdate Success, retPath = {}", retPath.str);
+    createTask(asyncResp, std::move(payload), objectPath);
+}
+
+inline void startUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                        task::Payload payload,
+                        const MemoryFileDescriptor& memfd,
+                        const std::string& applyTime,
+                        const std::string& objectPath,
+                        const std::string& serviceName)
+{
+    crow::connections::systemBus->async_method_call(
+        [asyncResp, payload = std::move(payload),
+         objectPath](const boost::system::error_code& ec1,
+                     const sdbusplus::message::object_path& retPath) mutable {
+        handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
+                          retPath);
+    },
+        serviceName, objectPath, "xyz.openbmc_project.Software.Update",
+        "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
+}
+
+inline void getAssociatedUpdateInterface(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload,
+    const MemoryFileDescriptor& memfd, const std::string& applyTime,
+    const boost::system::error_code& ec,
+    const dbus::utility::MapperGetSubTreeResponse& subtree)
+{
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR("error_code = {}", ec);
+        BMCWEB_LOG_ERROR("error msg = {}", ec.message());
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    BMCWEB_LOG_DEBUG("Found {} startUpdate subtree paths", subtree.size());
+
+    if (subtree.size() > 1)
+    {
+        BMCWEB_LOG_ERROR("Found more than one startUpdate subtree paths");
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    auto objectPath = subtree[0].first;
+    auto serviceName = subtree[0].second[0].first;
+
+    BMCWEB_LOG_DEBUG("Found objectPath {} serviceName {}", objectPath,
+                     serviceName);
+    startUpdate(asyncResp, std::move(payload), memfd, applyTime, objectPath,
+                serviceName);
+}
+
+inline void
+    getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+              task::Payload payload, MemoryFileDescriptor memfd,
+              const std::string& applyTime, const std::string& target,
+              const boost::system::error_code& ec,
+              const dbus::utility::MapperGetSubTreePathsResponse& subtree)
+{
+    using SwInfoMap =
+        std::unordered_map<std::string, sdbusplus::message::object_path>;
+    SwInfoMap swInfoMap;
+
+    if (ec)
+    {
+        BMCWEB_LOG_ERROR("error_code = {}", ec);
+        BMCWEB_LOG_ERROR("error msg = {}", ec.message());
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size());
+
+    for (const auto& objectPath : subtree)
+    {
+        sdbusplus::message::object_path path(objectPath);
+        std::string swId = path.filename();
+        swInfoMap.emplace(swId, path);
+    }
+
+    auto swEntry = swInfoMap.find(target);
+    if (swEntry == swInfoMap.end())
+    {
+        BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target);
+        messages::propertyValueFormatError(asyncResp->res, target, "Targets");
+        return;
+    }
+
+    BMCWEB_LOG_DEBUG("Found software version path {}", swEntry->second.str);
+
+    sdbusplus::message::object_path swObjectPath = swEntry->second /
+                                                   "software_version";
+    constexpr std::array<std::string_view, 1> interfaces = {
+        "xyz.openbmc_project.Software.Update"};
+    dbus::utility::getAssociatedSubTree(
+        swObjectPath,
+        sdbusplus::message::object_path("/xyz/openbmc_project/software"), 0,
+        interfaces,
+        [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
+         applyTime](
+            const boost::system::error_code& ec1,
+            const dbus::utility::MapperGetSubTreeResponse& subtree1) mutable {
+        getAssociatedUpdateInterface(asyncResp, std::move(payload), memfd,
+                                     applyTime, ec1, subtree1);
+    });
+}
+
+inline void
+    processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+                         const crow::Request& req, std::string_view body,
+                         const std::string& applyTime,
+                         std::vector<std::string>& targets)
+{
+    std::string applyTimeNewVal;
+
+    if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal))
+    {
+        return;
+    }
+
+    MemoryFileDescriptor memfd("update-image");
+    if (memfd.fd == -1)
+    {
+        BMCWEB_LOG_ERROR("Failed to create image memfd");
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    if (write(memfd.fd, body.data(), body.length()) !=
+        static_cast<ssize_t>(body.length()))
+    {
+        BMCWEB_LOG_ERROR("Failed to write to image memfd");
+        messages::internalError(asyncResp->res);
+        return;
+    }
+    if (!memfd.rewind())
+    {
+        messages::internalError(asyncResp->res);
+        return;
+    }
+
+    task::Payload payload(req);
+    if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
+    {
+        startUpdate(asyncResp, std::move(payload), memfd, applyTimeNewVal,
+                    "/xyz/openbmc_project/software/bmc",
+                    "xyz.openbmc_project.Software.Manager");
+    }
+    else
+    {
+        constexpr std::array<std::string_view, 1> interfaces = {
+            "xyz.openbmc_project.Software.Version"};
+        dbus::utility::getSubTreePaths(
+            "/xyz/openbmc_project/software", 1, interfaces,
+            [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
+             applyTimeNewVal,
+             targets](const boost::system::error_code& ec,
+                      const dbus::utility::MapperGetSubTreePathsResponse&
+                          subtree) mutable {
+            getSwInfo(asyncResp, std::move(payload), std::move(memfd),
+                      applyTimeNewVal, targets[0], ec, subtree);
+        });
+    }
+}
+
+inline void
     updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                            const crow::Request& req, MultipartParser&& parser)
 {
@@ -816,12 +1079,21 @@
         multipart->applyTime = "OnReset";
     }
 
-    setApplyTime(asyncResp, *multipart->applyTime);
+    if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS)
+    {
+        processUpdateRequest(asyncResp, req, multipart->uploadData,
+                             *multipart->applyTime, multipart->targets);
+    }
+    else
+    {
+        setApplyTime(asyncResp, *multipart->applyTime);
 
-    // Setup callback for when new software detected
-    monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService");
+        // Setup callback for when new software detected
+        monitorForSoftwareAvailable(asyncResp, req,
+                                    "/redfish/v1/UpdateService");
 
-    uploadImageFile(asyncResp->res, multipart->uploadData);
+        uploadImageFile(asyncResp->res, multipart->uploadData);
+    }
 }
 
 inline void