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 // NOLINTBEGIN(bugprone-branch-clone) 342 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 343 { 344 messages::missingOrMalformedPart(asyncResp->res); 345 } 346 else if (type == 347 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") 348 { 349 messages::missingOrMalformedPart(asyncResp->res); 350 } 351 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") 352 { 353 messages::missingOrMalformedPart(asyncResp->res); 354 } 355 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 356 { 357 messages::resourceAlreadyExists(asyncResp->res, "UpdateService", 358 "Version", "uploaded version"); 359 } 360 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") 361 { 362 messages::serviceTemporarilyUnavailable(asyncResp->res, url); 363 } 364 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") 365 { 366 messages::internalError(asyncResp->res); 367 } 368 else if (type == 369 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") 370 { 371 messages::internalError(asyncResp->res); 372 } 373 else if (type == 374 "xyz.openbmc_project.Software.Version.Error.InvalidSignature") 375 { 376 messages::missingOrMalformedPart(asyncResp->res); 377 } 378 else if (type == 379 "xyz.openbmc_project.Software.Image.Error.InternalFailure" || 380 type == "xyz.openbmc_project.Software.Version.Error.HostFile") 381 { 382 BMCWEB_LOG_ERROR("Software Image Error type={}", type); 383 messages::internalError(asyncResp->res); 384 } 385 else 386 { 387 // Unrelated error types. Ignored 388 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type); 389 return; 390 } 391 // NOLINTEND(bugprone-branch-clone) 392 // Clear the timer 393 fwAvailableTimer = nullptr; 394 } 395 396 inline void 397 afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 398 const std::string& url, sdbusplus::message_t& m) 399 { 400 dbus::utility::DBusInterfacesMap interfacesProperties; 401 sdbusplus::message::object_path objPath; 402 m.read(objPath, interfacesProperties); 403 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 404 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 405 interface : interfacesProperties) 406 { 407 if (interface.first == "xyz.openbmc_project.Logging.Entry") 408 { 409 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 410 value : interface.second) 411 { 412 if (value.first != "Message") 413 { 414 continue; 415 } 416 const std::string* type = 417 std::get_if<std::string>(&value.second); 418 if (type == nullptr) 419 { 420 // if this was our message, timeout will cover it 421 return; 422 } 423 handleUpdateErrorType(asyncResp, url, *type); 424 } 425 } 426 } 427 } 428 429 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 430 // then no asyncResp updates will occur 431 inline void monitorForSoftwareAvailable( 432 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 433 const crow::Request& req, const std::string& url, 434 int timeoutTimeSeconds = 25) 435 { 436 // Only allow one FW update at a time 437 if (fwUpdateInProgress) 438 { 439 if (asyncResp) 440 { 441 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 442 } 443 return; 444 } 445 446 if (req.ioService == nullptr) 447 { 448 messages::internalError(asyncResp->res); 449 return; 450 } 451 452 fwAvailableTimer = 453 std::make_unique<boost::asio::steady_timer>(*req.ioService); 454 455 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 456 457 fwAvailableTimer->async_wait( 458 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); 459 460 task::Payload payload(req); 461 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 462 BMCWEB_LOG_DEBUG("Match fired"); 463 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 464 }; 465 466 fwUpdateInProgress = true; 467 468 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 469 *crow::connections::systemBus, 470 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 471 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 472 callback); 473 474 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 475 *crow::connections::systemBus, 476 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 477 "member='InterfacesAdded'," 478 "path='/xyz/openbmc_project/logging'", 479 std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); 480 } 481 482 inline std::optional<boost::urls::url> parseSimpleUpdateUrl( 483 std::string imageURI, std::optional<std::string> transferProtocol, 484 crow::Response& res) 485 { 486 if (imageURI.find("://") == std::string::npos) 487 { 488 if (imageURI.starts_with("/")) 489 { 490 messages::actionParameterValueTypeError( 491 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 492 return std::nullopt; 493 } 494 if (!transferProtocol) 495 { 496 messages::actionParameterValueTypeError( 497 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 498 return std::nullopt; 499 } 500 // OpenBMC currently only supports HTTPS 501 if (*transferProtocol == "HTTPS") 502 { 503 imageURI = "https://" + imageURI; 504 } 505 else 506 { 507 messages::actionParameterNotSupported(res, "TransferProtocol", 508 *transferProtocol); 509 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}", 510 *transferProtocol); 511 return std::nullopt; 512 } 513 } 514 515 boost::system::result<boost::urls::url> url = 516 boost::urls::parse_absolute_uri(imageURI); 517 if (!url) 518 { 519 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 520 "UpdateService.SimpleUpdate"); 521 522 return std::nullopt; 523 } 524 url->normalize(); 525 526 if (url->scheme() == "tftp") 527 { 528 if (url->encoded_path().size() < 2) 529 { 530 messages::actionParameterNotSupported(res, "ImageURI", 531 url->buffer()); 532 return std::nullopt; 533 } 534 } 535 else if (url->scheme() == "https") 536 { 537 // Empty paths default to "/" 538 if (url->encoded_path().empty()) 539 { 540 url->set_encoded_path("/"); 541 } 542 } 543 else 544 { 545 messages::actionParameterNotSupported(res, "ImageURI", imageURI); 546 return std::nullopt; 547 } 548 549 if (url->encoded_path().empty()) 550 { 551 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 552 "UpdateService.SimpleUpdate"); 553 return std::nullopt; 554 } 555 556 return *url; 557 } 558 559 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 560 const boost::urls::url_view_base& url) 561 { 562 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 563 url.buffer()); 564 } 565 566 inline void handleUpdateServiceSimpleUpdateAction( 567 crow::App& app, const crow::Request& req, 568 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 569 { 570 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 571 { 572 return; 573 } 574 575 std::optional<std::string> transferProtocol; 576 std::string imageURI; 577 578 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 579 580 // User can pass in both TransferProtocol and ImageURI parameters or 581 // they can pass in just the ImageURI with the transfer protocol 582 // embedded within it. 583 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 584 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 585 586 if (!json_util::readJsonAction( // 587 req, asyncResp->res, // 588 "ImageURI", imageURI, // 589 "TransferProtocol", transferProtocol // 590 )) 591 { 592 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); 593 return; 594 } 595 596 std::optional<boost::urls::url> url = 597 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res); 598 if (!url) 599 { 600 return; 601 } 602 if (url->scheme() == "https") 603 { 604 doHttpsUpdate(asyncResp, *url); 605 } 606 else 607 { 608 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 609 url->buffer()); 610 return; 611 } 612 613 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); 614 } 615 616 inline void uploadImageFile(crow::Response& res, std::string_view body) 617 { 618 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); 619 620 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); 621 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 622 std::ofstream::trunc); 623 // set the permission of the file to 640 624 std::filesystem::perms permission = 625 std::filesystem::perms::owner_read | std::filesystem::perms::group_read; 626 std::filesystem::permissions(filepath, permission); 627 out << body; 628 629 if (out.bad()) 630 { 631 messages::internalError(res); 632 cleanUp(); 633 } 634 } 635 636 // Convert the Request Apply Time to the D-Bus value 637 inline bool convertApplyTime(crow::Response& res, const std::string& applyTime, 638 std::string& applyTimeNewVal) 639 { 640 if (applyTime == "Immediate") 641 { 642 applyTimeNewVal = 643 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 644 } 645 else if (applyTime == "OnReset") 646 { 647 applyTimeNewVal = 648 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 649 } 650 else 651 { 652 BMCWEB_LOG_WARNING( 653 "ApplyTime value {} is not in the list of acceptable values", 654 applyTime); 655 messages::propertyValueNotInList(res, applyTime, "ApplyTime"); 656 return false; 657 } 658 return true; 659 } 660 661 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 662 const std::string& applyTime) 663 { 664 std::string applyTimeNewVal; 665 if (!convertApplyTime(asyncResp->res, applyTime, applyTimeNewVal)) 666 { 667 return; 668 } 669 670 setDbusProperty(asyncResp, "ApplyTime", "xyz.openbmc_project.Settings", 671 sdbusplus::message::object_path( 672 "/xyz/openbmc_project/software/apply_time"), 673 "xyz.openbmc_project.Software.ApplyTime", 674 "RequestedApplyTime", applyTimeNewVal); 675 } 676 677 struct MultiPartUpdateParameters 678 { 679 std::optional<std::string> applyTime; 680 std::string uploadData; 681 std::vector<std::string> targets; 682 }; 683 684 inline std::optional<std::string> 685 processUrl(boost::system::result<boost::urls::url_view>& url) 686 { 687 if (!url) 688 { 689 return std::nullopt; 690 } 691 if (crow::utility::readUrlSegments(*url, "redfish", "v1", "Managers", 692 BMCWEB_REDFISH_MANAGER_URI_NAME)) 693 { 694 return std::make_optional(std::string(BMCWEB_REDFISH_MANAGER_URI_NAME)); 695 } 696 if constexpr (!BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 697 { 698 return std::nullopt; 699 } 700 std::string firmwareId; 701 if (!crow::utility::readUrlSegments(*url, "redfish", "v1", "UpdateService", 702 "FirmwareInventory", 703 std::ref(firmwareId))) 704 { 705 return std::nullopt; 706 } 707 708 return std::make_optional(firmwareId); 709 } 710 711 inline std::optional<MultiPartUpdateParameters> 712 extractMultipartUpdateParameters( 713 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 714 MultipartParser parser) 715 { 716 MultiPartUpdateParameters multiRet; 717 for (FormPart& formpart : parser.mime_fields) 718 { 719 boost::beast::http::fields::const_iterator it = 720 formpart.fields.find("Content-Disposition"); 721 if (it == formpart.fields.end()) 722 { 723 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); 724 return std::nullopt; 725 } 726 BMCWEB_LOG_INFO("Parsing value {}", it->value()); 727 728 // The construction parameters of param_list must start with `;` 729 size_t index = it->value().find(';'); 730 if (index == std::string::npos) 731 { 732 continue; 733 } 734 735 for (const auto& param : 736 boost::beast::http::param_list{it->value().substr(index)}) 737 { 738 if (param.first != "name" || param.second.empty()) 739 { 740 continue; 741 } 742 743 if (param.second == "UpdateParameters") 744 { 745 std::vector<std::string> tempTargets; 746 nlohmann::json content = 747 nlohmann::json::parse(formpart.content, nullptr, false); 748 if (content.is_discarded()) 749 { 750 return std::nullopt; 751 } 752 nlohmann::json::object_t* obj = 753 content.get_ptr<nlohmann::json::object_t*>(); 754 if (obj == nullptr) 755 { 756 messages::propertyValueTypeError( 757 asyncResp->res, formpart.content, "UpdateParameters"); 758 return std::nullopt; 759 } 760 761 if (!json_util::readJsonObject( // 762 *obj, asyncResp->res, // 763 "@Redfish.OperationApplyTime", multiRet.applyTime, // 764 "Targets", tempTargets // 765 )) 766 { 767 return std::nullopt; 768 } 769 770 for (size_t urlIndex = 0; urlIndex < tempTargets.size(); 771 urlIndex++) 772 { 773 const std::string& target = tempTargets[urlIndex]; 774 boost::system::result<boost::urls::url_view> url = 775 boost::urls::parse_origin_form(target); 776 auto res = processUrl(url); 777 if (!res.has_value()) 778 { 779 messages::propertyValueFormatError( 780 asyncResp->res, target, 781 std::format("Targets/{}", urlIndex)); 782 return std::nullopt; 783 } 784 multiRet.targets.emplace_back(res.value()); 785 } 786 if (multiRet.targets.size() != 1) 787 { 788 messages::propertyValueFormatError( 789 asyncResp->res, multiRet.targets, "Targets"); 790 return std::nullopt; 791 } 792 } 793 else if (param.second == "UpdateFile") 794 { 795 multiRet.uploadData = std::move(formpart.content); 796 } 797 } 798 } 799 800 if (multiRet.uploadData.empty()) 801 { 802 BMCWEB_LOG_ERROR("Upload data is NULL"); 803 messages::propertyMissing(asyncResp->res, "UpdateFile"); 804 return std::nullopt; 805 } 806 if (multiRet.targets.empty()) 807 { 808 messages::propertyMissing(asyncResp->res, "Targets"); 809 return std::nullopt; 810 } 811 return multiRet; 812 } 813 814 inline void handleStartUpdate( 815 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 816 const std::string& objectPath, const boost::system::error_code& ec, 817 const sdbusplus::message::object_path& retPath) 818 { 819 if (ec) 820 { 821 BMCWEB_LOG_ERROR("error_code = {}", ec); 822 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 823 messages::internalError(asyncResp->res); 824 return; 825 } 826 827 BMCWEB_LOG_INFO("Call to StartUpdate on {} Success, retPath = {}", 828 objectPath, retPath.str); 829 createTask(asyncResp, std::move(payload), retPath); 830 } 831 832 inline void startUpdate( 833 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 834 const MemoryFileDescriptor& memfd, const std::string& applyTime, 835 const std::string& objectPath, const std::string& serviceName) 836 { 837 crow::connections::systemBus->async_method_call( 838 [asyncResp, payload = std::move(payload), 839 objectPath](const boost::system::error_code& ec1, 840 const sdbusplus::message::object_path& retPath) mutable { 841 handleStartUpdate(asyncResp, std::move(payload), objectPath, ec1, 842 retPath); 843 }, 844 serviceName, objectPath, "xyz.openbmc_project.Software.Update", 845 "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime); 846 } 847 848 inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 849 task::Payload payload, const MemoryFileDescriptor& memfd, 850 const std::string& applyTime, const std::string& target, 851 const boost::system::error_code& ec, 852 const dbus::utility::MapperGetSubTreeResponse& subtree) 853 { 854 using SwInfoMap = std::unordered_map< 855 std::string, std::pair<sdbusplus::message::object_path, std::string>>; 856 SwInfoMap swInfoMap; 857 858 if (ec) 859 { 860 BMCWEB_LOG_ERROR("error_code = {}", ec); 861 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 862 messages::internalError(asyncResp->res); 863 return; 864 } 865 BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size()); 866 867 for (const auto& entry : subtree) 868 { 869 sdbusplus::message::object_path path(entry.first); 870 std::string swId = path.filename(); 871 swInfoMap.emplace(swId, make_pair(path, entry.second[0].first)); 872 } 873 874 auto swEntry = swInfoMap.find(target); 875 if (swEntry == swInfoMap.end()) 876 { 877 BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target); 878 messages::propertyValueFormatError(asyncResp->res, target, "Targets"); 879 return; 880 } 881 882 BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}", 883 swEntry->second.first.str, swEntry->second.second); 884 885 startUpdate(asyncResp, std::move(payload), memfd, applyTime, 886 swEntry->second.first.str, swEntry->second.second); 887 } 888 889 inline void handleBMCUpdate( 890 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, 891 const MemoryFileDescriptor& memfd, const std::string& applyTime, 892 const boost::system::error_code& ec, 893 const dbus::utility::MapperEndPoints& functionalSoftware) 894 { 895 if (ec) 896 { 897 BMCWEB_LOG_ERROR("error_code = {}", ec); 898 BMCWEB_LOG_ERROR("error msg = {}", ec.message()); 899 messages::internalError(asyncResp->res); 900 return; 901 } 902 if (functionalSoftware.size() != 1) 903 { 904 BMCWEB_LOG_ERROR("Found {} functional software endpoints", 905 functionalSoftware.size()); 906 messages::internalError(asyncResp->res); 907 return; 908 } 909 910 startUpdate(asyncResp, std::move(payload), memfd, applyTime, 911 functionalSoftware[0], "xyz.openbmc_project.Software.Manager"); 912 } 913 914 inline void processUpdateRequest( 915 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 916 task::Payload&& payload, std::string_view body, 917 const std::string& applyTime, std::vector<std::string>& targets) 918 { 919 MemoryFileDescriptor memfd("update-image"); 920 if (memfd.fd == -1) 921 { 922 BMCWEB_LOG_ERROR("Failed to create image memfd"); 923 messages::internalError(asyncResp->res); 924 return; 925 } 926 if (write(memfd.fd, body.data(), body.length()) != 927 static_cast<ssize_t>(body.length())) 928 { 929 BMCWEB_LOG_ERROR("Failed to write to image memfd"); 930 messages::internalError(asyncResp->res); 931 return; 932 } 933 if (!memfd.rewind()) 934 { 935 messages::internalError(asyncResp->res); 936 return; 937 } 938 939 if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME) 940 { 941 dbus::utility::getAssociationEndPoints( 942 "/xyz/openbmc_project/software/bmc/updateable", 943 [asyncResp, payload = std::move(payload), memfd = std::move(memfd), 944 applyTime]( 945 const boost::system::error_code& ec, 946 const dbus::utility::MapperEndPoints& objectPaths) mutable { 947 handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime, 948 ec, objectPaths); 949 }); 950 } 951 else 952 { 953 constexpr std::array<std::string_view, 1> interfaces = { 954 "xyz.openbmc_project.Software.Version"}; 955 dbus::utility::getSubTree( 956 "/xyz/openbmc_project/software", 1, interfaces, 957 [asyncResp, payload = std::move(payload), memfd = std::move(memfd), 958 applyTime, targets](const boost::system::error_code& ec, 959 const dbus::utility::MapperGetSubTreeResponse& 960 subtree) mutable { 961 getSwInfo(asyncResp, std::move(payload), memfd, applyTime, 962 targets[0], ec, subtree); 963 }); 964 } 965 } 966 967 inline void 968 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 969 const crow::Request& req, MultipartParser&& parser) 970 { 971 std::optional<MultiPartUpdateParameters> multipart = 972 extractMultipartUpdateParameters(asyncResp, std::move(parser)); 973 if (!multipart) 974 { 975 return; 976 } 977 if (!multipart->applyTime) 978 { 979 multipart->applyTime = "OnReset"; 980 } 981 982 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 983 { 984 std::string applyTimeNewVal; 985 if (!convertApplyTime(asyncResp->res, *multipart->applyTime, 986 applyTimeNewVal)) 987 { 988 return; 989 } 990 task::Payload payload(req); 991 992 processUpdateRequest(asyncResp, std::move(payload), 993 multipart->uploadData, applyTimeNewVal, 994 multipart->targets); 995 } 996 else 997 { 998 setApplyTime(asyncResp, *multipart->applyTime); 999 1000 // Setup callback for when new software detected 1001 monitorForSoftwareAvailable(asyncResp, req, 1002 "/redfish/v1/UpdateService"); 1003 1004 uploadImageFile(asyncResp->res, multipart->uploadData); 1005 } 1006 } 1007 1008 inline void doHTTPUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1009 const crow::Request& req) 1010 { 1011 if constexpr (BMCWEB_REDFISH_UPDATESERVICE_USE_DBUS) 1012 { 1013 task::Payload payload(req); 1014 // HTTP push only supports BMC updates (with ApplyTime as immediate) for 1015 // backwards compatibility. Specific component updates will be handled 1016 // through Multipart form HTTP push. 1017 std::vector<std::string> targets; 1018 targets.emplace_back(BMCWEB_REDFISH_MANAGER_URI_NAME); 1019 1020 processUpdateRequest( 1021 asyncResp, std::move(payload), req.body(), 1022 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate", 1023 targets); 1024 } 1025 else 1026 { 1027 // Setup callback for when new software detected 1028 monitorForSoftwareAvailable(asyncResp, req, 1029 "/redfish/v1/UpdateService"); 1030 1031 uploadImageFile(asyncResp->res, req.body()); 1032 } 1033 } 1034 1035 inline void 1036 handleUpdateServicePost(App& app, const crow::Request& req, 1037 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1038 { 1039 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1040 { 1041 return; 1042 } 1043 std::string_view contentType = req.getHeaderValue("Content-Type"); 1044 1045 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); 1046 1047 // Make sure that content type is application/octet-stream or 1048 // multipart/form-data 1049 if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) 1050 { 1051 doHTTPUpdate(asyncResp, req); 1052 } 1053 else if (contentType.starts_with("multipart/form-data")) 1054 { 1055 MultipartParser parser; 1056 1057 ParserError ec = parser.parse(req); 1058 if (ec != ParserError::PARSER_SUCCESS) 1059 { 1060 // handle error 1061 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", 1062 static_cast<int>(ec)); 1063 messages::internalError(asyncResp->res); 1064 return; 1065 } 1066 1067 updateMultipartContext(asyncResp, req, std::move(parser)); 1068 } 1069 else 1070 { 1071 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 1072 asyncResp->res.result(boost::beast::http::status::bad_request); 1073 } 1074 } 1075 1076 inline void 1077 handleUpdateServiceGet(App& app, const crow::Request& req, 1078 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1079 { 1080 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1081 { 1082 return; 1083 } 1084 asyncResp->res.jsonValue["@odata.type"] = 1085 "#UpdateService.v1_11_1.UpdateService"; 1086 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 1087 asyncResp->res.jsonValue["Id"] = "UpdateService"; 1088 asyncResp->res.jsonValue["Description"] = "Service for Software Update"; 1089 asyncResp->res.jsonValue["Name"] = "Update Service"; 1090 1091 asyncResp->res.jsonValue["HttpPushUri"] = 1092 "/redfish/v1/UpdateService/update"; 1093 asyncResp->res.jsonValue["MultipartHttpPushUri"] = 1094 "/redfish/v1/UpdateService/update"; 1095 1096 // UpdateService cannot be disabled 1097 asyncResp->res.jsonValue["ServiceEnabled"] = true; 1098 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 1099 "/redfish/v1/UpdateService/FirmwareInventory"; 1100 // Get the MaxImageSizeBytes 1101 asyncResp->res.jsonValue["MaxImageSizeBytes"] = 1102 BMCWEB_HTTP_BODY_LIMIT * 1024 * 1024; 1103 1104 if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE) 1105 { 1106 // Update Actions object. 1107 nlohmann::json& updateSvcSimpleUpdate = 1108 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 1109 updateSvcSimpleUpdate["target"] = 1110 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 1111 1112 nlohmann::json::array_t allowed; 1113 allowed.emplace_back(update_service::TransferProtocolType::HTTPS); 1114 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = 1115 std::move(allowed); 1116 } 1117 1118 asyncResp->res 1119 .jsonValue["HttpPushUriOptions"]["HttpPushUriApplyTime"]["ApplyTime"] = 1120 update_service::ApplyTime::Immediate; 1121 } 1122 1123 inline void handleUpdateServiceFirmwareInventoryCollectionGet( 1124 App& app, const crow::Request& req, 1125 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1126 { 1127 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1128 { 1129 return; 1130 } 1131 asyncResp->res.jsonValue["@odata.type"] = 1132 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 1133 asyncResp->res.jsonValue["@odata.id"] = 1134 "/redfish/v1/UpdateService/FirmwareInventory"; 1135 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; 1136 const std::array<const std::string_view, 1> iface = { 1137 "xyz.openbmc_project.Software.Version"}; 1138 1139 redfish::collection_util::getCollectionMembers( 1140 asyncResp, 1141 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface, 1142 "/xyz/openbmc_project/software"); 1143 } 1144 1145 /* Fill related item links (i.e. bmc, bios) in for inventory */ 1146 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1147 const std::string& purpose) 1148 { 1149 if (purpose == sw_util::bmcPurpose) 1150 { 1151 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1152 nlohmann::json::object_t item; 1153 item["@odata.id"] = boost::urls::format( 1154 "/redfish/v1/Managers/{}", BMCWEB_REDFISH_MANAGER_URI_NAME); 1155 relatedItem.emplace_back(std::move(item)); 1156 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1157 relatedItem.size(); 1158 } 1159 else if (purpose == sw_util::biosPurpose) 1160 { 1161 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1162 nlohmann::json::object_t item; 1163 item["@odata.id"] = std::format("/redfish/v1/Systems/{}/Bios", 1164 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1165 relatedItem.emplace_back(std::move(item)); 1166 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1167 relatedItem.size(); 1168 } 1169 else 1170 { 1171 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); 1172 } 1173 } 1174 1175 inline void 1176 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1177 const std::string& service, const std::string& path, 1178 const std::string& swId) 1179 { 1180 sdbusplus::asio::getAllProperties( 1181 *crow::connections::systemBus, service, path, 1182 "xyz.openbmc_project.Software.Version", 1183 [asyncResp, 1184 swId](const boost::system::error_code& ec, 1185 const dbus::utility::DBusPropertiesMap& propertiesList) { 1186 if (ec) 1187 { 1188 messages::internalError(asyncResp->res); 1189 return; 1190 } 1191 1192 const std::string* swInvPurpose = nullptr; 1193 const std::string* version = nullptr; 1194 1195 const bool success = sdbusplus::unpackPropertiesNoThrow( 1196 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 1197 swInvPurpose, "Version", version); 1198 1199 if (!success) 1200 { 1201 messages::internalError(asyncResp->res); 1202 return; 1203 } 1204 1205 if (swInvPurpose == nullptr) 1206 { 1207 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!"); 1208 messages::internalError(asyncResp->res); 1209 return; 1210 } 1211 1212 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose); 1213 1214 if (version == nullptr) 1215 { 1216 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!"); 1217 1218 messages::internalError(asyncResp->res); 1219 1220 return; 1221 } 1222 asyncResp->res.jsonValue["Version"] = *version; 1223 asyncResp->res.jsonValue["Id"] = swId; 1224 1225 // swInvPurpose is of format: 1226 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 1227 // Translate this to "ABC image" 1228 size_t endDesc = swInvPurpose->rfind('.'); 1229 if (endDesc == std::string::npos) 1230 { 1231 messages::internalError(asyncResp->res); 1232 return; 1233 } 1234 endDesc++; 1235 if (endDesc >= swInvPurpose->size()) 1236 { 1237 messages::internalError(asyncResp->res); 1238 return; 1239 } 1240 1241 std::string formatDesc = swInvPurpose->substr(endDesc); 1242 asyncResp->res.jsonValue["Description"] = formatDesc + " image"; 1243 getRelatedItems(asyncResp, *swInvPurpose); 1244 }); 1245 } 1246 1247 inline void handleUpdateServiceFirmwareInventoryGet( 1248 App& app, const crow::Request& req, 1249 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1250 const std::string& param) 1251 { 1252 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1253 { 1254 return; 1255 } 1256 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param); 1257 1258 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1259 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); 1260 1261 constexpr std::array<std::string_view, 1> interfaces = { 1262 "xyz.openbmc_project.Software.Version"}; 1263 dbus::utility::getSubTree( 1264 "/", 0, interfaces, 1265 [asyncResp, 1266 swId](const boost::system::error_code& ec, 1267 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1268 BMCWEB_LOG_DEBUG("doGet callback..."); 1269 if (ec) 1270 { 1271 messages::internalError(asyncResp->res); 1272 return; 1273 } 1274 1275 // Ensure we find our input swId, otherwise return an error 1276 bool found = false; 1277 for (const std::pair<std::string, 1278 std::vector<std::pair< 1279 std::string, std::vector<std::string>>>>& 1280 obj : subtree) 1281 { 1282 if (!obj.first.ends_with(*swId)) 1283 { 1284 continue; 1285 } 1286 1287 if (obj.second.empty()) 1288 { 1289 continue; 1290 } 1291 1292 found = true; 1293 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first); 1294 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, 1295 *swId); 1296 } 1297 if (!found) 1298 { 1299 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId); 1300 messages::resourceMissingAtURI( 1301 asyncResp->res, 1302 boost::urls::format( 1303 "/redfish/v1/UpdateService/FirmwareInventory/{}", 1304 *swId)); 1305 return; 1306 } 1307 asyncResp->res.jsonValue["@odata.type"] = 1308 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 1309 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 1310 asyncResp->res.jsonValue["Status"]["HealthRollup"] = 1311 resource::Health::OK; 1312 1313 asyncResp->res.jsonValue["Updateable"] = false; 1314 sw_util::getSwUpdatableStatus(asyncResp, swId); 1315 }); 1316 } 1317 1318 inline void requestRoutesUpdateService(App& app) 1319 { 1320 if constexpr (BMCWEB_REDFISH_ALLOW_SIMPLE_UPDATE) 1321 { 1322 BMCWEB_ROUTE( 1323 app, 1324 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 1325 .privileges(redfish::privileges::postUpdateService) 1326 .methods(boost::beast::http::verb::post)(std::bind_front( 1327 handleUpdateServiceSimpleUpdateAction, std::ref(app))); 1328 } 1329 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 1330 .privileges(redfish::privileges::getSoftwareInventory) 1331 .methods(boost::beast::http::verb::get)(std::bind_front( 1332 handleUpdateServiceFirmwareInventoryGet, std::ref(app))); 1333 1334 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1335 .privileges(redfish::privileges::getUpdateService) 1336 .methods(boost::beast::http::verb::get)( 1337 std::bind_front(handleUpdateServiceGet, std::ref(app))); 1338 1339 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 1340 .privileges(redfish::privileges::postUpdateService) 1341 .methods(boost::beast::http::verb::post)( 1342 std::bind_front(handleUpdateServicePost, std::ref(app))); 1343 1344 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 1345 .privileges(redfish::privileges::getSoftwareInventoryCollection) 1346 .methods(boost::beast::http::verb::get)(std::bind_front( 1347 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app))); 1348 } 1349 1350 } // namespace redfish 1351