1 /* 2 Copyright (c) 2018 Intel Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 #pragma once 17 18 #include "bmcweb_config.h" 19 20 #include "app.hpp" 21 #include "dbus_utility.hpp" 22 #include "error_messages.hpp" 23 #include "generated/enums/update_service.hpp" 24 #include "multipart_parser.hpp" 25 #include "ossl_random.hpp" 26 #include "query.hpp" 27 #include "registries/privilege_registry.hpp" 28 #include "task.hpp" 29 #include "task_messages.hpp" 30 #include "utils/collection.hpp" 31 #include "utils/dbus_utils.hpp" 32 #include "utils/json_utils.hpp" 33 #include "utils/sw_utils.hpp" 34 35 #include <sys/mman.h> 36 37 #include <boost/system/error_code.hpp> 38 #include <boost/url/format.hpp> 39 #include <sdbusplus/asio/property.hpp> 40 #include <sdbusplus/bus/match.hpp> 41 #include <sdbusplus/unpack_properties.hpp> 42 43 #include <array> 44 #include <cstddef> 45 #include <filesystem> 46 #include <functional> 47 #include <iterator> 48 #include <memory> 49 #include <optional> 50 #include <string> 51 #include <string_view> 52 #include <unordered_map> 53 #include <vector> 54 55 namespace redfish 56 { 57 58 // Match signals added on software path 59 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 60 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher; 61 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 62 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher; 63 // Only allow one update at a time 64 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 65 static bool fwUpdateInProgress = false; 66 // Timer for software available 67 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 68 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; 69 70 struct MemoryFileDescriptor 71 { 72 int fd = -1; 73 74 explicit MemoryFileDescriptor(const std::string& filename) : 75 fd(memfd_create(filename.c_str(), 0)) 76 {} 77 78 MemoryFileDescriptor(const MemoryFileDescriptor&) = default; 79 MemoryFileDescriptor(MemoryFileDescriptor&& other) noexcept : fd(other.fd) 80 { 81 other.fd = -1; 82 } 83 MemoryFileDescriptor& operator=(const MemoryFileDescriptor&) = delete; 84 MemoryFileDescriptor& operator=(MemoryFileDescriptor&&) = default; 85 86 ~MemoryFileDescriptor() 87 { 88 if (fd != -1) 89 { 90 close(fd); 91 } 92 } 93 94 bool rewind() const 95 { 96 if (lseek(fd, 0, SEEK_SET) == -1) 97 { 98 BMCWEB_LOG_ERROR("Failed to seek to beginning of image memfd"); 99 return false; 100 } 101 return true; 102 } 103 }; 104 105 inline void cleanUp() 106 { 107 fwUpdateInProgress = false; 108 fwUpdateMatcher = nullptr; 109 fwUpdateErrorMatcher = nullptr; 110 } 111 112 inline void activateImage(const std::string& objPath, 113 const std::string& service) 114 { 115 BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service); 116 sdbusplus::asio::setProperty( 117 *crow::connections::systemBus, service, objPath, 118 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 119 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active", 120 [](const boost::system::error_code& ec) { 121 if (ec) 122 { 123 BMCWEB_LOG_DEBUG("error_code = {}", ec); 124 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 125 } 126 }); 127 } 128 129 inline bool handleCreateTask(const boost::system::error_code& ec2, 130 sdbusplus::message_t& msg, 131 const std::shared_ptr<task::TaskData>& taskData) 132 { 133 if (ec2) 134 { 135 return task::completed; 136 } 137 138 std::string iface; 139 dbus::utility::DBusPropertiesMap values; 140 141 std::string index = std::to_string(taskData->index); 142 msg.read(iface, values); 143 144 if (iface == "xyz.openbmc_project.Software.Activation") 145 { 146 const std::string* state = nullptr; 147 for (const auto& property : values) 148 { 149 if (property.first == "Activation") 150 { 151 state = std::get_if<std::string>(&property.second); 152 if (state == nullptr) 153 { 154 taskData->messages.emplace_back(messages::internalError()); 155 return task::completed; 156 } 157 } 158 } 159 160 if (state == nullptr) 161 { 162 return !task::completed; 163 } 164 165 if (state->ends_with("Invalid") || state->ends_with("Failed")) 166 { 167 taskData->state = "Exception"; 168 taskData->status = "Warning"; 169 taskData->messages.emplace_back(messages::taskAborted(index)); 170 return task::completed; 171 } 172 173 if (state->ends_with("Staged")) 174 { 175 taskData->state = "Stopping"; 176 taskData->messages.emplace_back(messages::taskPaused(index)); 177 178 // its staged, set a long timer to 179 // allow them time to complete the 180 // update (probably cycle the 181 // system) if this expires then 182 // task will be canceled 183 taskData->extendTimer(std::chrono::hours(5)); 184 return !task::completed; 185 } 186 187 if (state->ends_with("Active")) 188 { 189 taskData->messages.emplace_back(messages::taskCompletedOK(index)); 190 taskData->state = "Completed"; 191 return task::completed; 192 } 193 } 194 else if (iface == "xyz.openbmc_project.Software.ActivationProgress") 195 { 196 const uint8_t* progress = nullptr; 197 for (const auto& property : values) 198 { 199 if (property.first == "Progress") 200 { 201 progress = std::get_if<uint8_t>(&property.second); 202 if (progress == nullptr) 203 { 204 taskData->messages.emplace_back(messages::internalError()); 205 return task::completed; 206 } 207 } 208 } 209 210 if (progress == nullptr) 211 { 212 return !task::completed; 213 } 214 taskData->percentComplete = *progress; 215 taskData->messages.emplace_back( 216 messages::taskProgressChanged(index, *progress)); 217 218 // if we're getting status updates it's 219 // still alive, update timer 220 taskData->extendTimer(std::chrono::minutes(5)); 221 } 222 223 // as firmware update often results in a 224 // reboot, the task may never "complete" 225 // unless it is an error 226 227 return !task::completed; 228 } 229 230 inline void createTask(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 231 task::Payload&& payload, 232 const sdbusplus::message::object_path& objPath) 233 { 234 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 235 std::bind_front(handleCreateTask), 236 "type='signal',interface='org.freedesktop.DBus.Properties'," 237 "member='PropertiesChanged',path='" + 238 objPath.str + "'"); 239 task->startTimer(std::chrono::minutes(5)); 240 task->populateResp(asyncResp->res); 241 task->payload.emplace(std::move(payload)); 242 } 243 244 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 245 // then no asyncResp updates will occur 246 inline void 247 softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 248 sdbusplus::message_t& m, task::Payload&& payload) 249 { 250 dbus::utility::DBusInterfacesMap interfacesProperties; 251 252 sdbusplus::message::object_path objPath; 253 254 m.read(objPath, interfacesProperties); 255 256 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 257 for (const auto& interface : interfacesProperties) 258 { 259 BMCWEB_LOG_DEBUG("interface = {}", interface.first); 260 261 if (interface.first == "xyz.openbmc_project.Software.Activation") 262 { 263 // Retrieve service and activate 264 constexpr std::array<std::string_view, 1> interfaces = { 265 "xyz.openbmc_project.Software.Activation"}; 266 dbus::utility::getDbusObject( 267 objPath.str, interfaces, 268 [objPath, asyncResp, payload(std::move(payload))]( 269 const boost::system::error_code& ec, 270 const std::vector< 271 std::pair<std::string, std::vector<std::string>>>& 272 objInfo) mutable { 273 if (ec) 274 { 275 BMCWEB_LOG_DEBUG("error_code = {}", ec); 276 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 277 if (asyncResp) 278 { 279 messages::internalError(asyncResp->res); 280 } 281 cleanUp(); 282 return; 283 } 284 // Ensure we only got one service back 285 if (objInfo.size() != 1) 286 { 287 BMCWEB_LOG_ERROR("Invalid Object Size {}", 288 objInfo.size()); 289 if (asyncResp) 290 { 291 messages::internalError(asyncResp->res); 292 } 293 cleanUp(); 294 return; 295 } 296 // cancel timer only when 297 // xyz.openbmc_project.Software.Activation interface 298 // is added 299 fwAvailableTimer = nullptr; 300 301 activateImage(objPath.str, objInfo[0].first); 302 if (asyncResp) 303 { 304 createTask(asyncResp, std::move(payload), objPath); 305 } 306 fwUpdateInProgress = false; 307 }); 308 309 break; 310 } 311 } 312 } 313 314 inline void afterAvailbleTimerAsyncWait( 315 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 316 const boost::system::error_code& ec) 317 { 318 cleanUp(); 319 if (ec == boost::asio::error::operation_aborted) 320 { 321 // expected, we were canceled before the timer completed. 322 return; 323 } 324 BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created"); 325 BMCWEB_LOG_ERROR("FW image may has already been uploaded to server"); 326 if (ec) 327 { 328 BMCWEB_LOG_ERROR("Async_wait failed{}", ec); 329 return; 330 } 331 if (asyncResp) 332 { 333 redfish::messages::internalError(asyncResp->res); 334 } 335 } 336 337 inline void 338 handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 339 const std::string& url, const std::string& type) 340 { 341 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 342 { 343 redfish::messages::invalidUpload(asyncResp->res, url, 344 "Invalid archive"); 345 } 346 else if (type == 347 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") 348 { 349 redfish::messages::invalidUpload(asyncResp->res, url, 350 "Invalid manifest"); 351 } 352 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") 353 { 354 redfish::messages::invalidUpload(asyncResp->res, url, 355 "Invalid image format"); 356 } 357 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 358 { 359 redfish::messages::invalidUpload(asyncResp->res, url, 360 "Image version already exists"); 361 362 redfish::messages::resourceAlreadyExists( 363 asyncResp->res, "UpdateService", "Version", "uploaded version"); 364 } 365 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") 366 { 367 redfish::messages::resourceExhaustion(asyncResp->res, url); 368 } 369 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") 370 { 371 redfish::messages::invalidUpload(asyncResp->res, url, 372 "Incompatible image version"); 373 } 374 else if (type == 375 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") 376 { 377 redfish::messages::invalidUpload(asyncResp->res, url, 378 "Update Access Key Expired"); 379 } 380 else if (type == 381 "xyz.openbmc_project.Software.Version.Error.InvalidSignature") 382 { 383 redfish::messages::invalidUpload(asyncResp->res, url, 384 "Invalid image signature"); 385 } 386 else if (type == 387 "xyz.openbmc_project.Software.Image.Error.InternalFailure" || 388 type == "xyz.openbmc_project.Software.Version.Error.HostFile") 389 { 390 BMCWEB_LOG_ERROR("Software Image Error type={}", type); 391 redfish::messages::internalError(asyncResp->res); 392 } 393 else 394 { 395 // Unrelated error types. Ignored 396 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type); 397 return; 398 } 399 // Clear the timer 400 fwAvailableTimer = nullptr; 401 } 402 403 inline void 404 afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 405 const std::string& url, sdbusplus::message_t& m) 406 { 407 dbus::utility::DBusInterfacesMap interfacesProperties; 408 sdbusplus::message::object_path objPath; 409 m.read(objPath, interfacesProperties); 410 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 411 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 412 interface : interfacesProperties) 413 { 414 if (interface.first == "xyz.openbmc_project.Logging.Entry") 415 { 416 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 417 value : interface.second) 418 { 419 if (value.first != "Message") 420 { 421 continue; 422 } 423 const std::string* type = 424 std::get_if<std::string>(&value.second); 425 if (type == nullptr) 426 { 427 // if this was our message, timeout will cover it 428 return; 429 } 430 handleUpdateErrorType(asyncResp, url, *type); 431 } 432 } 433 } 434 } 435 436 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 437 // then no asyncResp updates will occur 438 inline void monitorForSoftwareAvailable( 439 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 440 const crow::Request& req, const std::string& url, 441 int timeoutTimeSeconds = 25) 442 { 443 // Only allow one FW update at a time 444 if (fwUpdateInProgress) 445 { 446 if (asyncResp) 447 { 448 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 449 } 450 return; 451 } 452 453 if (req.ioService == nullptr) 454 { 455 messages::internalError(asyncResp->res); 456 return; 457 } 458 459 fwAvailableTimer = 460 std::make_unique<boost::asio::steady_timer>(*req.ioService); 461 462 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 463 464 fwAvailableTimer->async_wait( 465 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); 466 467 task::Payload payload(req); 468 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 469 BMCWEB_LOG_DEBUG("Match fired"); 470 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 471 }; 472 473 fwUpdateInProgress = true; 474 475 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 476 *crow::connections::systemBus, 477 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 478 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 479 callback); 480 481 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 482 *crow::connections::systemBus, 483 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 484 "member='InterfacesAdded'," 485 "path='/xyz/openbmc_project/logging'", 486 std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); 487 } 488 489 inline std::optional<boost::urls::url> parseSimpleUpdateUrl( 490 std::string imageURI, std::optional<std::string> transferProtocol, 491 crow::Response& res) 492 { 493 if (imageURI.find("://") == std::string::npos) 494 { 495 if (imageURI.starts_with("/")) 496 { 497 messages::actionParameterValueTypeError( 498 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 499 return std::nullopt; 500 } 501 if (!transferProtocol) 502 { 503 messages::actionParameterValueTypeError( 504 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 505 return std::nullopt; 506 } 507 // OpenBMC currently only supports TFTP or HTTPS 508 if (*transferProtocol == "TFTP") 509 { 510 imageURI = "tftp://" + imageURI; 511 } 512 else if (*transferProtocol == "HTTPS") 513 { 514 imageURI = "https://" + imageURI; 515 } 516 else 517 { 518 messages::actionParameterNotSupported(res, "TransferProtocol", 519 *transferProtocol); 520 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}", 521 *transferProtocol); 522 return std::nullopt; 523 } 524 } 525 526 boost::system::result<boost::urls::url> url = 527 boost::urls::parse_absolute_uri(imageURI); 528 if (!url) 529 { 530 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 531 "UpdateService.SimpleUpdate"); 532 533 return std::nullopt; 534 } 535 url->normalize(); 536 537 if (url->scheme() == "tftp") 538 { 539 if (url->encoded_path().size() < 2) 540 { 541 messages::actionParameterNotSupported(res, "ImageURI", 542 url->buffer()); 543 return std::nullopt; 544 } 545 } 546 else if (url->scheme() == "https") 547 { 548 // Empty paths default to "/" 549 if (url->encoded_path().empty()) 550 { 551 url->set_encoded_path("/"); 552 } 553 } 554 else 555 { 556 messages::actionParameterNotSupported(res, "ImageURI", imageURI); 557 return std::nullopt; 558 } 559 560 if (url->encoded_path().empty()) 561 { 562 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 563 "UpdateService.SimpleUpdate"); 564 return std::nullopt; 565 } 566 567 return *url; 568 } 569 570 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 571 const boost::urls::url_view_base& url) 572 { 573 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 574 url.buffer()); 575 } 576 577 inline void handleUpdateServiceSimpleUpdateAction( 578 crow::App& app, const crow::Request& req, 579 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 580 { 581 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 582 { 583 return; 584 } 585 586 std::optional<std::string> transferProtocol; 587 std::string imageURI; 588 589 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 590 591 // User can pass in both TransferProtocol and ImageURI parameters or 592 // they can pass in just the ImageURI with the transfer protocol 593 // embedded within it. 594 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 595 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 596 597 if (!json_util::readJsonAction(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/updateable", 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