1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation 4 #pragma once 5 6 #include "bmcweb_config.h" 7 8 #include "app.hpp" 9 #include "async_resp.hpp" 10 #include "dbus_singleton.hpp" 11 #include "dbus_utility.hpp" 12 #include "error_messages.hpp" 13 #include "generated/enums/resource.hpp" 14 #include "generated/enums/update_service.hpp" 15 #include "http_request.hpp" 16 #include "http_response.hpp" 17 #include "io_context_singleton.hpp" 18 #include "logging.hpp" 19 #include "multipart_parser.hpp" 20 #include "ossl_random.hpp" 21 #include "query.hpp" 22 #include "registries/privilege_registry.hpp" 23 #include "str_utility.hpp" 24 #include "task.hpp" 25 #include "task_messages.hpp" 26 #include "utility.hpp" 27 #include "utils/collection.hpp" 28 #include "utils/dbus_utils.hpp" 29 #include "utils/json_utils.hpp" 30 #include "utils/sw_utils.hpp" 31 32 #include <sys/mman.h> 33 #include <unistd.h> 34 35 #include <boost/asio/error.hpp> 36 #include <boost/asio/steady_timer.hpp> 37 #include <boost/beast/http/fields.hpp> 38 #include <boost/beast/http/status.hpp> 39 #include <boost/beast/http/verb.hpp> 40 #include <boost/system/error_code.hpp> 41 #include <boost/system/result.hpp> 42 #include <boost/url/format.hpp> 43 #include <boost/url/parse.hpp> 44 #include <boost/url/url.hpp> 45 #include <boost/url/url_view.hpp> 46 #include <boost/url/url_view_base.hpp> 47 #include <sdbusplus/asio/property.hpp> 48 #include <sdbusplus/bus/match.hpp> 49 #include <sdbusplus/message.hpp> 50 #include <sdbusplus/message/native_types.hpp> 51 #include <sdbusplus/unpack_properties.hpp> 52 53 #include <array> 54 #include <chrono> 55 #include <cstddef> 56 #include <cstdint> 57 #include <cstdio> 58 #include <filesystem> 59 #include <format> 60 #include <fstream> 61 #include <functional> 62 #include <memory> 63 #include <optional> 64 #include <string> 65 #include <string_view> 66 #include <unordered_map> 67 #include <utility> 68 #include <variant> 69 #include <vector> 70 71 namespace redfish 72 { 73 74 // Match signals added on software path 75 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 76 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher; 77 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 78 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher; 79 // Only allow one update at a time 80 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 81 static bool fwUpdateInProgress = false; 82 // Timer for software available 83 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 84 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; 85 86 struct MemoryFileDescriptor 87 { 88 int fd = -1; 89 90 explicit MemoryFileDescriptor(const std::string& filename) : 91 fd(memfd_create(filename.c_str(), 0)) 92 {} 93 94 MemoryFileDescriptor(const MemoryFileDescriptor&) = default; 95 MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd) 96 { 97 other.fd = -1; 98 } 99 MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete; 100 MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default; 101 102 ~MemoryFileDescriptor() 103 { 104 if (fd != -1) 105 { 106 close(fd); 107 } 108 } 109 110 bool rewind() const 111 { 112 if (lseek(fd, 0, SEEK_SET) == -1) 113 { 114 BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd"); 115 return false; 116 } 117 return true; 118 } 119 }; 120 121 inline void cleanUp() 122 { 123 fwUpdateInProgress = false; 124 fwUpdateMatcher = nullptr; 125 fwUpdateErrorMatcher = nullptr; 126 } 127 128 inline void activateImage(const std::string& objPath, 129 const std::string& service) 130 { 131 BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service); 132 sdbusplus::asio::setProperty( 133 *crow::connections::systemBus, service, objPath, 134 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 135 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active", 136 [](const boost::system::error_code& ec) { 137 if (ec) 138 { 139 BMCWEB_LOG_DEBUG("error_code = {}", ec); 140 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 141 } 142 }); 143 } 144 145 inline bool handleCreateTask(const boost::system::error_code& ec2, 146 sdbusplus::message_t& msg, 147 const std::shared_ptr<task::TaskData>& taskData) 148 { 149 if (ec2) 150 { 151 return task::completed; 152 } 153 154 std::string iface; 155 dbus::utility::DBusPropertiesMap values; 156 157 std::string index = std::to_string(taskData->index); 158 msg.read(iface, values); 159 160 if (iface == "xyz.openbmc_project.Software.Activation") 161 { 162 const std::string* state = nullptr; 163 for (const auto& property : values) 164 { 165 if (property.first == "Activation") 166 { 167 state = std::get_if<std::string>(&property.second); 168 if (state == nullptr) 169 { 170 taskData->messages.emplace_back(messages::internalError()); 171 return task::completed; 172 } 173 } 174 } 175 176 if (state == nullptr) 177 { 178 return !task::completed; 179 } 180 181 if (state->ends_with("Invalid") || state->ends_with("Failed")) 182 { 183 taskData->state = "Exception"; 184 taskData->status = "Warning"; 185 taskData->messages.emplace_back(messages::taskAborted(index)); 186 return task::completed; 187 } 188 189 if (state->ends_with("Staged")) 190 { 191 taskData->state = "Stopping"; 192 taskData->messages.emplace_back(messages::taskPaused(index)); 193 194 // its staged, set a long timer to 195 // allow them time to complete the 196 // update (probably cycle the 197 // system) if this expires then 198 // task will be canceled 199 taskData->extendTimer(std::chrono::hours(5)); 200 return !task::completed; 201 } 202 203 if (state->ends_with("Active")) 204 { 205 taskData->messages.emplace_back(messages::taskCompletedOK(index)); 206 taskData->state = "Completed"; 207 return task::completed; 208 } 209 } 210 else if (iface == "xyz.openbmc_project.Software.ActivationProgress") 211 { 212 const uint8_t* progress = nullptr; 213 for (const auto& property : values) 214 { 215 if (property.first == "Progress") 216 { 217 progress = std::get_if<uint8_t>(&property.second); 218 if (progress == nullptr) 219 { 220 taskData->messages.emplace_back(messages::internalError()); 221 return task::completed; 222 } 223 } 224 } 225 226 if (progress == nullptr) 227 { 228 return !task::completed; 229 } 230 taskData->percentComplete = *progress; 231 taskData->messages.emplace_back( 232 messages::taskProgressChanged(index, *progress)); 233 234 // if we're getting status updates it's 235 // still alive, update timer 236 taskData->extendTimer(std::chrono::minutes(5)); 237 } 238 239 // as firmware update often results in a 240 // reboot, the task may never "complete" 241 // unless it is an error 242 243 return !task::completed; 244 } 245 246 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 247 task::Payload&& payload, 248 const sdbusplus::message::object_path& objPath) 249 { 250 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 251 std::bind_front(handleCreateTask), 252 "type='signal',interface='org.freedesktop.DBus.Properties'," 253 "member='PropertiesChanged',path='" + 254 objPath.str + "'"); 255 task->startTimer(std::chrono::minutes(5)); 256 task->payload.emplace(std::move(payload)); 257 task->populateResp(asyncResp->res); 258 } 259 260 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 261 // then no asyncResp updates will occur 262 inline void softwareInterfaceAdded( 263 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 264 sdbusplus::message_t& m, task::Payload&& payload) 265 { 266 dbus::utility::DBusInterfacesMap interfacesProperties; 267 268 sdbusplus::message::object_path objPath; 269 270 m.read(objPath, interfacesProperties); 271 272 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 273 for (const auto& interface : interfacesProperties) 274 { 275 BMCWEB_LOG_DEBUG("interface = {}", interface.first); 276 277 if (interface.first == "xyz.openbmc_project.Software.Activation") 278 { 279 // Retrieve service and activate 280 constexpr std::array<std::string_view, 1> interfaces = { 281 "xyz.openbmc_project.Software.Activation"}; 282 dbus::utility::getDbusObject( 283 objPath.str, interfaces, 284 [objPath, asyncResp, payload(std::move(payload))]( 285 const boost::system::error_code& ec, 286 const std::vector< 287 std::pair<std::string, std::vector<std::string>>>& 288 objInfo) mutable { 289 if (ec) 290 { 291 BMCWEB_LOG_DEBUG("error_code = {}", ec); 292 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 293 if (asyncResp) 294 { 295 messages::internalError(asyncResp->res); 296 } 297 cleanUp(); 298 return; 299 } 300 // Ensure we only got one service back 301 if (objInfo.size() != 1) 302 { 303 BMCWEB_LOG_ERROR("Invalid Object Size {}", 304 objInfo.size()); 305 if (asyncResp) 306 { 307 messages::internalError(asyncResp->res); 308 } 309 cleanUp(); 310 return; 311 } 312 // cancel timer only when 313 // xyz.openbmc_project.Software.Activation interface 314 // is added 315 fwAvailableTimer = nullptr; 316 317 activateImage(objPath.str, objInfo[0].first); 318 if (asyncResp) 319 { 320 createTask(asyncResp, std::move(payload), objPath); 321 } 322 fwUpdateInProgress = false; 323 }); 324 325 break; 326 } 327 } 328 } 329 330 inline void afterAvailbleTimerAsyncWait( 331 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 332 const boost::system::error_code& ec) 333 { 334 cleanUp(); 335 if (ec == boost::asio::error::operation_aborted) 336 { 337 // expected, we were canceled before the timer completed. 338 return; 339 } 340 BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created"); 341 BMCWEB_LOG_ERROR("FW image may has already been uploaded to server"); 342 if (ec) 343 { 344 BMCWEB_LOG_ERROR("Async_wait failed{}", ec); 345 return; 346 } 347 if (asyncResp) 348 { 349 redfish::messages::internalError(asyncResp->res); 350 } 351 } 352 353 inline void handleUpdateErrorType( 354 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url, 355 const std::string& type) 356 { 357 // NOLINTBEGIN(bugprone-branch-clone) 358 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 359 { 360 messages::missingOrMalformedPart(asyncResp->res); 361 } 362 else if (type == 363 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") 364 { 365 messages::missingOrMalformedPart(asyncResp->res); 366 } 367 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") 368 { 369 messages::missingOrMalformedPart(asyncResp->res); 370 } 371 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 372 { 373 messages::resourceAlreadyExists(asyncResp->res, "UpdateService", 374 "Version", "uploaded version"); 375 } 376 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") 377 { 378 messages::serviceTemporarilyUnavailable(asyncResp->res, url); 379 } 380 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") 381 { 382 messages::internalError(asyncResp->res); 383 } 384 else if (type == 385 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") 386 { 387 messages::internalError(asyncResp->res); 388 } 389 else if (type == 390 "xyz.openbmc_project.Software.Version.Error.InvalidSignature") 391 { 392 messages::missingOrMalformedPart(asyncResp->res); 393 } 394 else if (type == 395 "xyz.openbmc_project.Software.Image.Error.InternalFailure" || 396 type == "xyz.openbmc_project.Software.Version.Error.HostFile") 397 { 398 BMCWEB_LOG_ERROR("Software Image Error type={}", type); 399 messages::internalError(asyncResp->res); 400 } 401 else 402 { 403 // Unrelated error types. Ignored 404 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type); 405 return; 406 } 407 // NOLINTEND(bugprone-branch-clone) 408 // Clear the timer 409 fwAvailableTimer = nullptr; 410 } 411 412 inline void afterUpdateErrorMatcher( 413 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& url, 414 sdbusplus::message_t& m) 415 { 416 dbus::utility::DBusInterfacesMap interfacesProperties; 417 sdbusplus::message::object_path objPath; 418 m.read(objPath, interfacesProperties); 419 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 420 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 421 interface : interfacesProperties) 422 { 423 if (interface.first == "xyz.openbmc_project.Logging.Entry") 424 { 425 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 426 value : interface.second) 427 { 428 if (value.first != "Message") 429 { 430 continue; 431 } 432 const std::string* type = 433 std::get_if<std::string>(&value.second); 434 if (type == nullptr) 435 { 436 // if this was our message, timeout will cover it 437 return; 438 } 439 handleUpdateErrorType(asyncResp, url, *type); 440 } 441 } 442 } 443 } 444 445 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 446 // then no asyncResp updates will occur 447 inline void monitorForSoftwareAvailable( 448 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 449 const crow::Request& req, const std::string& url, 450 int timeoutTimeSeconds = 50) 451 { 452 // Only allow one FW update at a time 453 if (fwUpdateInProgress) 454 { 455 if (asyncResp) 456 { 457 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 458 } 459 return; 460 } 461 462 fwAvailableTimer = 463 std::make_unique<boost::asio::steady_timer>(getIoContext()); 464 465 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 466 467 fwAvailableTimer->async_wait( 468 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); 469 470 task::Payload payload(req); 471 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 472 BMCWEB_LOG_DEBUG("Match fired"); 473 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 474 }; 475 476 fwUpdateInProgress = true; 477 478 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 479 *crow::connections::systemBus, 480 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 481 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 482 callback); 483 484 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 485 *crow::connections::systemBus, 486 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 487 "member='InterfacesAdded'," 488 "path='/xyz/openbmc_project/logging'", 489 std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); 490 } 491 492 inline std::optional<boost::urls::url> parseSimpleUpdateUrl( 493 std::string imageURI, std::optional<std::string> transferProtocol, 494 crow::Response& res) 495 { 496 if (imageURI.find("://") == std::string::npos) 497 { 498 if (imageURI.starts_with("/")) 499 { 500 messages::actionParameterValueTypeError( 501 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 502 return std::nullopt; 503 } 504 if (!transferProtocol) 505 { 506 messages::actionParameterValueTypeError( 507 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 508 return std::nullopt; 509 } 510 // OpenBMC currently only supports HTTPS 511 if (*transferProtocol == "HTTPS") 512 { 513 imageURI = "https://" + imageURI; 514 } 515 else 516 { 517 messages::actionParameterNotSupported(res, "TransferProtocol", 518 *transferProtocol); 519 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}", 520 *transferProtocol); 521 return std::nullopt; 522 } 523 } 524 525 boost::system::result<boost::urls::url> url = 526 boost::urls::parse_absolute_uri(imageURI); 527 if (!url) 528 { 529 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 530 "UpdateService.SimpleUpdate"); 531 532 return std::nullopt; 533 } 534 url->normalize(); 535 536 if (url->scheme() == "tftp") 537 { 538 if (url->encoded_path().size() < 2) 539 { 540 messages::actionParameterNotSupported(res, "ImageURI", 541 url->buffer()); 542 return std::nullopt; 543 } 544 } 545 else if (url->scheme() == "https") 546 { 547 // Empty paths default to "/" 548 if (url->encoded_path().empty()) 549 { 550 url->set_encoded_path("/"); 551 } 552 } 553 else 554 { 555 messages::actionParameterNotSupported(res, "ImageURI", imageURI); 556 return std::nullopt; 557 } 558 559 if (url->encoded_path().empty()) 560 { 561 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 562 "UpdateService.SimpleUpdate"); 563 return std::nullopt; 564 } 565 566 return *url; 567 } 568 569 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 570 const boost::urls::url_view_base& url) 571 { 572 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 573 url.buffer()); 574 } 575 576 inline void handleUpdateServiceSimpleUpdateAction( 577 crow::App& app, const crow::Request& req, 578 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 579 { 580 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 581 { 582 return; 583 } 584 585 std::optional<std::string> transferProtocol; 586 std::string imageURI; 587 588 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 589 590 // User can pass in both TransferProtocol and ImageURI parameters or 591 // they can pass in just the ImageURI with the transfer protocol 592 // embedded within it. 593 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 594 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 595 596 if (!json_util::readJsonAction( // 597 req, asyncResp->res, // 598 "ImageURI", imageURI, // 599 "TransferProtocol", transferProtocol // 600 )) 601 { 602 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); 603 return; 604 } 605 606 std::optional<boost::urls::url> url = 607 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res); 608 if (!url) 609 { 610 return; 611 } 612 if (url->scheme() == "https") 613 { 614 doHttpsUpdate(asyncResp, *url); 615 } 616 else 617 { 618 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 619 url->buffer()); 620 return; 621 } 622 623 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); 624 } 625 626 inline void uploadImageFile(crow::Response& res, std::string_view body) 627 { 628 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); 629 630 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); 631 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 632 std::ofstream::trunc); 633 // set the permission of the file to 640 634 std::filesystem::perms permission = 635 std::filesystem::perms::owner_read | std::filesystem::perms::group_read; 636 std::filesystem::permissions(filepath, permission); 637 out << body; 638 639 if (out.bad()) 640 { 641 messages::internalError(res); 642 cleanUp(); 643 } 644 } 645 646 // Convert the Request Apply Time to the D-Bus value 647 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime, 648 std::string& applyTimeNewVal) 649 { 650 if (applyTime == "Immediate") 651 { 652 applyTimeNewVal = 653 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 654 } 655 else if (applyTime == "OnReset") 656 { 657 applyTimeNewVal = 658 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 659 } 660 else 661 { 662 BMCWEB_LOG_WARNING( 663 "ApplyTime value {} is not in the list of acceptable values", 664 applyTime); 665 messages::propertyValueNotInList(res, applyTime, "ApplyTime"); 666 return false; 667 } 668 return true; 669 } 670 671 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 672 const std::string& applyTime) 673 { 674 std::string applyTimeNewVal; 675 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal)) 676 { 677 return; 678 } 679 680 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings", 681 sdbusplus::message::object_path( 682 "/xyz/openbmc_project/software/apply_time"), 683 "xyz.openbmc_project.Software.ApplyTime", 684 "RequestedApplyTime", applyTimeNewVal); 685 } 686 687 struct MultiPartUpdate 688 { 689 std::string uploadData; 690 struct UpdateParameters 691 { 692 std::optional<std::string> applyTime; 693 std::vector<std::string> targets; 694 } params; 695 }; 696 697 inline std::optional<std::string> processUrl( 698 boost::system::result<boost::urls::url_view>& url) 699 { 700 if (!url) 701 { 702 return std::nullopt; 703 } 704 if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers", 705 BMCWEB_REDFISH_MANAGER_URI_NAME)) 706 { 707 return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME)); 708 } 709 if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 710 { 711 return std::nullopt; 712 } 713 std::string firmwareId; 714 if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService", 715 "FirmwareInventory", 716 std::ref(firmwareId))) 717 { 718 return std::nullopt; 719 } 720 721 return std::make_optional(firmwareId); 722 } 723 724 inline std::optional<std::string> parseFormPartName( 725 const boost::beast::http::fields::const_iterator& contentDisposition) 726 { 727 size_t semicolonPos = contentDisposition->value().find(';'); 728 if (semicolonPos == std::string::npos) 729 { 730 return std::nullopt; 731 } 732 733 for (const auto& param : boost::beast::http::param_list{ 734 contentDisposition->value().substr(semicolonPos)}) 735 { 736 if (param.first == "name" && !param.second.empty()) 737 { 738 return std::string(param.second); 739 } 740 } 741 return std::nullopt; 742 } 743 744 inline std::optional<MultiPartUpdate::UpdateParameters> processUpdateParameters( 745 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 746 std::string_view content) 747 { 748 MultiPartUpdate::UpdateParameters multiRet; 749 nlohmann::json jsonContent = nlohmann::json::parse(content, nullptr, false); 750 if (jsonContent.is_discarded()) 751 { 752 return std::nullopt; 753 } 754 nlohmann::json::object_t* obj = 755 jsonContent.get_ptr<nlohmann::json::object_t*>(); 756 if (obj == nullptr) 757 { 758 messages::propertyValueTypeError(asyncResp->res, content, 759 "UpdateParameters"); 760 return std::nullopt; 761 } 762 763 std::vector<std::string> tempTargets; 764 if (!json_util::readJsonObject( // 765 *obj, asyncResp->res, // 766 "@Redfish.OperationApplyTime", multiRet.applyTime, // 767 "Targets", tempTargets // 768 )) 769 { 770 return std::nullopt; 771 } 772 773 for (size_t urlIndex = 0; urlIndex < tempTargets.size(); urlIndex++) 774 { 775 const std::string& target = tempTargets[urlIndex]; 776 boost::system::result<boost::urls::url_view> url = 777 boost::urls::parse_origin_form(target); 778 auto res = processUrl(url); 779 if (!res.has_value()) 780 { 781 messages::propertyValueFormatError( 782 asyncResp->res, target, std::format("Targets/{}", urlIndex)); 783 return std::nullopt; 784 } 785 multiRet.targets.emplace_back(res.value()); 786 } 787 if (multiRet.targets.size() != 1) 788 { 789 messages::propertyValueFormatError(asyncResp->res, multiRet.targets, 790 "Targets"); 791 return std::nullopt; 792 } 793 return multiRet; 794 } 795 796 inline std::optional<MultiPartUpdate> extractMultipartUpdateParameters( 797 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, MultipartParser parser) 798 { 799 MultiPartUpdate multiRet; 800 for (FormPart& formpart : parser.mime_fields) 801 { 802 boost::beast::http::fields::const_iterator it = 803 formpart.fields.find("Content-Disposition"); 804 if (it == formpart.fields.end()) 805 { 806 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); 807 return std::nullopt; 808 } 809 BMCWEB_LOG_INFO("Parsing value {}", it->value()); 810 811 auto formFieldNameOpt = parseFormPartName(it); 812 if (!formFieldNameOpt.has_value()) 813 { 814 continue; 815 } 816 817 const std::string& formFieldName = formFieldNameOpt.value(); 818 819 if (formFieldName == "UpdateParameters") 820 { 821 std::optional<MultiPartUpdate::UpdateParameters> params = 822 processUpdateParameters(asyncResp, formpart.content); 823 if (!params) 824 { 825 return std::nullopt; 826 } 827 multiRet.params = std::move(*params); 828 } 829 else if (formFieldName == "UpdateFile") 830 { 831 multiRet.uploadData = std::move(formpart.content); 832 } 833 } 834 835 if (multiRet.uploadData.empty()) 836 { 837 BMCWEB_LOG_ERROR("Upload data is NULL"); 838 messages::propertyMissing(asyncResp->res, "UpdateFile"); 839 return std::nullopt; 840 } 841 if (multiRet.params.targets.empty()) 842 { 843 messages::propertyMissing(asyncResp->res, "Targets"); 844 return std::nullopt; 845 } 846 return multiRet; 847 } 848 849 inline void handleStartUpdate( 850 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 851 const std::string& objectPath, const boost::system::error_code& ec, 852 const sdbusplus::message::object_path& retPath) 853 { 854 if (ec) 855 { 856 BMCWEB_LOG_ERROR("error_code = {}", ec); 857 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 858 messages::internalError(asyncResp->res); 859 return; 860 } 861 862 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}", 863 objectPath, retPath.str); 864 createTask(asyncResp, std::move(payload), retPath); 865 } 866 867 inline void startUpdate( 868 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 869 const MemoryFileDescriptor& memfd, const std::string& applyTime, 870 const std::string& objectPath, const std::string& serviceName) 871 { 872 dbus::utility::async_method_call( 873 asyncResp, 874 [asyncResp, payload = std::move(payload), 875 objectPath](const boost::system::error_code& ec1, 876 const sdbusplus::message::object_path& retPath) mutable { 877 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1, 878 retPath); 879 }, 880 serviceName, objectPath, "xyz.openbmc_project.Software.Update", 881 "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime); 882 } 883 884 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 885 task::Payload payload, const MemoryFileDescriptor& memfd, 886 const std::string& applyTime, const std::string& target, 887 const boost::system::error_code& ec, 888 const dbus::utility::MapperGetSubTreeResponse& subtree) 889 { 890 using SwInfoMap = std::unordered_map< 891 std::string, std::pair<sdbusplus::message::object_path, std::string>>; 892 SwInfoMap swInfoMap; 893 894 if (ec) 895 { 896 BMCWEB_LOG_ERROR("error_code = {}", ec); 897 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 898 messages::internalError(asyncResp->res); 899 return; 900 } 901 BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size()); 902 903 for (const auto& entry : subtree) 904 { 905 sdbusplus::message::object_path path(entry.first); 906 std::string swId = path.filename(); 907 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first)); 908 } 909 910 auto swEntry = swInfoMap.find(target); 911 if (swEntry == swInfoMap.end()) 912 { 913 BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target); 914 messages::propertyValueFormatError(asyncResp->res, target, "Targets"); 915 return; 916 } 917 918 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}", 919 swEntry->second.first.str, swEntry->second.second); 920 921 startUpdate(asyncResp, std::move(payload), memfd, applyTime, 922 swEntry->second.first.str, swEntry->second.second); 923 } 924 925 inline void handleBMCUpdate( 926 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 927 const MemoryFileDescriptor& memfd, const std::string& applyTime, 928 const boost::system::error_code& ec, 929 const dbus::utility::MapperEndPoints& functionalSoftware) 930 { 931 if (ec) 932 { 933 BMCWEB_LOG_ERROR("error_code = {}", ec); 934 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 935 messages::internalError(asyncResp->res); 936 return; 937 } 938 if (functionalSoftware.size() != 1) 939 { 940 BMCWEB_LOG_ERROR("Found {} functional software endpoints", 941 functionalSoftware.size()); 942 messages::internalError(asyncResp->res); 943 return; 944 } 945 946 startUpdate(asyncResp, std::move(payload), memfd, applyTime, 947 functionalSoftware[0], "xyz.openbmc_project.Software.Manager"); 948 } 949 950 inline void processUpdateRequest( 951 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 952 task::Payload&& payload, std::string_view body, 953 const std::string& applyTime, std::vector<std::string>& targets) 954 { 955 MemoryFileDescriptor memfd("update-image"); 956 if (memfd.fd == -1) 957 { 958 BMCWEB_LOG_ERROR("Failed to create image memfd"); 959 messages::internalError(asyncResp->res); 960 return; 961 } 962 if (write(memfd.fd, body.data(), body.length()) != 963 static_cast<ssize_t>(body.length())) 964 { 965 BMCWEB_LOG_ERROR("Failed to write to image memfd"); 966 messages::internalError(asyncResp->res); 967 return; 968 } 969 if (!memfd.rewind()) 970 { 971 messages::internalError(asyncResp->res); 972 return; 973 } 974 975 if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME) 976 { 977 dbus::utility::getAssociationEndPoints( 978 "/xyz/openbmc_project/software/bmc/updateable", 979 [asyncResp, payload = std::move(payload), memfd = std::move(memfd), 980 applyTime]( 981 const boost::system::error_code& ec, 982 const dbus::utility::MapperEndPoints& objectPaths) mutable { 983 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime, 984 ec, objectPaths); 985 }); 986 } 987 else 988 { 989 constexpr std::array<std::string_view, 1> interfaces = { 990 "xyz.openbmc_project.Software.Version"}; 991 dbus::utility::getSubTree( 992 "/xyz/openbmc_project/software", 1, interfaces, 993 [asyncResp, payload = std::move(payload), memfd = std::move(memfd), 994 applyTime, targets](const boost::system::error_code& ec, 995 const dbus::utility::MapperGetSubTreeResponse& 996 subtree) mutable { 997 getSwInfo(asyncResp, std::move(payload), memfd, applyTime, 998 targets[0], ec, subtree); 999 }); 1000 } 1001 } 1002 1003 inline void updateMultipartContext( 1004 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1005 const crow::Request& req, MultipartParser&& parser) 1006 { 1007 std::optional<MultiPartUpdate> multipart = 1008 extractMultipartUpdateParameters(asyncResp, std::move(parser)); 1009 if (!multipart) 1010 { 1011 return; 1012 } 1013 if (!multipart->params.applyTime) 1014 { 1015 multipart->params.applyTime = "OnReset"; 1016 } 1017 1018 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 1019 { 1020 std::string applyTimeNewVal; 1021 if (!convertApplyTime(asyncResp->res, *multipart->params.applyTime, 1022 applyTimeNewVal)) 1023 { 1024 return; 1025 } 1026 task::Payload payload(req); 1027 1028 processUpdateRequest(asyncResp, std::move(payload), 1029 multipart->uploadData, applyTimeNewVal, 1030 multipart->params.targets); 1031 } 1032 else 1033 { 1034 setApplyTime(asyncResp, *multipart->params.applyTime); 1035 1036 // Setup callback for when new software detected 1037 monitorForSoftwareAvailable(asyncResp, req, 1038 "/redfish/v1/UpdateService"); 1039 1040 uploadImageFile(asyncResp->res, multipart->uploadData); 1041 } 1042 } 1043 1044 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1045 const crow::Request& req) 1046 { 1047 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 1048 { 1049 task::Payload payload(req); 1050 // HTTP push only supports BMC updates (with ApplyTime as immediate) for 1051 // backwards compatibility. Specific component updates will be handled 1052 // through Multipart form HTTP push. 1053 std::vector<std::string> targets; 1054 targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME); 1055 1056 processUpdateRequest( 1057 asyncResp, std::move(payload), req.body(), 1058 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate", 1059 targets); 1060 } 1061 else 1062 { 1063 // Setup callback for when new software detected 1064 monitorForSoftwareAvailable(asyncResp, req, 1065 "/redfish/v1/UpdateService"); 1066 1067 uploadImageFile(asyncResp->res, req.body()); 1068 } 1069 } 1070 1071 inline void handleUpdateServicePost( 1072 App& app, const crow::Request& req, 1073 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1074 { 1075 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1076 { 1077 return; 1078 } 1079 std::string_view contentType = req.getHeaderValue("Content-Type"); 1080 1081 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); 1082 1083 // Make sure that content type is application/octet-stream 1084 if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) 1085 { 1086 doHTTPUpdate(asyncResp, req); 1087 } 1088 else 1089 { 1090 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 1091 asyncResp->res.result(boost::beast::http::status::bad_request); 1092 } 1093 } 1094 1095 inline void handleUpdateServiceMultipartUpdatePost( 1096 App& app, const crow::Request& req, 1097 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1098 { 1099 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1100 { 1101 return; 1102 } 1103 1104 std::string_view contentType = req.getHeaderValue("Content-Type"); 1105 // Make sure that content type is multipart/form-data 1106 if (contentType.starts_with("multipart/form-data")) 1107 { 1108 MultipartParser parser; 1109 1110 ParserError ec = parser.parse(req); 1111 if (ec != ParserError::PARSER_SUCCESS) 1112 { 1113 // handle error 1114 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", 1115 static_cast<int>(ec)); 1116 messages::internalError(asyncResp->res); 1117 return; 1118 } 1119 1120 updateMultipartContext(asyncResp, req, std::move(parser)); 1121 } 1122 else 1123 { 1124 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 1125 asyncResp->res.result(boost::beast::http::status::bad_request); 1126 } 1127 } 1128 1129 inline void handleUpdateServiceGet( 1130 App& app, const crow::Request& req, 1131 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1132 { 1133 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1134 { 1135 return; 1136 } 1137 asyncResp->res.jsonValue["@odata.type"] = 1138 "#UpdateService.v1_11_1.UpdateService"; 1139 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 1140 asyncResp->res.jsonValue["Id"] = "UpdateService"; 1141 asyncResp->res.jsonValue["Description"] = "Service for Software Update"; 1142 asyncResp->res.jsonValue["Name"] = "Update Service"; 1143 1144 asyncResp->res.jsonValue["HttpPushUri"] = 1145 "/redfish/v1/UpdateService/update"; 1146 asyncResp->res.jsonValue["MultipartHttpPushUri"] = 1147 "/redfish/v1/UpdateService/update-multipart"; 1148 1149 // UpdateService cannot be disabled 1150 asyncResp->res.jsonValue["ServiceEnabled"] = true; 1151 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 1152 "/redfish/v1/UpdateService/FirmwareInventory"; 1153 // Get the MaxImageSizeBytes 1154 asyncResp->res.jsonValue["MaxImageSizeBytes"] = 1155 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024; 1156 1157 if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE) 1158 { 1159 // Update Actions object. 1160 nlohmann::json& updateSvcSimpleUpdate = 1161 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 1162 updateSvcSimpleUpdate["target"] = 1163 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 1164 1165 nlohmann::json::array_t allowed; 1166 allowed.emplace_back(update_service::TransferProtocolType::HTTPS); 1167 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = 1168 std::move(allowed); 1169 } 1170 1171 asyncResp->res 1172 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] = 1173 update_service::ApplyTime::Immediate; 1174 } 1175 1176 inline void handleUpdateServiceFirmwareInventoryCollectionGet( 1177 App& app, const crow::Request& req, 1178 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1179 { 1180 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1181 { 1182 return; 1183 } 1184 asyncResp->res.jsonValue["@odata.type"] = 1185 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 1186 asyncResp->res.jsonValue["@odata.id"] = 1187 "/redfish/v1/UpdateService/FirmwareInventory"; 1188 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; 1189 const std::array<const std::string_view, 1> iface = { 1190 "xyz.openbmc_project.Software.Version"}; 1191 1192 redfish::collection_util::getCollectionMembers( 1193 asyncResp, 1194 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface, 1195 "/xyz/openbmc_project/software"); 1196 } 1197 1198 /* Fill related item links (i.e. bmc, bios) in for inventory */ 1199 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1200 const std::string& purpose) 1201 { 1202 if (purpose == sw_util::bmcPurpose) 1203 { 1204 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1205 nlohmann::json::object_t item; 1206 item["@odata.id"] = boost::urls::format( 1207 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME); 1208 relatedItem.emplace_back(std::move(item)); 1209 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1210 relatedItem.size(); 1211 } 1212 else if (purpose == sw_util::biosPurpose) 1213 { 1214 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1215 nlohmann::json::object_t item; 1216 item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios", 1217 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1218 relatedItem.emplace_back(std::move(item)); 1219 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1220 relatedItem.size(); 1221 } 1222 else 1223 { 1224 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); 1225 } 1226 } 1227 1228 inline void getSoftwareVersion( 1229 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1230 const std::string& service, const std::string& path, 1231 const std::string& swId) 1232 { 1233 dbus::utility::getAllProperties( 1234 service, path, "xyz.openbmc_project.Software.Version", 1235 [asyncResp, 1236 swId](const boost::system::error_code& ec, 1237 const dbus::utility::DBusPropertiesMap& propertiesList) { 1238 if (ec) 1239 { 1240 messages::internalError(asyncResp->res); 1241 return; 1242 } 1243 1244 const std::string* swInvPurpose = nullptr; 1245 const std::string* version = nullptr; 1246 1247 const bool success = sdbusplus::unpackPropertiesNoThrow( 1248 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 1249 swInvPurpose, "Version", version); 1250 1251 if (!success) 1252 { 1253 messages::internalError(asyncResp->res); 1254 return; 1255 } 1256 1257 if (swInvPurpose == nullptr) 1258 { 1259 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!"); 1260 messages::internalError(asyncResp->res); 1261 return; 1262 } 1263 1264 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose); 1265 1266 if (version == nullptr) 1267 { 1268 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!"); 1269 1270 messages::internalError(asyncResp->res); 1271 1272 return; 1273 } 1274 asyncResp->res.jsonValue["Version"] = *version; 1275 asyncResp->res.jsonValue["Id"] = swId; 1276 1277 // swInvPurpose is of format: 1278 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 1279 // Translate this to "ABC image" 1280 size_t endDesc = swInvPurpose->rfind('.'); 1281 if (endDesc == std::string::npos) 1282 { 1283 messages::internalError(asyncResp->res); 1284 return; 1285 } 1286 endDesc++; 1287 if (endDesc >= swInvPurpose->size()) 1288 { 1289 messages::internalError(asyncResp->res); 1290 return; 1291 } 1292 1293 std::string formatDesc = swInvPurpose->substr(endDesc); 1294 asyncResp->res.jsonValue["Description"] = formatDesc + " image"; 1295 getRelatedItems(asyncResp, *swInvPurpose); 1296 }); 1297 } 1298 1299 inline void handleUpdateServiceFirmwareInventoryGetCallback( 1300 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1301 const std::shared_ptr<std::string>& swId, 1302 const boost::system::error_code& ec, 1303 const dbus::utility::MapperGetSubTreeResponse& subtree) 1304 { 1305 BMCWEB_LOG_DEBUG("doGet callback..."); 1306 if (ec) 1307 { 1308 messages::internalError(asyncResp->res); 1309 return; 1310 } 1311 // Ensure we find our input swId, otherwise return an error 1312 bool found = false; 1313 for (const std::pair< 1314 std::string, 1315 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1316 obj : subtree) 1317 { 1318 sdbusplus::message::object_path path(obj.first); 1319 std::string id = path.filename(); 1320 if (id.empty()) 1321 { 1322 BMCWEB_LOG_DEBUG("Failed to find software id in {}", obj.first); 1323 continue; 1324 } 1325 if (id != *swId) 1326 { 1327 continue; 1328 } 1329 if (obj.second.empty()) 1330 { 1331 continue; 1332 } 1333 found = true; 1334 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first); 1335 sw_util::getSwMinimumVersion(asyncResp, swId, obj.second[0].first); 1336 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, *swId); 1337 } 1338 if (!found) 1339 { 1340 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId); 1341 messages::resourceMissingAtURI( 1342 asyncResp->res, 1343 boost::urls::format( 1344 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId)); 1345 return; 1346 } 1347 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1348 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); 1349 asyncResp->res.jsonValue["@odata.type"] = 1350 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 1351 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 1352 asyncResp->res.jsonValue["Status"]["HealthRollup"] = resource::Health::OK; 1353 asyncResp->res.jsonValue["Updateable"] = false; 1354 sw_util::getSwUpdatableStatus(asyncResp, swId); 1355 } 1356 1357 inline void handleUpdateServiceFirmwareInventoryGet( 1358 App& app, const crow::Request& req, 1359 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1360 const std::string& param) 1361 { 1362 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1363 { 1364 return; 1365 } 1366 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param); 1367 1368 constexpr std::array<std::string_view, 1> interfaces = { 1369 "xyz.openbmc_project.Software.Version"}; 1370 dbus::utility::getSubTree( 1371 "/xyz/openbmc_project/software/", 0, interfaces, 1372 std::bind_front(handleUpdateServiceFirmwareInventoryGetCallback, 1373 asyncResp, swId)); 1374 } 1375 1376 inline void requestRoutesUpdateService(App& app) 1377 { 1378 if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE) 1379 { 1380 BMCWEB_ROUTE( 1381 app, 1382 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 1383 .privileges(redfish::privileges::postUpdateService) 1384 .methods(boost::beast::http::verb::post)(std::bind_front( 1385 handleUpdateServiceSimpleUpdateAction, std::ref(app))); 1386 } 1387 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 1388 .privileges(redfish::privileges::getSoftwareInventory) 1389 .methods(boost::beast::http::verb::get)(std::bind_front( 1390 handleUpdateServiceFirmwareInventoryGet, std::ref(app))); 1391 1392 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1393 .privileges(redfish::privileges::getUpdateService) 1394 .methods(boost::beast::http::verb::get)( 1395 std::bind_front(handleUpdateServiceGet, std::ref(app))); 1396 1397 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 1398 .privileges(redfish::privileges::postUpdateService) 1399 .methods(boost::beast::http::verb::post)( 1400 std::bind_front(handleUpdateServicePost, std::ref(app))); 1401 1402 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update-multipart/") 1403 .privileges(redfish::privileges::postUpdateService) 1404 .methods(boost::beast::http::verb::post)(std::bind_front( 1405 handleUpdateServiceMultipartUpdatePost, std::ref(app))); 1406 1407 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 1408 .privileges(redfish::privileges::getSoftwareInventoryCollection) 1409 .methods(boost::beast::http::verb::get)(std::bind_front( 1410 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app))); 1411 } 1412 1413 } // namespace redfish 1414