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