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