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