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