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