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 <boost/system/error_code.hpp> 36 #include <boost/url/format.hpp> 37 #include <sdbusplus/asio/property.hpp> 38 #include <sdbusplus/bus/match.hpp> 39 #include <sdbusplus/unpack_properties.hpp> 40 41 #include <array> 42 #include <filesystem> 43 #include <memory> 44 #include <optional> 45 #include <string> 46 #include <string_view> 47 #include <vector> 48 49 namespace redfish 50 { 51 52 // Match signals added on software path 53 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 54 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher; 55 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 56 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateErrorMatcher; 57 // Only allow one update at a time 58 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 59 static bool fwUpdateInProgress = false; 60 // Timer for software available 61 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 62 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; 63 64 inline void cleanUp() 65 { 66 fwUpdateInProgress = false; 67 fwUpdateMatcher = nullptr; 68 fwUpdateErrorMatcher = nullptr; 69 } 70 71 inline void activateImage(const std::string& objPath, 72 const std::string& service) 73 { 74 BMCWEB_LOG_DEBUG("Activate image for {} {}", objPath, service); 75 sdbusplus::asio::setProperty( 76 *crow::connections::systemBus, service, objPath, 77 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 78 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active", 79 [](const boost::system::error_code& ec) { 80 if (ec) 81 { 82 BMCWEB_LOG_DEBUG("error_code = {}", ec); 83 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 84 } 85 }); 86 } 87 88 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 89 // then no asyncResp updates will occur 90 static void 91 softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 92 sdbusplus::message_t& m, task::Payload&& payload) 93 { 94 dbus::utility::DBusInterfacesMap interfacesProperties; 95 96 sdbusplus::message::object_path objPath; 97 98 m.read(objPath, interfacesProperties); 99 100 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 101 for (const auto& interface : interfacesProperties) 102 { 103 BMCWEB_LOG_DEBUG("interface = {}", interface.first); 104 105 if (interface.first == "xyz.openbmc_project.Software.Activation") 106 { 107 // Retrieve service and activate 108 constexpr std::array<std::string_view, 1> interfaces = { 109 "xyz.openbmc_project.Software.Activation"}; 110 dbus::utility::getDbusObject( 111 objPath.str, interfaces, 112 [objPath, asyncResp, payload(std::move(payload))]( 113 const boost::system::error_code& ec, 114 const std::vector< 115 std::pair<std::string, std::vector<std::string>>>& 116 objInfo) mutable { 117 if (ec) 118 { 119 BMCWEB_LOG_DEBUG("error_code = {}", ec); 120 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 121 if (asyncResp) 122 { 123 messages::internalError(asyncResp->res); 124 } 125 cleanUp(); 126 return; 127 } 128 // Ensure we only got one service back 129 if (objInfo.size() != 1) 130 { 131 BMCWEB_LOG_ERROR("Invalid Object Size {}", objInfo.size()); 132 if (asyncResp) 133 { 134 messages::internalError(asyncResp->res); 135 } 136 cleanUp(); 137 return; 138 } 139 // cancel timer only when 140 // xyz.openbmc_project.Software.Activation interface 141 // is added 142 fwAvailableTimer = nullptr; 143 144 activateImage(objPath.str, objInfo[0].first); 145 if (asyncResp) 146 { 147 std::shared_ptr<task::TaskData> task = 148 task::TaskData::createTask( 149 [](const boost::system::error_code& ec2, 150 sdbusplus::message_t& msg, 151 const std::shared_ptr<task::TaskData>& 152 taskData) { 153 if (ec2) 154 { 155 return task::completed; 156 } 157 158 std::string iface; 159 dbus::utility::DBusPropertiesMap values; 160 161 std::string index = std::to_string(taskData->index); 162 msg.read(iface, values); 163 164 if (iface == "xyz.openbmc_project.Software.Activation") 165 { 166 const std::string* state = nullptr; 167 for (const auto& property : values) 168 { 169 if (property.first == "Activation") 170 { 171 state = std::get_if<std::string>( 172 &property.second); 173 if (state == nullptr) 174 { 175 taskData->messages.emplace_back( 176 messages::internalError()); 177 return task::completed; 178 } 179 } 180 } 181 182 if (state == nullptr) 183 { 184 return !task::completed; 185 } 186 187 if (state->ends_with("Invalid") || 188 state->ends_with("Failed")) 189 { 190 taskData->state = "Exception"; 191 taskData->status = "Warning"; 192 taskData->messages.emplace_back( 193 messages::taskAborted(index)); 194 return task::completed; 195 } 196 197 if (state->ends_with("Staged")) 198 { 199 taskData->state = "Stopping"; 200 taskData->messages.emplace_back( 201 messages::taskPaused(index)); 202 203 // its staged, set a long timer to 204 // allow them time to complete the 205 // update (probably cycle the 206 // system) if this expires then 207 // task will be canceled 208 taskData->extendTimer(std::chrono::hours(5)); 209 return !task::completed; 210 } 211 212 if (state->ends_with("Active")) 213 { 214 taskData->messages.emplace_back( 215 messages::taskCompletedOK(index)); 216 taskData->state = "Completed"; 217 return task::completed; 218 } 219 } 220 else if ( 221 iface == 222 "xyz.openbmc_project.Software.ActivationProgress") 223 { 224 const uint8_t* progress = nullptr; 225 for (const auto& property : values) 226 { 227 if (property.first == "Progress") 228 { 229 progress = 230 std::get_if<uint8_t>(&property.second); 231 if (progress == nullptr) 232 { 233 taskData->messages.emplace_back( 234 messages::internalError()); 235 return task::completed; 236 } 237 } 238 } 239 240 if (progress == nullptr) 241 { 242 return !task::completed; 243 } 244 taskData->percentComplete = *progress; 245 taskData->messages.emplace_back( 246 messages::taskProgressChanged(index, 247 *progress)); 248 249 // if we're getting status updates it's 250 // still alive, update timer 251 taskData->extendTimer(std::chrono::minutes(5)); 252 } 253 254 // as firmware update often results in a 255 // reboot, the task may never "complete" 256 // unless it is an error 257 258 return !task::completed; 259 }, 260 "type='signal',interface='org.freedesktop.DBus.Properties'," 261 "member='PropertiesChanged',path='" + 262 objPath.str + "'"); 263 task->startTimer(std::chrono::minutes(5)); 264 task->populateResp(asyncResp->res); 265 task->payload.emplace(std::move(payload)); 266 } 267 fwUpdateInProgress = false; 268 }); 269 270 break; 271 } 272 } 273 } 274 275 inline void afterAvailbleTimerAsyncWait( 276 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 277 const boost::system::error_code& ec) 278 { 279 cleanUp(); 280 if (ec == boost::asio::error::operation_aborted) 281 { 282 // expected, we were canceled before the timer completed. 283 return; 284 } 285 BMCWEB_LOG_ERROR("Timed out waiting for firmware object being created"); 286 BMCWEB_LOG_ERROR("FW image may has already been uploaded to server"); 287 if (ec) 288 { 289 BMCWEB_LOG_ERROR("Async_wait failed{}", ec); 290 return; 291 } 292 if (asyncResp) 293 { 294 redfish::messages::internalError(asyncResp->res); 295 } 296 } 297 298 inline void 299 handleUpdateErrorType(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 300 const std::string& url, const std::string& type) 301 { 302 if (type == "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 303 { 304 redfish::messages::invalidUpload(asyncResp->res, url, 305 "Invalid archive"); 306 } 307 else if (type == 308 "xyz.openbmc_project.Software.Image.Error.ManifestFileFailure") 309 { 310 redfish::messages::invalidUpload(asyncResp->res, url, 311 "Invalid manifest"); 312 } 313 else if (type == "xyz.openbmc_project.Software.Image.Error.ImageFailure") 314 { 315 redfish::messages::invalidUpload(asyncResp->res, url, 316 "Invalid image format"); 317 } 318 else if (type == "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 319 { 320 redfish::messages::invalidUpload(asyncResp->res, url, 321 "Image version already exists"); 322 323 redfish::messages::resourceAlreadyExists( 324 asyncResp->res, "UpdateService", "Version", "uploaded version"); 325 } 326 else if (type == "xyz.openbmc_project.Software.Image.Error.BusyFailure") 327 { 328 redfish::messages::resourceExhaustion(asyncResp->res, url); 329 } 330 else if (type == "xyz.openbmc_project.Software.Version.Error.Incompatible") 331 { 332 redfish::messages::invalidUpload(asyncResp->res, url, 333 "Incompatible image version"); 334 } 335 else if (type == 336 "xyz.openbmc_project.Software.Version.Error.ExpiredAccessKey") 337 { 338 redfish::messages::invalidUpload(asyncResp->res, url, 339 "Update Access Key Expired"); 340 } 341 else if (type == 342 "xyz.openbmc_project.Software.Version.Error.InvalidSignature") 343 { 344 redfish::messages::invalidUpload(asyncResp->res, url, 345 "Invalid image signature"); 346 } 347 else if (type == 348 "xyz.openbmc_project.Software.Image.Error.InternalFailure" || 349 type == "xyz.openbmc_project.Software.Version.Error.HostFile") 350 { 351 BMCWEB_LOG_ERROR("Software Image Error type={}", type); 352 redfish::messages::internalError(asyncResp->res); 353 } 354 else 355 { 356 // Unrelated error types. Ignored 357 BMCWEB_LOG_INFO("Non-Software-related Error type={}. Ignored", type); 358 return; 359 } 360 // Clear the timer 361 fwAvailableTimer = nullptr; 362 } 363 364 inline void 365 afterUpdateErrorMatcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 366 const std::string& url, sdbusplus::message_t& m) 367 { 368 dbus::utility::DBusInterfacesMap interfacesProperties; 369 sdbusplus::message::object_path objPath; 370 m.read(objPath, interfacesProperties); 371 BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); 372 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 373 interface : interfacesProperties) 374 { 375 if (interface.first == "xyz.openbmc_project.Logging.Entry") 376 { 377 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 378 value : interface.second) 379 { 380 if (value.first != "Message") 381 { 382 continue; 383 } 384 const std::string* type = 385 std::get_if<std::string>(&value.second); 386 if (type == nullptr) 387 { 388 // if this was our message, timeout will cover it 389 return; 390 } 391 handleUpdateErrorType(asyncResp, url, *type); 392 } 393 } 394 } 395 } 396 397 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 398 // then no asyncResp updates will occur 399 inline void monitorForSoftwareAvailable( 400 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 401 const crow::Request& req, const std::string& url, 402 int timeoutTimeSeconds = 25) 403 { 404 // Only allow one FW update at a time 405 if (fwUpdateInProgress) 406 { 407 if (asyncResp) 408 { 409 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 410 } 411 return; 412 } 413 414 if (req.ioService == nullptr) 415 { 416 messages::internalError(asyncResp->res); 417 return; 418 } 419 420 fwAvailableTimer = 421 std::make_unique<boost::asio::steady_timer>(*req.ioService); 422 423 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 424 425 fwAvailableTimer->async_wait( 426 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); 427 428 task::Payload payload(req); 429 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 430 BMCWEB_LOG_DEBUG("Match fired"); 431 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 432 }; 433 434 fwUpdateInProgress = true; 435 436 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 437 *crow::connections::systemBus, 438 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 439 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 440 callback); 441 442 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 443 *crow::connections::systemBus, 444 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 445 "member='InterfacesAdded'," 446 "path='/xyz/openbmc_project/logging'", 447 std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); 448 } 449 450 inline std::optional<boost::urls::url> 451 parseSimpleUpdateUrl(std::string imageURI, 452 std::optional<std::string> transferProtocol, 453 crow::Response& res) 454 { 455 if (imageURI.find("://") == std::string::npos) 456 { 457 if (imageURI.starts_with("/")) 458 { 459 messages::actionParameterValueTypeError( 460 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 461 return std::nullopt; 462 } 463 if (!transferProtocol) 464 { 465 messages::actionParameterValueTypeError( 466 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 467 return std::nullopt; 468 } 469 // OpenBMC currently only supports TFTP or HTTPS 470 if (*transferProtocol == "TFTP") 471 { 472 imageURI = "tftp://" + imageURI; 473 } 474 else if (*transferProtocol == "HTTPS") 475 { 476 imageURI = "https://" + imageURI; 477 } 478 else 479 { 480 messages::actionParameterNotSupported(res, "TransferProtocol", 481 *transferProtocol); 482 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}", 483 *transferProtocol); 484 return std::nullopt; 485 } 486 } 487 488 boost::system::result<boost::urls::url> url = 489 boost::urls::parse_absolute_uri(imageURI); 490 if (!url) 491 { 492 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 493 "UpdateService.SimpleUpdate"); 494 495 return std::nullopt; 496 } 497 url->normalize(); 498 499 if (url->scheme() == "tftp") 500 { 501 if (url->encoded_path().size() < 2) 502 { 503 messages::actionParameterNotSupported(res, "ImageURI", 504 url->buffer()); 505 return std::nullopt; 506 } 507 } 508 else if (url->scheme() == "https") 509 { 510 // Empty paths default to "/" 511 if (url->encoded_path().empty()) 512 { 513 url->set_encoded_path("/"); 514 } 515 } 516 else 517 { 518 messages::actionParameterNotSupported(res, "ImageURI", imageURI); 519 return std::nullopt; 520 } 521 522 if (url->encoded_path().empty()) 523 { 524 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 525 "UpdateService.SimpleUpdate"); 526 return std::nullopt; 527 } 528 529 return *url; 530 } 531 532 inline void doHttpsUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 533 const boost::urls::url_view_base& url) 534 { 535 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 536 url.buffer()); 537 } 538 539 inline void doTftpUpdate(const crow::Request& req, 540 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 541 const boost::urls::url_view_base& url) 542 { 543 if (!BMCWEB_INSECURE_TFTP_UPDATE) 544 { 545 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 546 url.buffer()); 547 return; 548 } 549 550 std::string path(url.encoded_path()); 551 if (path.size() < 2) 552 { 553 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 554 url.buffer()); 555 return; 556 } 557 // TFTP expects a path without a / 558 path.erase(0, 1); 559 std::string host(url.encoded_host_and_port()); 560 BMCWEB_LOG_DEBUG("Server: {} File: {}", host, path); 561 562 // Setup callback for when new software detected 563 // Give TFTP 10 minutes to complete 564 monitorForSoftwareAvailable( 565 asyncResp, req, 566 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 600); 567 568 // TFTP can take up to 10 minutes depending on image size and 569 // connection speed. Return to caller as soon as the TFTP operation 570 // has been started. The callback above will ensure the activate 571 // is started once the download has completed 572 redfish::messages::success(asyncResp->res); 573 574 // Call TFTP service 575 crow::connections::systemBus->async_method_call( 576 [](const boost::system::error_code& ec) { 577 if (ec) 578 { 579 // messages::internalError(asyncResp->res); 580 cleanUp(); 581 BMCWEB_LOG_DEBUG("error_code = {}", ec); 582 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 583 } 584 else 585 { 586 BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success"); 587 } 588 }, 589 "xyz.openbmc_project.Software.Download", 590 "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP", 591 "DownloadViaTFTP", path, host); 592 } 593 594 inline void handleUpdateServiceSimpleUpdateAction( 595 crow::App& app, const crow::Request& req, 596 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 597 { 598 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 599 { 600 return; 601 } 602 603 std::optional<std::string> transferProtocol; 604 std::string imageURI; 605 606 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 607 608 // User can pass in both TransferProtocol and ImageURI parameters or 609 // they can pass in just the ImageURI with the transfer protocol 610 // embedded within it. 611 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 612 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 613 614 if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol", 615 transferProtocol, "ImageURI", imageURI)) 616 { 617 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); 618 return; 619 } 620 621 std::optional<boost::urls::url> url = 622 parseSimpleUpdateUrl(imageURI, transferProtocol, asyncResp->res); 623 if (!url) 624 { 625 return; 626 } 627 if (url->scheme() == "tftp") 628 { 629 doTftpUpdate(req, asyncResp, *url); 630 } 631 else if (url->scheme() == "https") 632 { 633 doHttpsUpdate(asyncResp, *url); 634 } 635 else 636 { 637 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", 638 url->buffer()); 639 return; 640 } 641 642 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); 643 } 644 645 inline void uploadImageFile(crow::Response& res, std::string_view body) 646 { 647 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); 648 649 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); 650 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 651 std::ofstream::trunc); 652 // set the permission of the file to 640 653 std::filesystem::perms permission = std::filesystem::perms::owner_read | 654 std::filesystem::perms::group_read; 655 std::filesystem::permissions(filepath, permission); 656 out << body; 657 658 if (out.bad()) 659 { 660 messages::internalError(res); 661 cleanUp(); 662 } 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 (applyTime == "Immediate") 670 { 671 applyTimeNewVal = 672 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 673 } 674 else if (applyTime == "OnReset") 675 { 676 applyTimeNewVal = 677 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 678 } 679 else 680 { 681 BMCWEB_LOG_INFO( 682 "ApplyTime value is not in the list of acceptable values"); 683 messages::propertyValueNotInList(asyncResp->res, applyTime, 684 "ApplyTime"); 685 return; 686 } 687 688 setDbusProperty(asyncResp, "xyz.openbmc_project.Settings", 689 sdbusplus::message::object_path( 690 "/xyz/openbmc_project/software/apply_time"), 691 "xyz.openbmc_project.Software.ApplyTime", 692 "RequestedApplyTime", "ApplyTime", applyTimeNewVal); 693 } 694 695 struct MultiPartUpdateParameters 696 { 697 std::optional<std::string> applyTime; 698 std::string uploadData; 699 std::vector<boost::urls::url> targets; 700 }; 701 702 inline std::optional<MultiPartUpdateParameters> 703 extractMultipartUpdateParameters( 704 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 705 MultipartParser parser) 706 { 707 MultiPartUpdateParameters multiRet; 708 for (FormPart& formpart : parser.mime_fields) 709 { 710 boost::beast::http::fields::const_iterator it = 711 formpart.fields.find("Content-Disposition"); 712 if (it == formpart.fields.end()) 713 { 714 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); 715 return std::nullopt; 716 } 717 BMCWEB_LOG_INFO("Parsing value {}", it->value()); 718 719 // The construction parameters of param_list must start with `;` 720 size_t index = it->value().find(';'); 721 if (index == std::string::npos) 722 { 723 continue; 724 } 725 726 for (const auto& param : 727 boost::beast::http::param_list{it->value().substr(index)}) 728 { 729 if (param.first != "name" || param.second.empty()) 730 { 731 continue; 732 } 733 734 if (param.second == "UpdateParameters") 735 { 736 std::vector<std::string> tempTargets; 737 nlohmann::json content = 738 nlohmann::json::parse(formpart.content); 739 nlohmann::json::object_t* obj = 740 content.get_ptr<nlohmann::json::object_t*>(); 741 if (obj == nullptr) 742 { 743 messages::propertyValueTypeError( 744 asyncResp->res, formpart.content, "UpdateParameters"); 745 return std::nullopt; 746 } 747 748 if (!json_util::readJsonObject( 749 *obj, asyncResp->res, "Targets", tempTargets, 750 "@Redfish.OperationApplyTime", multiRet.applyTime)) 751 { 752 return std::nullopt; 753 } 754 755 for (size_t urlIndex = 0; urlIndex < tempTargets.size(); 756 urlIndex++) 757 { 758 const std::string& target = tempTargets[urlIndex]; 759 boost::system::result<boost::urls::url_view> url = 760 boost::urls::parse_origin_form(target); 761 if (!url) 762 { 763 messages::propertyValueFormatError( 764 asyncResp->res, target, 765 std::format("Targets/{}", urlIndex)); 766 return std::nullopt; 767 } 768 multiRet.targets.emplace_back(*url); 769 } 770 if (multiRet.targets.size() != 1) 771 { 772 messages::propertyValueFormatError( 773 asyncResp->res, multiRet.targets, "Targets"); 774 return std::nullopt; 775 } 776 if (multiRet.targets[0].path() != "/redfish/v1/Managers/bmc") 777 { 778 messages::propertyValueNotInList( 779 asyncResp->res, multiRet.targets[0], "Targets/0"); 780 return std::nullopt; 781 } 782 } 783 else if (param.second == "UpdateFile") 784 { 785 multiRet.uploadData = std::move(formpart.content); 786 } 787 } 788 } 789 790 if (multiRet.uploadData.empty()) 791 { 792 BMCWEB_LOG_ERROR("Upload data is NULL"); 793 messages::propertyMissing(asyncResp->res, "UpdateFile"); 794 return std::nullopt; 795 } 796 if (multiRet.targets.empty()) 797 { 798 messages::propertyMissing(asyncResp->res, "Targets"); 799 return std::nullopt; 800 } 801 return multiRet; 802 } 803 804 inline void 805 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 806 const crow::Request& req, MultipartParser&& parser) 807 { 808 std::optional<MultiPartUpdateParameters> multipart = 809 extractMultipartUpdateParameters(asyncResp, std::move(parser)); 810 if (!multipart) 811 { 812 return; 813 } 814 if (!multipart->applyTime) 815 { 816 multipart->applyTime = "OnReset"; 817 } 818 819 setApplyTime(asyncResp, *multipart->applyTime); 820 821 // Setup callback for when new software detected 822 monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService"); 823 824 uploadImageFile(asyncResp->res, multipart->uploadData); 825 } 826 827 inline void 828 handleUpdateServicePost(App& app, const crow::Request& req, 829 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 830 { 831 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 832 { 833 return; 834 } 835 std::string_view contentType = req.getHeaderValue("Content-Type"); 836 837 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); 838 839 // Make sure that content type is application/octet-stream or 840 // multipart/form-data 841 if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) 842 { 843 // Setup callback for when new software detected 844 monitorForSoftwareAvailable(asyncResp, req, 845 "/redfish/v1/UpdateService"); 846 847 uploadImageFile(asyncResp->res, req.body()); 848 } 849 else if (contentType.starts_with("multipart/form-data")) 850 { 851 MultipartParser parser; 852 853 ParserError ec = parser.parse(req); 854 if (ec != ParserError::PARSER_SUCCESS) 855 { 856 // handle error 857 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", 858 static_cast<int>(ec)); 859 messages::internalError(asyncResp->res); 860 return; 861 } 862 863 updateMultipartContext(asyncResp, req, std::move(parser)); 864 } 865 else 866 { 867 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 868 asyncResp->res.result(boost::beast::http::status::bad_request); 869 } 870 } 871 872 inline void 873 handleUpdateServiceGet(App& app, const crow::Request& req, 874 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 875 { 876 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 877 { 878 return; 879 } 880 asyncResp->res.jsonValue["@odata.type"] = 881 "#UpdateService.v1_11_1.UpdateService"; 882 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 883 asyncResp->res.jsonValue["Id"] = "UpdateService"; 884 asyncResp->res.jsonValue["Description"] = "Service for Software Update"; 885 asyncResp->res.jsonValue["Name"] = "Update Service"; 886 887 asyncResp->res.jsonValue["HttpPushUri"] = 888 "/redfish/v1/UpdateService/update"; 889 asyncResp->res.jsonValue["MultipartHttpPushUri"] = 890 "/redfish/v1/UpdateService/update"; 891 892 // UpdateService cannot be disabled 893 asyncResp->res.jsonValue["ServiceEnabled"] = true; 894 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 895 "/redfish/v1/UpdateService/FirmwareInventory"; 896 // Get the MaxImageSizeBytes 897 asyncResp->res.jsonValue["MaxImageSizeBytes"] = BMCWEB_HTTP_BODY_LIMIT * 898 1024 * 1024; 899 900 // Update Actions object. 901 nlohmann::json& updateSvcSimpleUpdate = 902 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 903 updateSvcSimpleUpdate["target"] = 904 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 905 906 nlohmann::json::array_t allowed; 907 allowed.emplace_back(update_service::TransferProtocolType::HTTPS); 908 909 if constexpr (BMCWEB_INSECURE_PUSH_STYLE_NOTIFICATION) 910 { 911 allowed.emplace_back(update_service::TransferProtocolType::TFTP); 912 } 913 914 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = 915 std::move(allowed); 916 917 // Get the current ApplyTime value 918 sdbusplus::asio::getProperty<std::string>( 919 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 920 "/xyz/openbmc_project/software/apply_time", 921 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime", 922 [asyncResp](const boost::system::error_code& ec, 923 const std::string& applyTime) { 924 if (ec) 925 { 926 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 927 messages::internalError(asyncResp->res); 928 return; 929 } 930 931 // Store the ApplyTime Value 932 if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 933 "RequestedApplyTimes.Immediate") 934 { 935 asyncResp->res.jsonValue["HttpPushUriOptions"] 936 ["HttpPushUriApplyTime"]["ApplyTime"] = 937 "Immediate"; 938 } 939 else if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 940 "RequestedApplyTimes.OnReset") 941 { 942 asyncResp->res.jsonValue["HttpPushUriOptions"] 943 ["HttpPushUriApplyTime"]["ApplyTime"] = 944 "OnReset"; 945 } 946 }); 947 } 948 949 inline void handleUpdateServicePatch( 950 App& app, const crow::Request& req, 951 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 952 { 953 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 954 { 955 return; 956 } 957 BMCWEB_LOG_DEBUG("doPatch..."); 958 959 std::optional<std::string> applyTime; 960 if (!json_util::readJsonPatch( 961 req, asyncResp->res, 962 "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime)) 963 { 964 return; 965 } 966 967 if (applyTime) 968 { 969 setApplyTime(asyncResp, *applyTime); 970 } 971 } 972 973 inline void handleUpdateServiceFirmwareInventoryCollectionGet( 974 App& app, const crow::Request& req, 975 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 976 { 977 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 978 { 979 return; 980 } 981 asyncResp->res.jsonValue["@odata.type"] = 982 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 983 asyncResp->res.jsonValue["@odata.id"] = 984 "/redfish/v1/UpdateService/FirmwareInventory"; 985 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; 986 const std::array<const std::string_view, 1> iface = { 987 "xyz.openbmc_project.Software.Version"}; 988 989 redfish::collection_util::getCollectionMembers( 990 asyncResp, 991 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface, 992 "/xyz/openbmc_project/software"); 993 } 994 995 /* Fill related item links (i.e. bmc, bios) in for inventory */ 996 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 997 const std::string& purpose) 998 { 999 if (purpose == sw_util::bmcPurpose) 1000 { 1001 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1002 nlohmann::json::object_t item; 1003 item["@odata.id"] = "/redfish/v1/Managers/bmc"; 1004 relatedItem.emplace_back(std::move(item)); 1005 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1006 relatedItem.size(); 1007 } 1008 else if (purpose == sw_util::biosPurpose) 1009 { 1010 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 1011 nlohmann::json::object_t item; 1012 item["@odata.id"] = "/redfish/v1/Systems/system/Bios"; 1013 relatedItem.emplace_back(std::move(item)); 1014 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 1015 relatedItem.size(); 1016 } 1017 else 1018 { 1019 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); 1020 } 1021 } 1022 1023 inline void 1024 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1025 const std::string& service, const std::string& path, 1026 const std::string& swId) 1027 { 1028 sdbusplus::asio::getAllProperties( 1029 *crow::connections::systemBus, service, path, 1030 "xyz.openbmc_project.Software.Version", 1031 [asyncResp, 1032 swId](const boost::system::error_code& ec, 1033 const dbus::utility::DBusPropertiesMap& propertiesList) { 1034 if (ec) 1035 { 1036 messages::internalError(asyncResp->res); 1037 return; 1038 } 1039 1040 const std::string* swInvPurpose = nullptr; 1041 const std::string* version = nullptr; 1042 1043 const bool success = sdbusplus::unpackPropertiesNoThrow( 1044 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 1045 swInvPurpose, "Version", version); 1046 1047 if (!success) 1048 { 1049 messages::internalError(asyncResp->res); 1050 return; 1051 } 1052 1053 if (swInvPurpose == nullptr) 1054 { 1055 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!"); 1056 messages::internalError(asyncResp->res); 1057 return; 1058 } 1059 1060 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose); 1061 1062 if (version == nullptr) 1063 { 1064 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!"); 1065 1066 messages::internalError(asyncResp->res); 1067 1068 return; 1069 } 1070 asyncResp->res.jsonValue["Version"] = *version; 1071 asyncResp->res.jsonValue["Id"] = swId; 1072 1073 // swInvPurpose is of format: 1074 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 1075 // Translate this to "ABC image" 1076 size_t endDesc = swInvPurpose->rfind('.'); 1077 if (endDesc == std::string::npos) 1078 { 1079 messages::internalError(asyncResp->res); 1080 return; 1081 } 1082 endDesc++; 1083 if (endDesc >= swInvPurpose->size()) 1084 { 1085 messages::internalError(asyncResp->res); 1086 return; 1087 } 1088 1089 std::string formatDesc = swInvPurpose->substr(endDesc); 1090 asyncResp->res.jsonValue["Description"] = formatDesc + " image"; 1091 getRelatedItems(asyncResp, *swInvPurpose); 1092 }); 1093 } 1094 1095 inline void handleUpdateServiceFirmwareInventoryGet( 1096 App& app, const crow::Request& req, 1097 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1098 const std::string& param) 1099 { 1100 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1101 { 1102 return; 1103 } 1104 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param); 1105 1106 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1107 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); 1108 1109 constexpr std::array<std::string_view, 1> interfaces = { 1110 "xyz.openbmc_project.Software.Version"}; 1111 dbus::utility::getSubTree( 1112 "/", 0, interfaces, 1113 [asyncResp, 1114 swId](const boost::system::error_code& ec, 1115 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1116 BMCWEB_LOG_DEBUG("doGet callback..."); 1117 if (ec) 1118 { 1119 messages::internalError(asyncResp->res); 1120 return; 1121 } 1122 1123 // Ensure we find our input swId, otherwise return an error 1124 bool found = false; 1125 for (const std::pair< 1126 std::string, 1127 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1128 obj : subtree) 1129 { 1130 if (!obj.first.ends_with(*swId)) 1131 { 1132 continue; 1133 } 1134 1135 if (obj.second.empty()) 1136 { 1137 continue; 1138 } 1139 1140 found = true; 1141 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first); 1142 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, 1143 *swId); 1144 } 1145 if (!found) 1146 { 1147 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId); 1148 messages::resourceMissingAtURI( 1149 asyncResp->res, 1150 boost::urls::format( 1151 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId)); 1152 return; 1153 } 1154 asyncResp->res.jsonValue["@odata.type"] = 1155 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 1156 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 1157 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 1158 1159 asyncResp->res.jsonValue["Updateable"] = false; 1160 sw_util::getSwUpdatableStatus(asyncResp, swId); 1161 }); 1162 } 1163 1164 inline void requestRoutesUpdateService(App& app) 1165 { 1166 BMCWEB_ROUTE( 1167 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 1168 .privileges(redfish::privileges::postUpdateService) 1169 .methods(boost::beast::http::verb::post)(std::bind_front( 1170 handleUpdateServiceSimpleUpdateAction, std::ref(app))); 1171 1172 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 1173 .privileges(redfish::privileges::getSoftwareInventory) 1174 .methods(boost::beast::http::verb::get)(std::bind_front( 1175 handleUpdateServiceFirmwareInventoryGet, std::ref(app))); 1176 1177 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1178 .privileges(redfish::privileges::getUpdateService) 1179 .methods(boost::beast::http::verb::get)( 1180 std::bind_front(handleUpdateServiceGet, std::ref(app))); 1181 1182 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1183 .privileges(redfish::privileges::patchUpdateService) 1184 .methods(boost::beast::http::verb::patch)( 1185 std::bind_front(handleUpdateServicePatch, std::ref(app))); 1186 1187 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 1188 .privileges(redfish::privileges::postUpdateService) 1189 .methods(boost::beast::http::verb::post)( 1190 std::bind_front(handleUpdateServicePost, std::ref(app))); 1191 1192 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 1193 .privileges(redfish::privileges::getSoftwareInventoryCollection) 1194 .methods(boost::beast::http::verb::get)(std::bind_front( 1195 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app))); 1196 } 1197 1198 } // namespace redfish 1199