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