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