/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace redfish { // Match signals added on software path // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static std::unique_ptr fwUpdateMatcher; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) static std::unique_ptr 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 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& 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(&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(&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& asyncResp, task::Payload&& payload, const sdbusplus::message::object_path& objPath) { std::shared_ptr 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& 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 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>>& 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& 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& asyncResp, const std::string& url, const std::string& type) { if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") { redfish::messages::invalidUpload(asyncResp->res, url, "Invalid archive"); } else if (type == "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") { redfish::messages::invalidUpload(asyncResp->res, url, "Invalid manifest"); } else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") { redfish::messages::invalidUpload(asyncResp->res, url, "Invalid image format"); } else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") { redfish::messages::invalidUpload(asyncResp->res, url, "Image version already exists"); redfish::messages::resourceAlreadyExists( asyncResp->res, "UpdateService", "Version", "uploaded version"); } else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") { redfish::messages::resourceExhaustion(asyncResp->res, url); } else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") { redfish::messages::invalidUpload(asyncResp->res, url, "Incompatible image version"); } else if (type == "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") { redfish::messages::invalidUpload(asyncResp->res, url, "Update Access Key Expired"); } else if (type == "xyz.openbmc_project.Software.Version.Error.InvalidSignature") { redfish::messages::invalidUpload(asyncResp->res, url, "Invalid image signature"); } 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; } // Clear the timer fwAvailableTimer = nullptr; } inline void afterUpdateErrorMatcher(const std::shared_ptr& 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& interface : interfacesProperties) { if (interface.first == "xyz.openbmc_project.Logging.Entry") { for (const std::pair& value : interface.second) { if (value.first != "Message") { continue; } const std::string* type = std::get_if(&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& 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(*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( *crow::connections::systemBus, "interface='org.freedesktop.DBus.ObjectManager',type='signal'," "member='InterfacesAdded',path='/xyz/openbmc_project/software'", callback); fwUpdateErrorMatcher = std::make_unique( *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 parseSimpleUpdateUrl( std::string imageURI, std::optional 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 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& 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& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } std::optional 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, "TransferProtocol", transferProtocol, "ImageURI", imageURI)) { BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); return; } std::optional 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& 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 applyTime; std::string uploadData; std::vector targets; }; inline std::optional processUrl(boost::system::result& 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 extractMultipartUpdateParameters( const std::shared_ptr& 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 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(); if (obj == nullptr) { messages::propertyValueTypeError( asyncResp->res, formpart.content, "UpdateParameters"); return std::nullopt; } if (!json_util::readJsonObject( *obj, asyncResp->res, "Targets", tempTargets, "@Redfish.OperationApplyTime", multiRet.applyTime)) { return std::nullopt; } for (size_t urlIndex = 0; urlIndex < tempTargets.size(); urlIndex++) { const std::string& target = tempTargets[urlIndex]; boost::system::result 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& 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& 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& 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>; 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& 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& asyncResp, task::Payload&& payload, std::string_view body, const std::string& applyTime, std::vector& 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(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/functional", [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 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& asyncResp, const crow::Request& req, MultipartParser&& parser) { std::optional 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& 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 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& 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(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& 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& 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 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& 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& 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& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } std::shared_ptr swId = std::make_shared(param); asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); constexpr std::array 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>>>& 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//") .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