/* // 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 static 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