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 inline 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 {}", 288 objInfo.size()); 289 if (asyncResp) 290 { 291 messages::internalError(asyncResp->res); 292 } 293 cleanUp(); 294 return; 295 } 296 // cancel timer only when 297 // xyz.openbmc_project.Software.Activation interface 298 // is added 299 fwAvailableTimer = nullptr; 300 301 activateImage(objPath.str, objInfo[0].first); 302 if (asyncResp) 303 { 304 createTask(asyncResp, std::move(payload), objPath); 305 } 306 fwUpdateInProgress = false; 307 }); 308 309 break; 310 } 311 } 312 } 313 314 inline void afterAvailbleTimerAsyncWait( 315 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 316 const boost::system::error_code& ec) 317 { 318 cleanUp(); 319 if (ec == boost::asio::error::operation_aborted) 320 { 321 // expected, we were canceled before the timer completed. 322 return; 323 } 324 BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created"); 325 BMCWEB_LOG_ERROR("FW image may has already been uploaded to server"); 326 if (ec) 327 { 328 BMCWEB_LOG_ERROR("Async_wait failed{}", ec); 329 return; 330 } 331 if (asyncResp) 332 { 333 redfish::messages::internalError(asyncResp->res); 334 } 335 } 336 337 inline void 338 handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 339 const std::string& url, const std::string& type) 340 { 341 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 342 { 343 redfish::messages::invalidUpload(asyncResp->res, url, 344 "Invalid archive"); 345 } 346 else if (type == 347 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") 348 { 349 redfish::messages::invalidUpload(asyncResp->res, url, 350 "Invalid manifest"); 351 } 352 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") 353 { 354 redfish::messages::invalidUpload(asyncResp->res, url, 355 "Invalid image format"); 356 } 357 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 358 { 359 redfish::messages::invalidUpload(asyncResp->res, url, 360 "Image version already exists"); 361 362 redfish::messages::resourceAlreadyExists( 363 asyncResp->res, "UpdateService", "Version", "uploaded version"); 364 } 365 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") 366 { 367 redfish::messages::resourceExhaustion(asyncResp->res, url); 368 } 369 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") 370 { 371 redfish::messages::invalidUpload(asyncResp->res, url, 372 "Incompatible image version"); 373 } 374 else if (type == 375 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") 376 { 377 redfish::messages::invalidUpload(asyncResp->res, url, 378 "Update Access Key Expired"); 379 } 380 else if (type == 381 "xyz.openbmc_project.Software.Version.Error.InvalidSignature") 382 { 383 redfish::messages::invalidUpload(asyncResp->res, url, 384 "Invalid image signature"); 385 } 386 else if (type == 387 "xyz.openbmc_project.Software.Image.Error.InternalFailure" || 388 type == "xyz.openbmc_project.Software.Version.Error.HostFile") 389 { 390 BMCWEB_LOG_ERROR("Software Image Error type={}", type); 391 redfish::messages::internalError(asyncResp->res); 392 } 393 else 394 { 395 // Unrelated error types. Ignored 396 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type); 397 return; 398 } 399 // Clear the timer 400 fwAvailableTimer = nullptr; 401 } 402 403 inline void 404 afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 405 const std::string& url, sdbusplus::message_t& m) 406 { 407 dbus::utility::DBusInterfacesMap interfacesProperties; 408 sdbusplus::message::object_path objPath; 409 m.read(objPath, interfacesProperties); 410 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 411 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 412 interface : interfacesProperties) 413 { 414 if (interface.first == "xyz.openbmc_project.Logging.Entry") 415 { 416 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 417 value : interface.second) 418 { 419 if (value.first != "Message") 420 { 421 continue; 422 } 423 const std::string* type = 424 std::get_if<std::string>(&value.second); 425 if (type == nullptr) 426 { 427 // if this was our message, timeout will cover it 428 return; 429 } 430 handleUpdateErrorType(asyncResp, url, *type); 431 } 432 } 433 } 434 } 435 436 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 437 // then no asyncResp updates will occur 438 inline void monitorForSoftwareAvailable( 439 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 440 const crow::Request& req, const std::string& url, 441 int timeoutTimeSeconds = 25) 442 { 443 // Only allow one FW update at a time 444 if (fwUpdateInProgress) 445 { 446 if (asyncResp) 447 { 448 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 449 } 450 return; 451 } 452 453 if (req.ioService == nullptr) 454 { 455 messages::internalError(asyncResp->res); 456 return; 457 } 458 459 fwAvailableTimer = 460 std::make_unique<boost::asio::steady_timer>(*req.ioService); 461 462 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 463 464 fwAvailableTimer->async_wait( 465 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); 466 467 task::Payload payload(req); 468 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 469 BMCWEB_LOG_DEBUG("Match fired"); 470 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 471 }; 472 473 fwUpdateInProgress = true; 474 475 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 476 *crow::connections::systemBus, 477 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 478 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 479 callback); 480 481 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 482 *crow::connections::systemBus, 483 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 484 "member='InterfacesAdded'," 485 "path='/xyz/openbmc_project/logging'", 486 std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); 487 } 488 489 inline std::optional<boost::urls::url> parseSimpleUpdateUrl( 490 std::string imageURI, 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 handleUpdateServiceSimpleUpdateAction( 578 crow::App& app, const crow::Request& req, 579 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 580 { 581 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 582 { 583 return; 584 } 585 586 std::optional<std::string> transferProtocol; 587 std::string imageURI; 588 589 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 590 591 // User can pass in both TransferProtocol and ImageURI parameters or 592 // they can pass in just the ImageURI with the transfer protocol 593 // embedded within it. 594 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 595 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 596 597 if (!json_util::readJsonAction( // 598 req, asyncResp->res, // 599 "ImageURI", imageURI, // 600 "TransferProtocol", transferProtocol // 601 )) 602 { 603 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); 604 return; 605 } 606 607 std::optional<boost::urls::url> url = 608 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res); 609 if (!url) 610 { 611 return; 612 } 613 if (url->scheme() == "https") 614 { 615 doHttpsUpdate(asyncResp, *url); 616 } 617 else 618 { 619 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 620 url->buffer()); 621 return; 622 } 623 624 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); 625 } 626 627 inline void uploadImageFile(crow::Response& res, std::string_view body) 628 { 629 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); 630 631 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); 632 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 633 std::ofstream::trunc); 634 // set the permission of the file to 640 635 std::filesystem::perms permission = 636 std::filesystem::perms::owner_read | std::filesystem::perms::group_read; 637 std::filesystem::permissions(filepath, permission); 638 out << body; 639 640 if (out.bad()) 641 { 642 messages::internalError(res); 643 cleanUp(); 644 } 645 } 646 647 // Convert the Request Apply Time to the D-Bus value 648 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime, 649 std::string& applyTimeNewVal) 650 { 651 if (applyTime == "Immediate") 652 { 653 applyTimeNewVal = 654 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 655 } 656 else if (applyTime == "OnReset") 657 { 658 applyTimeNewVal = 659 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 660 } 661 else 662 { 663 BMCWEB_LOG_WARNING( 664 "ApplyTime value {} is not in the list of acceptable values", 665 applyTime); 666 messages::propertyValueNotInList(res, applyTime, "ApplyTime"); 667 return false; 668 } 669 return true; 670 } 671 672 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 673 const std::string& applyTime) 674 { 675 std::string applyTimeNewVal; 676 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal)) 677 { 678 return; 679 } 680 681 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings", 682 sdbusplus::message::object_path( 683 "/xyz/openbmc_project/software/apply_time"), 684 "xyz.openbmc_project.Software.ApplyTime", 685 "RequestedApplyTime", applyTimeNewVal); 686 } 687 688 struct MultiPartUpdateParameters 689 { 690 std::optional<std::string> applyTime; 691 std::string uploadData; 692 std::vector<std::string> targets; 693 }; 694 695 inline std::optional<std::string> 696 processUrl(boost::system::result<boost::urls::url_view>& url) 697 { 698 if (!url) 699 { 700 return std::nullopt; 701 } 702 if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers", 703 BMCWEB_REDFISH_MANAGER_URI_NAME)) 704 { 705 return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME)); 706 } 707 if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 708 { 709 return std::nullopt; 710 } 711 std::string firmwareId; 712 if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService", 713 "FirmwareInventory", 714 std::ref(firmwareId))) 715 { 716 return std::nullopt; 717 } 718 719 return std::make_optional(firmwareId); 720 } 721 722 inline std::optional<MultiPartUpdateParameters> 723 extractMultipartUpdateParameters( 724 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 725 MultipartParser parser) 726 { 727 MultiPartUpdateParameters multiRet; 728 for (FormPart& formpart : parser.mime_fields) 729 { 730 boost::beast::http::fields::const_iterator it = 731 formpart.fields.find("Content-Disposition"); 732 if (it == formpart.fields.end()) 733 { 734 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); 735 return std::nullopt; 736 } 737 BMCWEB_LOG_INFO("Parsing value {}", it->value()); 738 739 // The construction parameters of param_list must start with `;` 740 size_t index = it->value().find(';'); 741 if (index == std::string::npos) 742 { 743 continue; 744 } 745 746 for (const auto& param : 747 boost::beast::http::param_list{it->value().substr(index)}) 748 { 749 if (param.first != "name" || param.second.empty()) 750 { 751 continue; 752 } 753 754 if (param.second == "UpdateParameters") 755 { 756 std::vector<std::string> tempTargets; 757 nlohmann::json content = 758 nlohmann::json::parse(formpart.content, nullptr, false); 759 if (content.is_discarded()) 760 { 761 return std::nullopt; 762 } 763 nlohmann::json::object_t* obj = 764 content.get_ptr<nlohmann::json::object_t*>(); 765 if (obj == nullptr) 766 { 767 messages::propertyValueTypeError( 768 asyncResp->res, formpart.content, "UpdateParameters"); 769 return std::nullopt; 770 } 771 772 if (!json_util::readJsonObject( // 773 *obj, asyncResp->res, // 774 "@Redfish.OperationApplyTime", multiRet.applyTime, // 775 "Targets", tempTargets // 776 )) 777 { 778 return std::nullopt; 779 } 780 781 for (size_t urlIndex = 0; urlIndex < tempTargets.size(); 782 urlIndex++) 783 { 784 const std::string& target = tempTargets[urlIndex]; 785 boost::system::result<boost::urls::url_view> url = 786 boost::urls::parse_origin_form(target); 787 auto res = processUrl(url); 788 if (!res.has_value()) 789 { 790 messages::propertyValueFormatError( 791 asyncResp->res, target, 792 std::format("Targets/{}", urlIndex)); 793 return std::nullopt; 794 } 795 multiRet.targets.emplace_back(res.value()); 796 } 797 if (multiRet.targets.size() != 1) 798 { 799 messages::propertyValueFormatError( 800 asyncResp->res, multiRet.targets, "Targets"); 801 return std::nullopt; 802 } 803 } 804 else if (param.second == "UpdateFile") 805 { 806 multiRet.uploadData = std::move(formpart.content); 807 } 808 } 809 } 810 811 if (multiRet.uploadData.empty()) 812 { 813 BMCWEB_LOG_ERROR("Upload data is NULL"); 814 messages::propertyMissing(asyncResp->res, "UpdateFile"); 815 return std::nullopt; 816 } 817 if (multiRet.targets.empty()) 818 { 819 messages::propertyMissing(asyncResp->res, "Targets"); 820 return std::nullopt; 821 } 822 return multiRet; 823 } 824 825 inline void handleStartUpdate( 826 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 827 const std::string& objectPath, const boost::system::error_code& ec, 828 const sdbusplus::message::object_path& retPath) 829 { 830 if (ec) 831 { 832 BMCWEB_LOG_ERROR("error_code = {}", ec); 833 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 834 messages::internalError(asyncResp->res); 835 return; 836 } 837 838 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}", 839 objectPath, retPath.str); 840 createTask(asyncResp, std::move(payload), retPath); 841 } 842 843 inline void startUpdate( 844 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 845 const MemoryFileDescriptor& memfd, const std::string& applyTime, 846 const std::string& objectPath, const std::string& serviceName) 847 { 848 crow::connections::systemBus->async_method_call( 849 [asyncResp, payload = std::move(payload), 850 objectPath](const boost::system::error_code& ec1, 851 const sdbusplus::message::object_path& retPath) mutable { 852 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1, 853 retPath); 854 }, 855 serviceName, objectPath, "xyz.openbmc_project.Software.Update", 856 "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime); 857 } 858 859 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 860 task::Payload payload, const MemoryFileDescriptor& memfd, 861 const std::string& applyTime, const std::string& target, 862 const boost::system::error_code& ec, 863 const dbus::utility::MapperGetSubTreeResponse& subtree) 864 { 865 using SwInfoMap = std::unordered_map< 866 std::string, std::pair<sdbusplus::message::object_path, std::string>>; 867 SwInfoMap swInfoMap; 868 869 if (ec) 870 { 871 BMCWEB_LOG_ERROR("error_code = {}", ec); 872 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 873 messages::internalError(asyncResp->res); 874 return; 875 } 876 BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size()); 877 878 for (const auto& entry : subtree) 879 { 880 sdbusplus::message::object_path path(entry.first); 881 std::string swId = path.filename(); 882 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first)); 883 } 884 885 auto swEntry = swInfoMap.find(target); 886 if (swEntry == swInfoMap.end()) 887 { 888 BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target); 889 messages::propertyValueFormatError(asyncResp->res, target, "Targets"); 890 return; 891 } 892 893 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}", 894 swEntry->second.first.str, swEntry->second.second); 895 896 startUpdate(asyncResp, std::move(payload), memfd, applyTime, 897 swEntry->second.first.str, swEntry->second.second); 898 } 899 900 inline void handleBMCUpdate( 901 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 902 const MemoryFileDescriptor& memfd, const std::string& applyTime, 903 const boost::system::error_code& ec, 904 const dbus::utility::MapperEndPoints& functionalSoftware) 905 { 906 if (ec) 907 { 908 BMCWEB_LOG_ERROR("error_code = {}", ec); 909 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 910 messages::internalError(asyncResp->res); 911 return; 912 } 913 if (functionalSoftware.size() != 1) 914 { 915 BMCWEB_LOG_ERROR("Found {} functional software endpoints", 916 functionalSoftware.size()); 917 messages::internalError(asyncResp->res); 918 return; 919 } 920 921 startUpdate(asyncResp, std::move(payload), memfd, applyTime, 922 functionalSoftware[0], "xyz.openbmc_project.Software.Manager"); 923 } 924 925 inline void processUpdateRequest( 926 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 927 task::Payload&& payload, std::string_view body, 928 const std::string& applyTime, std::vector<std::string>& targets) 929 { 930 MemoryFileDescriptor memfd("update-image"); 931 if (memfd.fd == -1) 932 { 933 BMCWEB_LOG_ERROR("Failed to create image memfd"); 934 messages::internalError(asyncResp->res); 935 return; 936 } 937 if (write(memfd.fd, body.data(), body.length()) != 938 static_cast<ssize_t>(body.length())) 939 { 940 BMCWEB_LOG_ERROR("Failed to write to image memfd"); 941 messages::internalError(asyncResp->res); 942 return; 943 } 944 if (!memfd.rewind()) 945 { 946 messages::internalError(asyncResp->res); 947 return; 948 } 949 950 if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME) 951 { 952 dbus::utility::getAssociationEndPoints( 953 "/xyz/openbmc_project/software/bmc/updateable", 954 [asyncResp, payload = std::move(payload), memfd = std::move(memfd), 955 applyTime]( 956 const boost::system::error_code& ec, 957 const dbus::utility::MapperEndPoints& objectPaths) mutable { 958 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime, 959 ec, objectPaths); 960 }); 961 } 962 else 963 { 964 constexpr std::array<std::string_view, 1> interfaces = { 965 "xyz.openbmc_project.Software.Version"}; 966 dbus::utility::getSubTree( 967 "/xyz/openbmc_project/software", 1, interfaces, 968 [asyncResp, payload = std::move(payload), memfd = std::move(memfd), 969 applyTime, targets](const boost::system::error_code& ec, 970 const dbus::utility::MapperGetSubTreeResponse& 971 subtree) mutable { 972 getSwInfo(asyncResp, std::move(payload), memfd, applyTime, 973 targets[0], ec, subtree); 974 }); 975 } 976 } 977 978 inline void 979 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 980 const crow::Request& req, MultipartParser&& parser) 981 { 982 std::optional<MultiPartUpdateParameters> multipart = 983 extractMultipartUpdateParameters(asyncResp, std::move(parser)); 984 if (!multipart) 985 { 986 return; 987 } 988 if (!multipart->applyTime) 989 { 990 multipart->applyTime = "OnReset"; 991 } 992 993 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 994 { 995 std::string applyTimeNewVal; 996 if (!convertApplyTime(asyncResp->res, *multipart->applyTime, 997 applyTimeNewVal)) 998 { 999 return; 1000 } 1001 task::Payload payload(req); 1002 1003 processUpdateRequest(asyncResp, std::move(payload), 1004 multipart->uploadData, applyTimeNewVal, 1005 multipart->targets); 1006 } 1007 else 1008 { 1009 setApplyTime(asyncResp, *multipart->applyTime); 1010 1011 // Setup callback for when new software detected 1012 monitorForSoftwareAvailable(asyncResp, req, 1013 "/redfish/v1/UpdateService"); 1014 1015 uploadImageFile(asyncResp->res, multipart->uploadData); 1016 } 1017 } 1018 1019 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1020 const crow::Request& req) 1021 { 1022 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 1023 { 1024 task::Payload payload(req); 1025 // HTTP push only supports BMC updates (with ApplyTime as immediate) for 1026 // backwards compatibility. Specific component updates will be handled 1027 // through Multipart form HTTP push. 1028 std::vector<std::string> targets; 1029 targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME); 1030 1031 processUpdateRequest( 1032 asyncResp, std::move(payload), req.body(), 1033 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate", 1034 targets); 1035 } 1036 else 1037 { 1038 // Setup callback for when new software detected 1039 monitorForSoftwareAvailable(asyncResp, req, 1040 "/redfish/v1/UpdateService"); 1041 1042 uploadImageFile(asyncResp->res, req.body()); 1043 } 1044 } 1045 1046 inline void 1047 handleUpdateServicePost(App& app, const crow::Request& req, 1048 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1049 { 1050 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1051 { 1052 return; 1053 } 1054 std::string_view contentType = req.getHeaderValue("Content-Type"); 1055 1056 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); 1057 1058 // Make sure that content type is application/octet-stream or 1059 // multipart/form-data 1060 if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) 1061 { 1062 doHTTPUpdate(asyncResp, req); 1063 } 1064 else if (contentType.starts_with("multipart/form-data")) 1065 { 1066 MultipartParser parser; 1067 1068 ParserError ec = parser.parse(req); 1069 if (ec != ParserError::PARSER_SUCCESS) 1070 { 1071 // handle error 1072 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", 1073 static_cast<int>(ec)); 1074 messages::internalError(asyncResp->res); 1075 return; 1076 } 1077 1078 updateMultipartContext(asyncResp, req, std::move(parser)); 1079 } 1080 else 1081 { 1082 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 1083 asyncResp->res.result(boost::beast::http::status::bad_request); 1084 } 1085 } 1086 1087 inline void 1088 handleUpdateServiceGet(App& app, const crow::Request& req, 1089 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1090 { 1091 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1092 { 1093 return; 1094 } 1095 asyncResp->res.jsonValue["@odata.type"] = 1096 "#UpdateService.v1_11_1.UpdateService"; 1097 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 1098 asyncResp->res.jsonValue["Id"] = "UpdateService"; 1099 asyncResp->res.jsonValue["Description"] = "Service for Software Update"; 1100 asyncResp->res.jsonValue["Name"] = "Update Service"; 1101 1102 asyncResp->res.jsonValue["HttpPushUri"] = 1103 "/redfish/v1/UpdateService/update"; 1104 asyncResp->res.jsonValue["MultipartHttpPushUri"] = 1105 "/redfish/v1/UpdateService/update"; 1106 1107 // UpdateService cannot be disabled 1108 asyncResp->res.jsonValue["ServiceEnabled"] = true; 1109 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 1110 "/redfish/v1/UpdateService/FirmwareInventory"; 1111 // Get the MaxImageSizeBytes 1112 asyncResp->res.jsonValue["MaxImageSizeBytes"] = 1113 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024; 1114 1115 // Update Actions object. 1116 nlohmann::json& updateSvcSimpleUpdate = 1117 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 1118 updateSvcSimpleUpdate["target"] = 1119 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 1120 1121 nlohmann::json::array_t allowed; 1122 allowed.emplace_back(update_service::TransferProtocolType::HTTPS); 1123 1124 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION) 1125 { 1126 allowed.emplace_back(update_service::TransferProtocolType::TFTP); 1127 } 1128 1129 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = 1130 std::move(allowed); 1131 1132 asyncResp->res 1133 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] = 1134 update_service::ApplyTime::Immediate; 1135 } 1136 1137 inline void handleUpdateServiceFirmwareInventoryCollectionGet( 1138 App& app, const crow::Request& req, 1139 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1140 { 1141 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1142 { 1143 return; 1144 } 1145 asyncResp->res.jsonValue["@odata.type"] = 1146 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 1147 asyncResp->res.jsonValue["@odata.id"] = 1148 "/redfish/v1/UpdateService/FirmwareInventory"; 1149 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; 1150 const std::array<const std::string_view, 1> iface = { 1151 "xyz.openbmc_project.Software.Version"}; 1152 1153 redfish::collection_util::getCollectionMembers( 1154 asyncResp, 1155 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface, 1156 "/xyz/openbmc_project/software"); 1157 } 1158 1159 /* Fill related item links (i.e. bmc, bios) in for inventory */ 1160 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1161 const std::string& purpose) 1162 { 1163 if (purpose == sw_util::bmcPurpose) 1164 { 1165 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1166 nlohmann::json::object_t item; 1167 item["@odata.id"] = boost::urls::format( 1168 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME); 1169 relatedItem.emplace_back(std::move(item)); 1170 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1171 relatedItem.size(); 1172 } 1173 else if (purpose == sw_util::biosPurpose) 1174 { 1175 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1176 nlohmann::json::object_t item; 1177 item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios", 1178 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1179 relatedItem.emplace_back(std::move(item)); 1180 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1181 relatedItem.size(); 1182 } 1183 else 1184 { 1185 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); 1186 } 1187 } 1188 1189 inline void 1190 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1191 const std::string& service, const std::string& path, 1192 const std::string& swId) 1193 { 1194 sdbusplus::asio::getAllProperties( 1195 *crow::connections::systemBus, service, path, 1196 "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 BMCWEB_ROUTE( 1335 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 1336 .privileges(redfish::privileges::postUpdateService) 1337 .methods(boost::beast::http::verb::post)(std::bind_front( 1338 handleUpdateServiceSimpleUpdateAction, std::ref(app))); 1339 1340 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 1341 .privileges(redfish::privileges::getSoftwareInventory) 1342 .methods(boost::beast::http::verb::get)(std::bind_front( 1343 handleUpdateServiceFirmwareInventoryGet, std::ref(app))); 1344 1345 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1346 .privileges(redfish::privileges::getUpdateService) 1347 .methods(boost::beast::http::verb::get)( 1348 std::bind_front(handleUpdateServiceGet, std::ref(app))); 1349 1350 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 1351 .privileges(redfish::privileges::postUpdateService) 1352 .methods(boost::beast::http::verb::post)( 1353 std::bind_front(handleUpdateServicePost, std::ref(app))); 1354 1355 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 1356 .privileges(redfish::privileges::getSoftwareInventoryCollection) 1357 .methods(boost::beast::http::verb::get)(std::bind_front( 1358 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app))); 1359 } 1360 1361 } // namespace redfish 1362