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