|  | /* | 
|  | Copyright (c) 2018 Intel Corporation | 
|  |  | 
|  | Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | you may not use this file except in compliance with the License. | 
|  | You may obtain a copy of the License at | 
|  |  | 
|  | http://www.apache.org/licenses/LICENSE-2.0 | 
|  |  | 
|  | Unless required by applicable law or agreed to in writing, software | 
|  | distributed under the License is distributed on an "AS IS" BASIS, | 
|  | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | See the License for the specific language governing permissions and | 
|  | limitations under the License. | 
|  | */ | 
|  | #pragma once | 
|  |  | 
|  | #include "bmcweb_config.h" | 
|  |  | 
|  | #include "app.hpp" | 
|  | #include "dbus_utility.hpp" | 
|  | #include "error_messages.hpp" | 
|  | #include "generated/enums/update_service.hpp" | 
|  | #include "multipart_parser.hpp" | 
|  | #include "ossl_random.hpp" | 
|  | #include "query.hpp" | 
|  | #include "registries/privilege_registry.hpp" | 
|  | #include "task.hpp" | 
|  | #include "task_messages.hpp" | 
|  | #include "utils/collection.hpp" | 
|  | #include "utils/dbus_utils.hpp" | 
|  | #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> | 
|  | #include <sdbusplus/bus/match.hpp> | 
|  | #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 | 
|  | { | 
|  |  | 
|  | // Match signals added on software path | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | 
|  | static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher; | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | 
|  | static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher; | 
|  | // Only allow one update at a time | 
|  | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) | 
|  | static bool fwUpdateInProgress = false; | 
|  | // Timer for software available | 
|  | // 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; | 
|  | fwUpdateMatcher = nullptr; | 
|  | fwUpdateErrorMatcher = nullptr; | 
|  | } | 
|  |  | 
|  | inline void activateImage(const std::string& objPath, | 
|  | const std::string& service) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service); | 
|  | sdbusplus::asio::setProperty( | 
|  | *crow::connections::systemBus, service, objPath, | 
|  | "xyz.openbmc_project.Software.Activation", "RequestedActivation", | 
|  | "xyz.openbmc_project.Software.Activation.RequestedActivations.Active", | 
|  | [](const boost::system::error_code& ec) { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("error_code = {}", ec); | 
|  | BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline bool handleCreateTask(const boost::system::error_code& ec2, | 
|  | sdbusplus::message_t& msg, | 
|  | const std::shared_ptr<task::TaskData>& taskData) | 
|  | { | 
|  | if (ec2) | 
|  | { | 
|  | return task::completed; | 
|  | } | 
|  |  | 
|  | std::string iface; | 
|  | dbus::utility::DBusPropertiesMap values; | 
|  |  | 
|  | std::string index = std::to_string(taskData->index); | 
|  | msg.read(iface, values); | 
|  |  | 
|  | if (iface == "xyz.openbmc_project.Software.Activation") | 
|  | { | 
|  | const std::string* state = nullptr; | 
|  | for (const auto& property : values) | 
|  | { | 
|  | if (property.first == "Activation") | 
|  | { | 
|  | state = std::get_if<std::string>(&property.second); | 
|  | if (state == nullptr) | 
|  | { | 
|  | taskData->messages.emplace_back(messages::internalError()); | 
|  | return task::completed; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (state == nullptr) | 
|  | { | 
|  | return !task::completed; | 
|  | } | 
|  |  | 
|  | if (state->ends_with("Invalid") || state->ends_with("Failed")) | 
|  | { | 
|  | taskData->state = "Exception"; | 
|  | taskData->status = "Warning"; | 
|  | taskData->messages.emplace_back(messages::taskAborted(index)); | 
|  | return task::completed; | 
|  | } | 
|  |  | 
|  | if (state->ends_with("Staged")) | 
|  | { | 
|  | taskData->state = "Stopping"; | 
|  | taskData->messages.emplace_back(messages::taskPaused(index)); | 
|  |  | 
|  | // its staged, set a long timer to | 
|  | // allow them time to complete the | 
|  | // update (probably cycle the | 
|  | // system) if this expires then | 
|  | // task will be canceled | 
|  | taskData->extendTimer(std::chrono::hours(5)); | 
|  | return !task::completed; | 
|  | } | 
|  |  | 
|  | if (state->ends_with("Active")) | 
|  | { | 
|  | taskData->messages.emplace_back(messages::taskCompletedOK(index)); | 
|  | taskData->state = "Completed"; | 
|  | return task::completed; | 
|  | } | 
|  | } | 
|  | else if (iface == "xyz.openbmc_project.Software.ActivationProgress") | 
|  | { | 
|  | const uint8_t* progress = nullptr; | 
|  | for (const auto& property : values) | 
|  | { | 
|  | if (property.first == "Progress") | 
|  | { | 
|  | progress = std::get_if<uint8_t>(&property.second); | 
|  | if (progress == nullptr) | 
|  | { | 
|  | taskData->messages.emplace_back(messages::internalError()); | 
|  | return task::completed; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (progress == nullptr) | 
|  | { | 
|  | return !task::completed; | 
|  | } | 
|  | taskData->percentComplete = *progress; | 
|  | taskData->messages.emplace_back( | 
|  | messages::taskProgressChanged(index, *progress)); | 
|  |  | 
|  | // if we're getting status updates it's | 
|  | // still alive, update timer | 
|  | taskData->extendTimer(std::chrono::minutes(5)); | 
|  | } | 
|  |  | 
|  | // as firmware update often results in a | 
|  | // reboot, the task  may never "complete" | 
|  | // unless it is an error | 
|  |  | 
|  | return !task::completed; | 
|  | } | 
|  |  | 
|  | inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | task::Payload&& payload, | 
|  | const sdbusplus::message::object_path& objPath) | 
|  | { | 
|  | std::shared_ptr<task::TaskData> task = task::TaskData::createTask( | 
|  | std::bind_front(handleCreateTask), | 
|  | "type='signal',interface='org.freedesktop.DBus.Properties'," | 
|  | "member='PropertiesChanged',path='" + | 
|  | objPath.str + "'"); | 
|  | task->startTimer(std::chrono::minutes(5)); | 
|  | task->populateResp(asyncResp->res); | 
|  | task->payload.emplace(std::move(payload)); | 
|  | } | 
|  |  | 
|  | // Note that asyncResp can be either a valid pointer or nullptr. If nullptr | 
|  | // then no asyncResp updates will occur | 
|  | inline void | 
|  | softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | sdbusplus::message_t& m, task::Payload&& payload) | 
|  | { | 
|  | dbus::utility::DBusInterfacesMap interfacesProperties; | 
|  |  | 
|  | sdbusplus::message::object_path objPath; | 
|  |  | 
|  | m.read(objPath, interfacesProperties); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); | 
|  | for (const auto& interface : interfacesProperties) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("interface = {}", interface.first); | 
|  |  | 
|  | if (interface.first == "xyz.openbmc_project.Software.Activation") | 
|  | { | 
|  | // Retrieve service and activate | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.Software.Activation"}; | 
|  | dbus::utility::getDbusObject( | 
|  | objPath.str, interfaces, | 
|  | [objPath, asyncResp, payload(std::move(payload))]( | 
|  | const boost::system::error_code& ec, | 
|  | const std::vector< | 
|  | std::pair<std::string, std::vector<std::string>>>& | 
|  | objInfo) mutable { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("error_code = {}", ec); | 
|  | BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); | 
|  | if (asyncResp) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | cleanUp(); | 
|  | return; | 
|  | } | 
|  | // Ensure we only got one service back | 
|  | if (objInfo.size() != 1) | 
|  | { | 
|  | BMCWEB_LOG_ERROR("Invalid Object Size {}", | 
|  | objInfo.size()); | 
|  | if (asyncResp) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | cleanUp(); | 
|  | return; | 
|  | } | 
|  | // cancel timer only when | 
|  | // xyz.openbmc_project.Software.Activation interface | 
|  | // is added | 
|  | fwAvailableTimer = nullptr; | 
|  |  | 
|  | activateImage(objPath.str, objInfo[0].first); | 
|  | if (asyncResp) | 
|  | { | 
|  | createTask(asyncResp, std::move(payload), objPath); | 
|  | } | 
|  | fwUpdateInProgress = false; | 
|  | }); | 
|  |  | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void afterAvailbleTimerAsyncWait( | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const boost::system::error_code& ec) | 
|  | { | 
|  | cleanUp(); | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | // expected, we were canceled before the timer completed. | 
|  | return; | 
|  | } | 
|  | BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created"); | 
|  | BMCWEB_LOG_ERROR("FW image may has already been uploaded to server"); | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR("Async_wait failed{}", ec); | 
|  | return; | 
|  | } | 
|  | if (asyncResp) | 
|  | { | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void | 
|  | handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& /*url*/, const std::string& type) | 
|  | { | 
|  | // NOLINTBEGIN(bugprone-branch-clone) | 
|  | if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == | 
|  | "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") | 
|  | { | 
|  | messages::resourceAlreadyExists(asyncResp->res, "UpdateService", | 
|  | "Version", "uploaded version"); | 
|  | } | 
|  | else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == | 
|  | "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == | 
|  | "xyz.openbmc_project.Software.Version.Error.InvalidSignature") | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | } | 
|  | else if (type == | 
|  | "xyz.openbmc_project.Software.Image.Error.InternalFailure" || | 
|  | type == "xyz.openbmc_project.Software.Version.Error.HostFile") | 
|  | { | 
|  | BMCWEB_LOG_ERROR("Software Image Error type={}", type); | 
|  | redfish::messages::internalError(asyncResp->res); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Unrelated error types. Ignored | 
|  | BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type); | 
|  | return; | 
|  | } | 
|  | // NOLINTEND(bugprone-branch-clone) | 
|  | // Clear the timer | 
|  | fwAvailableTimer = nullptr; | 
|  | } | 
|  |  | 
|  | inline void | 
|  | afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& url, sdbusplus::message_t& m) | 
|  | { | 
|  | dbus::utility::DBusInterfacesMap interfacesProperties; | 
|  | sdbusplus::message::object_path objPath; | 
|  | m.read(objPath, interfacesProperties); | 
|  | BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); | 
|  | for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& | 
|  | interface : interfacesProperties) | 
|  | { | 
|  | if (interface.first == "xyz.openbmc_project.Logging.Entry") | 
|  | { | 
|  | for (const std::pair<std::string, dbus::utility::DbusVariantType>& | 
|  | value : interface.second) | 
|  | { | 
|  | if (value.first != "Message") | 
|  | { | 
|  | continue; | 
|  | } | 
|  | const std::string* type = | 
|  | std::get_if<std::string>(&value.second); | 
|  | if (type == nullptr) | 
|  | { | 
|  | // if this was our message, timeout will cover it | 
|  | return; | 
|  | } | 
|  | handleUpdateErrorType(asyncResp, url, *type); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Note that asyncResp can be either a valid pointer or nullptr. If nullptr | 
|  | // then no asyncResp updates will occur | 
|  | inline void monitorForSoftwareAvailable( | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const crow::Request& req, const std::string& url, | 
|  | int timeoutTimeSeconds = 25) | 
|  | { | 
|  | // Only allow one FW update at a time | 
|  | if (fwUpdateInProgress) | 
|  | { | 
|  | if (asyncResp) | 
|  | { | 
|  | messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (req.ioService == nullptr) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | fwAvailableTimer = | 
|  | std::make_unique<boost::asio::steady_timer>(*req.ioService); | 
|  |  | 
|  | fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); | 
|  |  | 
|  | fwAvailableTimer->async_wait( | 
|  | std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); | 
|  |  | 
|  | task::Payload payload(req); | 
|  | auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { | 
|  | BMCWEB_LOG_DEBUG("Match fired"); | 
|  | softwareInterfaceAdded(asyncResp, m, std::move(payload)); | 
|  | }; | 
|  |  | 
|  | fwUpdateInProgress = true; | 
|  |  | 
|  | fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( | 
|  | *crow::connections::systemBus, | 
|  | "interface='org.freedesktop.DBus.ObjectManager',type='signal'," | 
|  | "member='InterfacesAdded',path='/xyz/openbmc_project/software'", | 
|  | callback); | 
|  |  | 
|  | fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( | 
|  | *crow::connections::systemBus, | 
|  | "interface='org.freedesktop.DBus.ObjectManager',type='signal'," | 
|  | "member='InterfacesAdded'," | 
|  | "path='/xyz/openbmc_project/logging'", | 
|  | std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); | 
|  | } | 
|  |  | 
|  | inline std::optional<boost::urls::url> parseSimpleUpdateUrl( | 
|  | std::string imageURI, std::optional<std::string> transferProtocol, | 
|  | crow::Response& res) | 
|  | { | 
|  | if (imageURI.find("://") == std::string::npos) | 
|  | { | 
|  | if (imageURI.starts_with("/")) | 
|  | { | 
|  | messages::actionParameterValueTypeError( | 
|  | res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); | 
|  | return std::nullopt; | 
|  | } | 
|  | if (!transferProtocol) | 
|  | { | 
|  | messages::actionParameterValueTypeError( | 
|  | res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); | 
|  | return std::nullopt; | 
|  | } | 
|  | // OpenBMC currently only supports TFTP or HTTPS | 
|  | if (*transferProtocol == "TFTP") | 
|  | { | 
|  | imageURI = "tftp://" + imageURI; | 
|  | } | 
|  | else if (*transferProtocol == "HTTPS") | 
|  | { | 
|  | imageURI = "https://" + imageURI; | 
|  | } | 
|  | else | 
|  | { | 
|  | messages::actionParameterNotSupported(res, "TransferProtocol", | 
|  | *transferProtocol); | 
|  | BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}", | 
|  | *transferProtocol); | 
|  | return std::nullopt; | 
|  | } | 
|  | } | 
|  |  | 
|  | boost::system::result<boost::urls::url> url = | 
|  | boost::urls::parse_absolute_uri(imageURI); | 
|  | if (!url) | 
|  | { | 
|  | messages::actionParameterValueTypeError(res, imageURI, "ImageURI", | 
|  | "UpdateService.SimpleUpdate"); | 
|  |  | 
|  | return std::nullopt; | 
|  | } | 
|  | url->normalize(); | 
|  |  | 
|  | if (url->scheme() == "tftp") | 
|  | { | 
|  | if (url->encoded_path().size() < 2) | 
|  | { | 
|  | messages::actionParameterNotSupported(res, "ImageURI", | 
|  | url->buffer()); | 
|  | return std::nullopt; | 
|  | } | 
|  | } | 
|  | else if (url->scheme() == "https") | 
|  | { | 
|  | // Empty paths default to "/" | 
|  | if (url->encoded_path().empty()) | 
|  | { | 
|  | url->set_encoded_path("/"); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | messages::actionParameterNotSupported(res, "ImageURI", imageURI); | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | if (url->encoded_path().empty()) | 
|  | { | 
|  | messages::actionParameterValueTypeError(res, imageURI, "ImageURI", | 
|  | "UpdateService.SimpleUpdate"); | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | return *url; | 
|  | } | 
|  |  | 
|  | inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const boost::urls::url_view_base& url) | 
|  | { | 
|  | messages::actionParameterNotSupported(asyncResp->res, "ImageURI", | 
|  | url.buffer()); | 
|  | } | 
|  |  | 
|  | inline void handleUpdateServiceSimpleUpdateAction( | 
|  | crow::App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::optional<std::string> transferProtocol; | 
|  | std::string imageURI; | 
|  |  | 
|  | BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); | 
|  |  | 
|  | // User can pass in both TransferProtocol and ImageURI parameters or | 
|  | // they can pass in just the ImageURI with the transfer protocol | 
|  | // embedded within it. | 
|  | // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin | 
|  | // 2) ImageURI:tftp://1.1.1.1/myfile.bin | 
|  |  | 
|  | if (!json_util::readJsonAction( // | 
|  | req, asyncResp->res, // | 
|  | "ImageURI", imageURI, // | 
|  | "TransferProtocol", transferProtocol // | 
|  | )) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::optional<boost::urls::url> url = | 
|  | parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res); | 
|  | if (!url) | 
|  | { | 
|  | return; | 
|  | } | 
|  | if (url->scheme() == "https") | 
|  | { | 
|  | doHttpsUpdate(asyncResp, *url); | 
|  | } | 
|  | else | 
|  | { | 
|  | messages::actionParameterNotSupported(asyncResp->res, "ImageURI", | 
|  | url->buffer()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); | 
|  | } | 
|  |  | 
|  | inline void uploadImageFile(crow::Response& res, std::string_view body) | 
|  | { | 
|  | std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); | 
|  | std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | | 
|  | std::ofstream::trunc); | 
|  | // set the permission of the file to 640 | 
|  | std::filesystem::perms permission = | 
|  | std::filesystem::perms::owner_read | std::filesystem::perms::group_read; | 
|  | std::filesystem::permissions(filepath, permission); | 
|  | out << body; | 
|  |  | 
|  | if (out.bad()) | 
|  | { | 
|  | messages::internalError(res); | 
|  | cleanUp(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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.ApplyTime.RequestedApplyTimes.Immediate"; | 
|  | } | 
|  | else if (applyTime == "OnReset") | 
|  | { | 
|  | applyTimeNewVal = | 
|  | "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.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) | 
|  | { | 
|  | std::string applyTimeNewVal; | 
|  | if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal)) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings", | 
|  | sdbusplus::message::object_path( | 
|  | "/xyz/openbmc_project/software/apply_time"), | 
|  | "xyz.openbmc_project.Software.ApplyTime", | 
|  | "RequestedApplyTime", applyTimeNewVal); | 
|  | } | 
|  |  | 
|  | struct MultiPartUpdateParameters | 
|  | { | 
|  | std::optional<std::string> applyTime; | 
|  | std::string uploadData; | 
|  | 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, | 
|  | MultipartParser parser) | 
|  | { | 
|  | MultiPartUpdateParameters multiRet; | 
|  | for (FormPart& formpart : parser.mime_fields) | 
|  | { | 
|  | boost::beast::http::fields::const_iterator it = | 
|  | formpart.fields.find("Content-Disposition"); | 
|  | if (it == formpart.fields.end()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); | 
|  | return std::nullopt; | 
|  | } | 
|  | BMCWEB_LOG_INFO("Parsing value {}", it->value()); | 
|  |  | 
|  | // The construction parameters of param_list must start with `;` | 
|  | size_t index = it->value().find(';'); | 
|  | if (index == std::string::npos) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | for (const auto& param : | 
|  | boost::beast::http::param_list{it->value().substr(index)}) | 
|  | { | 
|  | if (param.first != "name" || param.second.empty()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (param.second == "UpdateParameters") | 
|  | { | 
|  | std::vector<std::string> tempTargets; | 
|  | nlohmann::json content = | 
|  | nlohmann::json::parse(formpart.content, nullptr, false); | 
|  | if (content.is_discarded()) | 
|  | { | 
|  | return std::nullopt; | 
|  | } | 
|  | nlohmann::json::object_t* obj = | 
|  | content.get_ptr<nlohmann::json::object_t*>(); | 
|  | if (obj == nullptr) | 
|  | { | 
|  | messages::propertyValueTypeError( | 
|  | asyncResp->res, formpart.content, "UpdateParameters"); | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | if (!json_util::readJsonObject( // | 
|  | *obj, asyncResp->res, // | 
|  | "@Redfish.OperationApplyTime", multiRet.applyTime, // | 
|  | "Targets", tempTargets // | 
|  | )) | 
|  | { | 
|  | return std::nullopt; | 
|  | } | 
|  |  | 
|  | for (size_t urlIndex = 0; urlIndex < tempTargets.size(); | 
|  | urlIndex++) | 
|  | { | 
|  | 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()) | 
|  | { | 
|  | messages::propertyValueFormatError( | 
|  | asyncResp->res, target, | 
|  | std::format("Targets/{}", urlIndex)); | 
|  | return std::nullopt; | 
|  | } | 
|  | multiRet.targets.emplace_back(res.value()); | 
|  | } | 
|  | if (multiRet.targets.size() != 1) | 
|  | { | 
|  | messages::propertyValueFormatError( | 
|  | asyncResp->res, multiRet.targets, "Targets"); | 
|  | return std::nullopt; | 
|  | } | 
|  | } | 
|  | else if (param.second == "UpdateFile") | 
|  | { | 
|  | multiRet.uploadData = std::move(formpart.content); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (multiRet.uploadData.empty()) | 
|  | { | 
|  | BMCWEB_LOG_ERROR("Upload data is NULL"); | 
|  | messages::propertyMissing(asyncResp->res, "UpdateFile"); | 
|  | return std::nullopt; | 
|  | } | 
|  | if (multiRet.targets.empty()) | 
|  | { | 
|  | messages::propertyMissing(asyncResp->res, "Targets"); | 
|  | return std::nullopt; | 
|  | } | 
|  | return multiRet; | 
|  | } | 
|  |  | 
|  | 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 on {} Success, retPath = {}", | 
|  | objectPath, retPath.str); | 
|  | createTask(asyncResp, std::move(payload), retPath); | 
|  | } | 
|  |  | 
|  | 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 getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | task::Payload payload, const MemoryFileDescriptor& memfd, | 
|  | const std::string& applyTime, const std::string& target, | 
|  | const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreeResponse& subtree) | 
|  | { | 
|  | using SwInfoMap = std::unordered_map< | 
|  | std::string, std::pair<sdbusplus::message::object_path, std::string>>; | 
|  | 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& entry : subtree) | 
|  | { | 
|  | sdbusplus::message::object_path path(entry.first); | 
|  | std::string swId = path.filename(); | 
|  | swInfoMap.emplace(swId, make_pair(path, entry.second[0].first)); | 
|  | } | 
|  |  | 
|  | 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 {} serviceName {}", | 
|  | swEntry->second.first.str, swEntry->second.second); | 
|  |  | 
|  | startUpdate(asyncResp, std::move(payload), memfd, applyTime, | 
|  | swEntry->second.first.str, swEntry->second.second); | 
|  | } | 
|  |  | 
|  | inline void handleBMCUpdate( | 
|  | 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::MapperEndPoints& functionalSoftware) | 
|  | { | 
|  | if (ec) | 
|  | { | 
|  | BMCWEB_LOG_ERROR("error_code = {}", ec); | 
|  | BMCWEB_LOG_ERROR("error msg = {}", ec.message()); | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | if (functionalSoftware.size() != 1) | 
|  | { | 
|  | BMCWEB_LOG_ERROR("Found {} functional software endpoints", | 
|  | functionalSoftware.size()); | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | startUpdate(asyncResp, std::move(payload), memfd, applyTime, | 
|  | functionalSoftware[0], "xyz.openbmc_project.Software.Manager"); | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | 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; | 
|  | } | 
|  |  | 
|  | if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME) | 
|  | { | 
|  | dbus::utility::getAssociationEndPoints( | 
|  | "/xyz/openbmc_project/software/bmc/updateable", | 
|  | [asyncResp, payload = std::move(payload), memfd = std::move(memfd), | 
|  | applyTime]( | 
|  | const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperEndPoints& objectPaths) mutable { | 
|  | handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime, | 
|  | ec, objectPaths); | 
|  | }); | 
|  | } | 
|  | else | 
|  | { | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.Software.Version"}; | 
|  | dbus::utility::getSubTree( | 
|  | "/xyz/openbmc_project/software", 1, interfaces, | 
|  | [asyncResp, payload = std::move(payload), memfd = std::move(memfd), | 
|  | applyTime, targets](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreeResponse& | 
|  | subtree) mutable { | 
|  | getSwInfo(asyncResp, std::move(payload), memfd, applyTime, | 
|  | targets[0], ec, subtree); | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void | 
|  | updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const crow::Request& req, MultipartParser&& parser) | 
|  | { | 
|  | std::optional<MultiPartUpdateParameters> multipart = | 
|  | extractMultipartUpdateParameters(asyncResp, std::move(parser)); | 
|  | if (!multipart) | 
|  | { | 
|  | return; | 
|  | } | 
|  | if (!multipart->applyTime) | 
|  | { | 
|  | multipart->applyTime = "OnReset"; | 
|  | } | 
|  |  | 
|  | if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) | 
|  | { | 
|  | std::string applyTimeNewVal; | 
|  | if (!convertApplyTime(asyncResp->res, *multipart->applyTime, | 
|  | applyTimeNewVal)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | task::Payload payload(req); | 
|  |  | 
|  | processUpdateRequest(asyncResp, std::move(payload), | 
|  | multipart->uploadData, applyTimeNewVal, | 
|  | multipart->targets); | 
|  | } | 
|  | else | 
|  | { | 
|  | setApplyTime(asyncResp, *multipart->applyTime); | 
|  |  | 
|  | // Setup callback for when new software detected | 
|  | monitorForSoftwareAvailable(asyncResp, req, | 
|  | "/redfish/v1/UpdateService"); | 
|  |  | 
|  | uploadImageFile(asyncResp->res, multipart->uploadData); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const crow::Request& req) | 
|  | { | 
|  | if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) | 
|  | { | 
|  | task::Payload payload(req); | 
|  | // HTTP push only supports BMC updates (with ApplyTime as immediate) for | 
|  | // backwards compatibility. Specific component updates will be handled | 
|  | // through Multipart form HTTP push. | 
|  | std::vector<std::string> targets; | 
|  | targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME); | 
|  |  | 
|  | processUpdateRequest( | 
|  | asyncResp, std::move(payload), req.body(), | 
|  | "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate", | 
|  | targets); | 
|  | } | 
|  | else | 
|  | { | 
|  | // Setup callback for when new software detected | 
|  | monitorForSoftwareAvailable(asyncResp, req, | 
|  | "/redfish/v1/UpdateService"); | 
|  |  | 
|  | uploadImageFile(asyncResp->res, req.body()); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void | 
|  | handleUpdateServicePost(App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | std::string_view contentType = req.getHeaderValue("Content-Type"); | 
|  |  | 
|  | BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); | 
|  |  | 
|  | // Make sure that content type is application/octet-stream or | 
|  | // multipart/form-data | 
|  | if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) | 
|  | { | 
|  | doHTTPUpdate(asyncResp, req); | 
|  | } | 
|  | else if (contentType.starts_with("multipart/form-data")) | 
|  | { | 
|  | MultipartParser parser; | 
|  |  | 
|  | ParserError ec = parser.parse(req); | 
|  | if (ec != ParserError::PARSER_SUCCESS) | 
|  | { | 
|  | // handle error | 
|  | BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", | 
|  | static_cast<int>(ec)); | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | updateMultipartContext(asyncResp, req, std::move(parser)); | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); | 
|  | asyncResp->res.result(boost::beast::http::status::bad_request); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void | 
|  | handleUpdateServiceGet(App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | asyncResp->res.jsonValue["@odata.type"] = | 
|  | "#UpdateService.v1_11_1.UpdateService"; | 
|  | asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; | 
|  | asyncResp->res.jsonValue["Id"] = "UpdateService"; | 
|  | asyncResp->res.jsonValue["Description"] = "Service for Software Update"; | 
|  | asyncResp->res.jsonValue["Name"] = "Update Service"; | 
|  |  | 
|  | asyncResp->res.jsonValue["HttpPushUri"] = | 
|  | "/redfish/v1/UpdateService/update"; | 
|  | asyncResp->res.jsonValue["MultipartHttpPushUri"] = | 
|  | "/redfish/v1/UpdateService/update"; | 
|  |  | 
|  | // UpdateService cannot be disabled | 
|  | asyncResp->res.jsonValue["ServiceEnabled"] = true; | 
|  | asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = | 
|  | "/redfish/v1/UpdateService/FirmwareInventory"; | 
|  | // Get the MaxImageSizeBytes | 
|  | asyncResp->res.jsonValue["MaxImageSizeBytes"] = | 
|  | BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024; | 
|  |  | 
|  | // Update Actions object. | 
|  | nlohmann::json& updateSvcSimpleUpdate = | 
|  | asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; | 
|  | updateSvcSimpleUpdate["target"] = | 
|  | "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; | 
|  |  | 
|  | nlohmann::json::array_t allowed; | 
|  | allowed.emplace_back(update_service::TransferProtocolType::HTTPS); | 
|  |  | 
|  | if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION) | 
|  | { | 
|  | allowed.emplace_back(update_service::TransferProtocolType::TFTP); | 
|  | } | 
|  |  | 
|  | updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = | 
|  | std::move(allowed); | 
|  |  | 
|  | asyncResp->res | 
|  | .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] = | 
|  | update_service::ApplyTime::Immediate; | 
|  | } | 
|  |  | 
|  | inline void handleUpdateServiceFirmwareInventoryCollectionGet( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | asyncResp->res.jsonValue["@odata.type"] = | 
|  | "#SoftwareInventoryCollection.SoftwareInventoryCollection"; | 
|  | asyncResp->res.jsonValue["@odata.id"] = | 
|  | "/redfish/v1/UpdateService/FirmwareInventory"; | 
|  | asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; | 
|  | const std::array<const std::string_view, 1> iface = { | 
|  | "xyz.openbmc_project.Software.Version"}; | 
|  |  | 
|  | redfish::collection_util::getCollectionMembers( | 
|  | asyncResp, | 
|  | boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface, | 
|  | "/xyz/openbmc_project/software"); | 
|  | } | 
|  |  | 
|  | /* Fill related item links (i.e. bmc, bios) in for inventory */ | 
|  | inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& purpose) | 
|  | { | 
|  | if (purpose == sw_util::bmcPurpose) | 
|  | { | 
|  | nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; | 
|  | nlohmann::json::object_t item; | 
|  | item["@odata.id"] = boost::urls::format( | 
|  | "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME); | 
|  | relatedItem.emplace_back(std::move(item)); | 
|  | asyncResp->res.jsonValue["RelatedItem@odata.count"] = | 
|  | relatedItem.size(); | 
|  | } | 
|  | else if (purpose == sw_util::biosPurpose) | 
|  | { | 
|  | nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; | 
|  | nlohmann::json::object_t item; | 
|  | item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios", | 
|  | BMCWEB_REDFISH_SYSTEM_URI_NAME); | 
|  | relatedItem.emplace_back(std::move(item)); | 
|  | asyncResp->res.jsonValue["RelatedItem@odata.count"] = | 
|  | relatedItem.size(); | 
|  | } | 
|  | else | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); | 
|  | } | 
|  | } | 
|  |  | 
|  | inline void | 
|  | getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& service, const std::string& path, | 
|  | const std::string& swId) | 
|  | { | 
|  | sdbusplus::asio::getAllProperties( | 
|  | *crow::connections::systemBus, service, path, | 
|  | "xyz.openbmc_project.Software.Version", | 
|  | [asyncResp, | 
|  | swId](const boost::system::error_code& ec, | 
|  | const dbus::utility::DBusPropertiesMap& propertiesList) { | 
|  | if (ec) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const std::string* swInvPurpose = nullptr; | 
|  | const std::string* version = nullptr; | 
|  |  | 
|  | const bool success = sdbusplus::unpackPropertiesNoThrow( | 
|  | dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", | 
|  | swInvPurpose, "Version", version); | 
|  |  | 
|  | if (!success) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (swInvPurpose == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!"); | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose); | 
|  |  | 
|  | if (version == nullptr) | 
|  | { | 
|  | BMCWEB_LOG_DEBUG("Can't find property \"Version\"!"); | 
|  |  | 
|  | messages::internalError(asyncResp->res); | 
|  |  | 
|  | return; | 
|  | } | 
|  | asyncResp->res.jsonValue["Version"] = *version; | 
|  | asyncResp->res.jsonValue["Id"] = swId; | 
|  |  | 
|  | // swInvPurpose is of format: | 
|  | // xyz.openbmc_project.Software.Version.VersionPurpose.ABC | 
|  | // Translate this to "ABC image" | 
|  | size_t endDesc = swInvPurpose->rfind('.'); | 
|  | if (endDesc == std::string::npos) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  | endDesc++; | 
|  | if (endDesc >= swInvPurpose->size()) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string formatDesc = swInvPurpose->substr(endDesc); | 
|  | asyncResp->res.jsonValue["Description"] = formatDesc + " image"; | 
|  | getRelatedItems(asyncResp, *swInvPurpose); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void handleUpdateServiceFirmwareInventoryGet( | 
|  | App& app, const crow::Request& req, | 
|  | const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
|  | const std::string& param) | 
|  | { | 
|  | if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | std::shared_ptr<std::string> swId = std::make_shared<std::string>(param); | 
|  |  | 
|  | asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( | 
|  | "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); | 
|  |  | 
|  | constexpr std::array<std::string_view, 1> interfaces = { | 
|  | "xyz.openbmc_project.Software.Version"}; | 
|  | dbus::utility::getSubTree( | 
|  | "/", 0, interfaces, | 
|  | [asyncResp, | 
|  | swId](const boost::system::error_code& ec, | 
|  | const dbus::utility::MapperGetSubTreeResponse& subtree) { | 
|  | BMCWEB_LOG_DEBUG("doGet callback..."); | 
|  | if (ec) | 
|  | { | 
|  | messages::internalError(asyncResp->res); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Ensure we find our input swId, otherwise return an error | 
|  | bool found = false; | 
|  | for (const std::pair<std::string, | 
|  | std::vector<std::pair< | 
|  | std::string, std::vector<std::string>>>>& | 
|  | obj : subtree) | 
|  | { | 
|  | if (!obj.first.ends_with(*swId)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (obj.second.empty()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | found = true; | 
|  | sw_util::getSwStatus(asyncResp, swId, obj.second[0].first); | 
|  | getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, | 
|  | *swId); | 
|  | } | 
|  | if (!found) | 
|  | { | 
|  | BMCWEB_LOG_WARNING("Input swID {} not found!", *swId); | 
|  | messages::resourceMissingAtURI( | 
|  | asyncResp->res, | 
|  | boost::urls::format( | 
|  | "/redfish/v1/UpdateService/FirmwareInventory/{}", | 
|  | *swId)); | 
|  | return; | 
|  | } | 
|  | asyncResp->res.jsonValue["@odata.type"] = | 
|  | "#SoftwareInventory.v1_1_0.SoftwareInventory"; | 
|  | asyncResp->res.jsonValue["Name"] = "Software Inventory"; | 
|  | asyncResp->res.jsonValue["Status"]["HealthRollup"] = | 
|  | resource::Health::OK; | 
|  |  | 
|  | asyncResp->res.jsonValue["Updateable"] = false; | 
|  | sw_util::getSwUpdatableStatus(asyncResp, swId); | 
|  | }); | 
|  | } | 
|  |  | 
|  | inline void requestRoutesUpdateService(App& app) | 
|  | { | 
|  | BMCWEB_ROUTE( | 
|  | app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") | 
|  | .privileges(redfish::privileges::postUpdateService) | 
|  | .methods(boost::beast::http::verb::post)(std::bind_front( | 
|  | handleUpdateServiceSimpleUpdateAction, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") | 
|  | .privileges(redfish::privileges::getSoftwareInventory) | 
|  | .methods(boost::beast::http::verb::get)(std::bind_front( | 
|  | handleUpdateServiceFirmwareInventoryGet, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") | 
|  | .privileges(redfish::privileges::getUpdateService) | 
|  | .methods(boost::beast::http::verb::get)( | 
|  | std::bind_front(handleUpdateServiceGet, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") | 
|  | .privileges(redfish::privileges::postUpdateService) | 
|  | .methods(boost::beast::http::verb::post)( | 
|  | std::bind_front(handleUpdateServicePost, std::ref(app))); | 
|  |  | 
|  | BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") | 
|  | .privileges(redfish::privileges::getSoftwareInventoryCollection) | 
|  | .methods(boost::beast::http::verb::get)(std::bind_front( | 
|  | handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app))); | 
|  | } | 
|  |  | 
|  | } // namespace redfish |