diff options
Diffstat (limited to 'redfish-core/lib/update_service.hpp')
-rw-r--r-- | redfish-core/lib/update_service.hpp | 660 |
1 files changed, 492 insertions, 168 deletions
diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp index 6895cb4949..23b5a3cad8 100644 --- a/redfish-core/lib/update_service.hpp +++ b/redfish-core/lib/update_service.hpp @@ -32,6 +32,8 @@ #include "utils/json_utils.hpp" #include "utils/sw_utils.hpp" +#include <sys/mman.h> + #include <boost/system/error_code.hpp> #include <boost/url/format.hpp> #include <sdbusplus/asio/property.hpp> @@ -39,10 +41,16 @@ #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 { @@ -59,6 +67,41 @@ static bool fwUpdateInProgress = false; // 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; @@ -83,6 +126,121 @@ inline void activateImage(const std::string& objPath, }); } +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 static void @@ -142,125 +300,7 @@ static void activateImage(objPath.str, objInfo[0].first); if (asyncResp) { - std::shared_ptr<task::TaskData> task = - task::TaskData::createTask( - [](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; - }, - "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)); + createTask(asyncResp, std::move(payload), objPath); } fwUpdateInProgress = false; }); @@ -464,11 +504,15 @@ inline std::optional<boost::urls::url> res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); return std::nullopt; } - // OpenBMC currently only supports TFTP + // 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", @@ -499,6 +543,14 @@ inline std::optional<boost::urls::url> 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); @@ -515,15 +567,23 @@ inline std::optional<boost::urls::url> 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 doTftpUpdate(const crow::Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const boost::urls::url_view_base& url) { -#ifndef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE - messages::actionParameterNotSupported(asyncResp->res, "ImageURI", - url.buffer()); - return; -#endif + if (!BMCWEB_INSECURE_TFTP_UPDATE) + { + messages::actionParameterNotSupported(asyncResp->res, "ImageURI", + url.buffer()); + return; + } std::string path(url.encoded_path()); if (path.size() < 2) @@ -606,6 +666,10 @@ inline void handleUpdateServiceSimpleUpdateAction( { doTftpUpdate(req, asyncResp, *url); } + else if (url->scheme() == "https") + { + doHttpsUpdate(asyncResp, *url); + } else { messages::actionParameterNotSupported(asyncResp->res, "ImageURI", @@ -636,10 +700,10 @@ inline void uploadImageFile(crow::Response& res, std::string_view body) } } -inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::string& applyTime) +// Convert the Request Apply Time to the D-Bus value +inline bool convertApplyTime(crow::Response& res, const std::string& applyTime, + std::string& applyTimeNewVal) { - std::string applyTimeNewVal; if (applyTime == "Immediate") { applyTimeNewVal = @@ -652,10 +716,21 @@ inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, } else { - BMCWEB_LOG_INFO( - "ApplyTime value is not in the list of acceptable values"); - messages::propertyValueNotInList(asyncResp->res, applyTime, - "ApplyTime"); + 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; } @@ -666,22 +741,54 @@ inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, "RequestedApplyTime", "ApplyTime", applyTimeNewVal); } -inline void - updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const crow::Request& req, - const MultipartParser& parser) +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) { - const std::string* uploadData = nullptr; - std::optional<std::string> applyTime = "OnReset"; - bool targetFound = false; - for (const FormPart& formpart : parser.mime_fields) + 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; + return std::nullopt; } BMCWEB_LOG_INFO("Parsing value {}", it->value()); @@ -702,63 +809,277 @@ inline void if (param.second == "UpdateParameters") { - std::vector<std::string> targets; + std::vector<std::string> tempTargets; nlohmann::json content = nlohmann::json::parse(formpart.content); nlohmann::json::object_t* obj = content.get_ptr<nlohmann::json::object_t*>(); if (obj == nullptr) { - messages::propertyValueFormatError(asyncResp->res, targets, - "UpdateParameters"); - return; + messages::propertyValueTypeError( + asyncResp->res, formpart.content, "UpdateParameters"); + return std::nullopt; } if (!json_util::readJsonObject( - *obj, asyncResp->res, "Targets", targets, - "@Redfish.OperationApplyTime", applyTime)) + *obj, asyncResp->res, "Targets", tempTargets, + "@Redfish.OperationApplyTime", multiRet.applyTime)) { - return; + return std::nullopt; } - if (targets.size() != 1) + + for (size_t urlIndex = 0; urlIndex < tempTargets.size(); + urlIndex++) { - messages::propertyValueFormatError(asyncResp->res, targets, - "Targets"); - return; + 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 (targets[0] != "/redfish/v1/Managers/bmc") + if (multiRet.targets.size() != 1) { - messages::propertyValueNotInList(asyncResp->res, targets[0], - "Targets/0"); - return; + messages::propertyValueFormatError( + asyncResp->res, multiRet.targets, "Targets"); + return std::nullopt; } - targetFound = true; } else if (param.second == "UpdateFile") { - uploadData = &(formpart.content); + multiRet.uploadData = std::move(formpart.content); } } } - if (uploadData == nullptr) + 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 Success, retPath = {}", retPath.str); + createTask(asyncResp, std::move(payload), objectPath); +} + +inline void startUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + task::Payload payload, + const MemoryFileDescriptor& memfd, + const std::string& applyTime, + const std::string& objectPath, + const std::string& serviceName) +{ + crow::connections::systemBus->async_method_call( + [asyncResp, payload = std::move(payload), + objectPath](const boost::system::error_code& ec1, + const sdbusplus::message::object_path& retPath) mutable { + handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1, + retPath); + }, + serviceName, objectPath, "xyz.openbmc_project.Software.Update", + "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime); +} + +inline void getAssociatedUpdateInterface( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, + const MemoryFileDescriptor& memfd, const std::string& applyTime, + const boost::system::error_code& ec, + const dbus::utility::MapperGetSubTreeResponse& subtree) +{ + if (ec) + { + BMCWEB_LOG_ERROR("error_code = {}", ec); + BMCWEB_LOG_ERROR("error msg = {}", ec.message()); + messages::internalError(asyncResp->res); return; } - if (!targetFound) + BMCWEB_LOG_DEBUG("Found {} startUpdate subtree paths", subtree.size()); + + if (subtree.size() > 1) { - messages::propertyMissing(asyncResp->res, "targets"); + BMCWEB_LOG_ERROR("Found more than one startUpdate subtree paths"); + messages::internalError(asyncResp->res); return; } - setApplyTime(asyncResp, *applyTime); + auto objectPath = subtree[0].first; + auto serviceName = subtree[0].second[0].first; - // Setup callback for when new software detected - monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService"); + BMCWEB_LOG_DEBUG("Found objectPath {} serviceName {}", objectPath, + serviceName); + startUpdate(asyncResp, std::move(payload), memfd, applyTime, objectPath, + serviceName); +} - uploadImageFile(asyncResp->res, *uploadData); +inline void + getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + task::Payload payload, MemoryFileDescriptor memfd, + const std::string& applyTime, const std::string& target, + const boost::system::error_code& ec, + const dbus::utility::MapperGetSubTreePathsResponse& subtree) +{ + using SwInfoMap = + std::unordered_map<std::string, sdbusplus::message::object_path>; + SwInfoMap swInfoMap; + + if (ec) + { + BMCWEB_LOG_ERROR("error_code = {}", ec); + BMCWEB_LOG_ERROR("error msg = {}", ec.message()); + messages::internalError(asyncResp->res); + return; + } + BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size()); + + for (const auto& objectPath : subtree) + { + sdbusplus::message::object_path path(objectPath); + std::string swId = path.filename(); + swInfoMap.emplace(swId, path); + } + + auto swEntry = swInfoMap.find(target); + if (swEntry == swInfoMap.end()) + { + BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target); + messages::propertyValueFormatError(asyncResp->res, target, "Targets"); + return; + } + + BMCWEB_LOG_DEBUG("Found software version path {}", swEntry->second.str); + + sdbusplus::message::object_path swObjectPath = swEntry->second / + "software_version"; + constexpr std::array<std::string_view, 1> interfaces = { + "xyz.openbmc_project.Software.Update"}; + dbus::utility::getAssociatedSubTree( + swObjectPath, + sdbusplus::message::object_path("/xyz/openbmc_project/software"), 0, + interfaces, + [asyncResp, payload = std::move(payload), memfd = std::move(memfd), + applyTime]( + const boost::system::error_code& ec1, + const dbus::utility::MapperGetSubTreeResponse& subtree1) mutable { + getAssociatedUpdateInterface(asyncResp, std::move(payload), memfd, + applyTime, ec1, subtree1); + }); +} + +inline void + processUpdateRequest(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const crow::Request& req, std::string_view body, + const std::string& applyTime, + std::vector<std::string>& targets) +{ + std::string applyTimeNewVal; + + if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal)) + { + return; + } + + MemoryFileDescriptor memfd("update-image"); + if (memfd.fd == -1) + { + BMCWEB_LOG_ERROR("Failed to create image memfd"); + messages::internalError(asyncResp->res); + return; + } + if (write(memfd.fd, body.data(), body.length()) != + static_cast<ssize_t>(body.length())) + { + BMCWEB_LOG_ERROR("Failed to write to image memfd"); + messages::internalError(asyncResp->res); + return; + } + if (!memfd.rewind()) + { + messages::internalError(asyncResp->res); + return; + } + + task::Payload payload(req); + if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME) + { + startUpdate(asyncResp, std::move(payload), memfd, applyTimeNewVal, + "/xyz/openbmc_project/software/bmc", + "xyz.openbmc_project.Software.Manager"); + } + else + { + constexpr std::array<std::string_view, 1> interfaces = { + "xyz.openbmc_project.Software.Version"}; + dbus::utility::getSubTreePaths( + "/xyz/openbmc_project/software", 1, interfaces, + [asyncResp, payload = std::move(payload), memfd = std::move(memfd), + applyTimeNewVal, + targets](const boost::system::error_code& ec, + const dbus::utility::MapperGetSubTreePathsResponse& + subtree) mutable { + getSwInfo(asyncResp, std::move(payload), std::move(memfd), + applyTimeNewVal, targets[0], ec, subtree); + }); + } +} + +inline void + updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const crow::Request& req, MultipartParser&& parser) +{ + 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) + { + processUpdateRequest(asyncResp, req, multipart->uploadData, + *multipart->applyTime, multipart->targets); + } + else + { + setApplyTime(asyncResp, *multipart->applyTime); + + // Setup callback for when new software detected + monitorForSoftwareAvailable(asyncResp, req, + "/redfish/v1/UpdateService"); + + uploadImageFile(asyncResp->res, multipart->uploadData); + } } inline void @@ -797,7 +1118,7 @@ inline void return; } - updateMultipartContext(asyncResp, req, parser); + updateMultipartContext(asyncResp, req, std::move(parser)); } else { @@ -841,6 +1162,7 @@ inline void "/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) { @@ -936,7 +1258,8 @@ inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, { nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; nlohmann::json::object_t item; - item["@odata.id"] = "/redfish/v1/Managers/bmc"; + 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(); @@ -945,7 +1268,8 @@ inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, { nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; nlohmann::json::object_t item; - item["@odata.id"] = "/redfish/v1/Systems/system/Bios"; + 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(); |