Allow multipart update when no targets are specified
If no targets are provided, bmcweb will automatically select the sole
software object that implements both the StartUpdate and
xyz.openbmc_project.Software.MultipartUpdate interfaces [1], provided
there is only one such object; otherwise, the behavior remains unchanged
(an error requiring explicit target is returned).
This enables support for PLDM [2], which uses a single StartUpdate API
and handles multi-component updates internally [3]. If multiple
MultipartUpdate instances are present and no targets are specified,
an error is returned.
Behavior:
- Single target specified: Unchanged; updates proceed as before.
- No targets specified: If exactly one entity implements both
MultipartUpdate interface and StartUpdate, invoke StartUpdate on it.
Otherwise, return an error requiring explicit target.
- Multiple targets: Not supported, return an error (unchanged).
[1] https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/78905
[2] https://gerrit.openbmc.org/c/openbmc/pldm/+/79192
[3] https://gerrit.openbmc.org/c/openbmc/docs/+/76645
Tests:
- FW Update using Multipart URI
```
curl -X POST -k https://{ip}/redfish/v1/UpdateService/update-multipart --form 'UpdateParameters={"Targets":[]};type=application/json' --form "UpdateFile=@vbios.fwpkg;type=application/octet-stream"
{
"@odata.id": "/redfish/v1/TaskService/Tasks/0",
"@odata.type": "#Task.v1_4_3.Task"
...
"PercentComplete": 0,
"StartTime": "2025-08-05T13:33:23+00:00",
"TaskMonitor": "/redfish/v1/TaskService/TaskMonitors/0",
"TaskState": "Running",
"TaskStatus": "OK"
}
```
Signed-off-by: Rajeev Ranjan <ranjan.rajeev1609@gmail.com>
Change-Id: I294b1447b6faf2055419d3659f9c963aeb6d5d0d
diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp
index 72ade3c..e76dc0c 100644
--- a/redfish-core/lib/update_service.hpp
+++ b/redfish-core/lib/update_service.hpp
@@ -83,6 +83,9 @@
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer;
+/* @brief String that indicates the Software Update D-Bus interface */
+constexpr const char* updateInterface = "xyz.openbmc_project.Software.Update";
+
struct MemoryFileDescriptor
{
int fd = -1;
@@ -690,7 +693,7 @@
struct UpdateParameters
{
std::optional<std::string> applyTime;
- std::vector<std::string> targets;
+ std::optional<std::vector<std::string>> targets;
} params;
};
@@ -760,36 +763,39 @@
return std::nullopt;
}
- std::vector<std::string> tempTargets;
if (!json_util::readJsonObject( //
*obj, asyncResp->res, //
"@Redfish.OperationApplyTime", multiRet.applyTime, //
- "Targets", tempTargets //
+ "Targets", multiRet.targets //
))
{
return std::nullopt;
}
- for (size_t urlIndex = 0; urlIndex < tempTargets.size(); urlIndex++)
+ if (multiRet.targets)
{
- const std::string& target = tempTargets[urlIndex];
- boost::system::result<boost::urls::url_view> url =
- boost::urls::parse_origin_form(target);
- auto res = processUrl(url);
- if (!res.has_value())
+ if (multiRet.targets->size() > 1)
{
- messages::propertyValueFormatError(
- asyncResp->res, target, std::format("Targets/{}", urlIndex));
+ messages::propertyValueFormatError(asyncResp->res,
+ *multiRet.targets, "Targets");
return std::nullopt;
}
- multiRet.targets.emplace_back(res.value());
+
+ for (auto& target : *multiRet.targets)
+ {
+ boost::system::result<boost::urls::url_view> url =
+ boost::urls::parse_origin_form(target);
+ auto res = processUrl(url);
+ if (!res.has_value())
+ {
+ messages::propertyValueFormatError(asyncResp->res, target,
+ "Targets");
+ return std::nullopt;
+ }
+ target = res.value();
+ }
}
- if (multiRet.targets.size() != 1)
- {
- messages::propertyValueFormatError(asyncResp->res, multiRet.targets,
- "Targets");
- return std::nullopt;
- }
+
return multiRet;
}
@@ -838,11 +844,7 @@
messages::propertyMissing(asyncResp->res, "UpdateFile");
return std::nullopt;
}
- if (multiRet.params.targets.empty())
- {
- messages::propertyMissing(asyncResp->res, "Targets");
- return std::nullopt;
- }
+
return multiRet;
}
@@ -877,8 +879,8 @@
handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1,
retPath);
},
- serviceName, objectPath, "xyz.openbmc_project.Software.Update",
- "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime);
+ serviceName, objectPath, updateInterface, "StartUpdate",
+ sdbusplus::message::unix_fd(memfd.fd), applyTime);
}
inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
@@ -947,10 +949,48 @@
functionalSoftware[0], "xyz.openbmc_project.Software.Manager");
}
+inline void handleMultipartManagerUpdate(
+ 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;
+ }
+ if (subtree.size() != 1)
+ {
+ BMCWEB_LOG_ERROR("Found {} MultipartUpdate objects, expected exactly 1",
+ subtree.size());
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ const auto& [objectPath, services] = subtree[0];
+ for (const auto& [serviceName, ifaces] : services)
+ {
+ if (std::find(ifaces.begin(), ifaces.end(), updateInterface) !=
+ ifaces.end())
+ {
+ startUpdate(asyncResp, std::move(payload), memfd, applyTime,
+ objectPath, serviceName);
+ return;
+ }
+ }
+ BMCWEB_LOG_ERROR(
+ "MultipartUpdate object at {} does not implement {} interface",
+ objectPath, updateInterface);
+ messages::internalError(asyncResp->res);
+}
+
inline void processUpdateRequest(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
task::Payload&& payload, std::string_view body,
- const std::string& applyTime, std::vector<std::string>& targets)
+ const std::string& applyTime, const std::vector<std::string>& targets)
{
MemoryFileDescriptor memfd("update-image");
if (memfd.fd == -1)
@@ -972,7 +1012,23 @@
return;
}
- if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
+ if (targets.empty())
+ {
+ constexpr std::array<std::string_view, 1> interfaces = {
+ "xyz.openbmc_project.Software.MultipartUpdate"};
+ dbus::utility::getSubTree(
+ "/xyz/openbmc_project/software", 1, interfaces,
+ [asyncResp, payload = std::move(payload), memfd = std::move(memfd),
+ applyTime](const boost::system::error_code& ec,
+ const dbus::utility::MapperGetSubTreeResponse&
+ subtree) mutable {
+ handleMultipartManagerUpdate(asyncResp, std::move(payload),
+ memfd, applyTime, ec, subtree);
+ });
+ return;
+ }
+
+ if (targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME)
{
dbus::utility::getAssociationEndPoints(
"/xyz/openbmc_project/software/bmc/updateable",
@@ -1025,9 +1081,10 @@
}
task::Payload payload(req);
- processUpdateRequest(asyncResp, std::move(payload),
- multipart->uploadData, applyTimeNewVal,
- multipart->params.targets);
+ processUpdateRequest(
+ asyncResp, std::move(payload), multipart->uploadData,
+ applyTimeNewVal,
+ multipart->params.targets.value_or(std::vector<std::string>{}));
}
else
{