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