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 handleUpdateServiceSimpleUpdateAction( 509 crow::App& app, const crow::Request& req, 510 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 511 { 512 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 513 { 514 return; 515 } 516 517 std::optional<std::string> transferProtocol; 518 std::string imageURI; 519 520 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 521 522 // User can pass in both TransferProtocol and ImageURI parameters or 523 // they can pass in just the ImageURI with the transfer protocol 524 // embedded within it. 525 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 526 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 527 528 if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol", 529 transferProtocol, "ImageURI", imageURI)) 530 { 531 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); 532 return; 533 } 534 535 std::optional<TftpUrl> ret = parseTftpUrl(imageURI, transferProtocol, 536 asyncResp->res); 537 if (!ret) 538 { 539 return; 540 } 541 542 BMCWEB_LOG_DEBUG("Server: {} File: {}", ret->tftpServer, ret->fwFile); 543 #ifndef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE 544 messages::actionParameterNotSupported(asyncResp->res, "ImageURI", imageURI); 545 return; 546 #endif 547 // Setup callback for when new software detected 548 // Give TFTP 10 minutes to complete 549 monitorForSoftwareAvailable( 550 asyncResp, req, 551 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 600); 552 553 // TFTP can take up to 10 minutes depending on image size and 554 // connection speed. Return to caller as soon as the TFTP operation 555 // has been started. The callback above will ensure the activate 556 // is started once the download has completed 557 redfish::messages::success(asyncResp->res); 558 559 // Call TFTP service 560 crow::connections::systemBus->async_method_call( 561 [](const boost::system::error_code& ec) { 562 if (ec) 563 { 564 // messages::internalError(asyncResp->res); 565 cleanUp(); 566 BMCWEB_LOG_DEBUG("error_code = {}", ec); 567 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 568 } 569 else 570 { 571 BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success"); 572 } 573 }, 574 "xyz.openbmc_project.Software.Download", 575 "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP", 576 "DownloadViaTFTP", ret->fwFile, ret->tftpServer); 577 578 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); 579 } 580 581 inline void uploadImageFile(crow::Response& res, std::string_view body) 582 { 583 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); 584 585 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); 586 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 587 std::ofstream::trunc); 588 // set the permission of the file to 640 589 std::filesystem::perms permission = std::filesystem::perms::owner_read | 590 std::filesystem::perms::group_read; 591 std::filesystem::permissions(filepath, permission); 592 out << body; 593 594 if (out.bad()) 595 { 596 messages::internalError(res); 597 cleanUp(); 598 } 599 } 600 601 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 602 const std::string& applyTime) 603 { 604 std::string applyTimeNewVal; 605 if (applyTime == "Immediate") 606 { 607 applyTimeNewVal = 608 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 609 } 610 else if (applyTime == "OnReset") 611 { 612 applyTimeNewVal = 613 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 614 } 615 else 616 { 617 BMCWEB_LOG_INFO( 618 "ApplyTime value is not in the list of acceptable values"); 619 messages::propertyValueNotInList(asyncResp->res, applyTime, 620 "ApplyTime"); 621 return; 622 } 623 624 setDbusProperty(asyncResp, "xyz.openbmc_project.Settings", 625 sdbusplus::message::object_path( 626 "/xyz/openbmc_project/software/apply_time"), 627 "xyz.openbmc_project.Software.ApplyTime", 628 "RequestedApplyTime", "ApplyTime", applyTimeNewVal); 629 } 630 631 inline void 632 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 633 const MultipartParser& parser) 634 { 635 const std::string* uploadData = nullptr; 636 std::optional<std::string> applyTime = "OnReset"; 637 bool targetFound = false; 638 for (const FormPart& formpart : parser.mime_fields) 639 { 640 boost::beast::http::fields::const_iterator it = 641 formpart.fields.find("Content-Disposition"); 642 if (it == formpart.fields.end()) 643 { 644 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); 645 return; 646 } 647 BMCWEB_LOG_INFO("Parsing value {}", it->value()); 648 649 // The construction parameters of param_list must start with `;` 650 size_t index = it->value().find(';'); 651 if (index == std::string::npos) 652 { 653 continue; 654 } 655 656 for (const auto& param : 657 boost::beast::http::param_list{it->value().substr(index)}) 658 { 659 if (param.first != "name" || param.second.empty()) 660 { 661 continue; 662 } 663 664 if (param.second == "UpdateParameters") 665 { 666 std::vector<std::string> targets; 667 nlohmann::json content = 668 nlohmann::json::parse(formpart.content); 669 nlohmann::json::object_t* obj = 670 content.get_ptr<nlohmann::json::object_t*>(); 671 if (obj == nullptr) 672 { 673 messages::propertyValueFormatError(asyncResp->res, targets, 674 "UpdateParameters"); 675 return; 676 } 677 678 if (!json_util::readJsonObject( 679 *obj, asyncResp->res, "Targets", targets, 680 "@Redfish.OperationApplyTime", applyTime)) 681 { 682 return; 683 } 684 if (targets.size() != 1) 685 { 686 messages::propertyValueFormatError(asyncResp->res, targets, 687 "Targets"); 688 return; 689 } 690 if (targets[0] != "/redfish/v1/Managers/bmc") 691 { 692 messages::propertyValueNotInList(asyncResp->res, targets[0], 693 "Targets/0"); 694 return; 695 } 696 targetFound = true; 697 } 698 else if (param.second == "UpdateFile") 699 { 700 uploadData = &(formpart.content); 701 } 702 } 703 } 704 705 if (uploadData == nullptr) 706 { 707 BMCWEB_LOG_ERROR("Upload data is NULL"); 708 messages::propertyMissing(asyncResp->res, "UpdateFile"); 709 return; 710 } 711 if (!targetFound) 712 { 713 messages::propertyMissing(asyncResp->res, "targets"); 714 return; 715 } 716 717 setApplyTime(asyncResp, *applyTime); 718 719 uploadImageFile(asyncResp->res, *uploadData); 720 } 721 722 inline void 723 handleUpdateServicePost(App& app, const crow::Request& req, 724 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 725 { 726 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 727 { 728 return; 729 } 730 std::string_view contentType = req.getHeaderValue("Content-Type"); 731 732 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); 733 734 // Make sure that content type is application/octet-stream or 735 // multipart/form-data 736 if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) 737 { 738 // Setup callback for when new software detected 739 monitorForSoftwareAvailable(asyncResp, req, 740 "/redfish/v1/UpdateService"); 741 742 uploadImageFile(asyncResp->res, req.body()); 743 } 744 else if (contentType.starts_with("multipart/form-data")) 745 { 746 MultipartParser parser; 747 748 // Setup callback for when new software detected 749 monitorForSoftwareAvailable(asyncResp, req, 750 "/redfish/v1/UpdateService"); 751 752 ParserError ec = parser.parse(req); 753 if (ec != ParserError::PARSER_SUCCESS) 754 { 755 // handle error 756 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", 757 static_cast<int>(ec)); 758 messages::internalError(asyncResp->res); 759 return; 760 } 761 updateMultipartContext(asyncResp, parser); 762 } 763 else 764 { 765 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 766 asyncResp->res.result(boost::beast::http::status::bad_request); 767 } 768 } 769 770 inline void 771 handleUpdateServiceGet(App& app, const crow::Request& req, 772 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 773 { 774 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 775 { 776 return; 777 } 778 asyncResp->res.jsonValue["@odata.type"] = 779 "#UpdateService.v1_11_1.UpdateService"; 780 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 781 asyncResp->res.jsonValue["Id"] = "UpdateService"; 782 asyncResp->res.jsonValue["Description"] = "Service for Software Update"; 783 asyncResp->res.jsonValue["Name"] = "Update Service"; 784 785 asyncResp->res.jsonValue["HttpPushUri"] = 786 "/redfish/v1/UpdateService/update"; 787 asyncResp->res.jsonValue["MultipartHttpPushUri"] = 788 "/redfish/v1/UpdateService/update"; 789 790 // UpdateService cannot be disabled 791 asyncResp->res.jsonValue["ServiceEnabled"] = true; 792 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 793 "/redfish/v1/UpdateService/FirmwareInventory"; 794 // Get the MaxImageSizeBytes 795 asyncResp->res.jsonValue["MaxImageSizeBytes"] = bmcwebHttpReqBodyLimitMb * 796 1024 * 1024; 797 798 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE 799 // Update Actions object. 800 nlohmann::json& updateSvcSimpleUpdate = 801 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 802 updateSvcSimpleUpdate["target"] = 803 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 804 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = { 805 "TFTP"}; 806 #endif 807 // Get the current ApplyTime value 808 sdbusplus::asio::getProperty<std::string>( 809 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 810 "/xyz/openbmc_project/software/apply_time", 811 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime", 812 [asyncResp](const boost::system::error_code& ec, 813 const std::string& applyTime) { 814 if (ec) 815 { 816 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 817 messages::internalError(asyncResp->res); 818 return; 819 } 820 821 // Store the ApplyTime Value 822 if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 823 "RequestedApplyTimes.Immediate") 824 { 825 asyncResp->res.jsonValue["HttpPushUriOptions"] 826 ["HttpPushUriApplyTime"]["ApplyTime"] = 827 "Immediate"; 828 } 829 else if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 830 "RequestedApplyTimes.OnReset") 831 { 832 asyncResp->res.jsonValue["HttpPushUriOptions"] 833 ["HttpPushUriApplyTime"]["ApplyTime"] = 834 "OnReset"; 835 } 836 }); 837 } 838 839 inline void handleUpdateServicePatch( 840 App& app, const crow::Request& req, 841 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 842 { 843 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 844 { 845 return; 846 } 847 BMCWEB_LOG_DEBUG("doPatch..."); 848 849 std::optional<std::string> applyTime; 850 if (!json_util::readJsonPatch( 851 req, asyncResp->res, 852 "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime)) 853 { 854 return; 855 } 856 857 if (applyTime) 858 { 859 setApplyTime(asyncResp, *applyTime); 860 } 861 } 862 863 inline void handleUpdateServiceFirmwareInventoryCollectionGet( 864 App& app, const crow::Request& req, 865 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 866 { 867 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 868 { 869 return; 870 } 871 asyncResp->res.jsonValue["@odata.type"] = 872 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 873 asyncResp->res.jsonValue["@odata.id"] = 874 "/redfish/v1/UpdateService/FirmwareInventory"; 875 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; 876 const std::array<const std::string_view, 1> iface = { 877 "xyz.openbmc_project.Software.Version"}; 878 879 redfish::collection_util::getCollectionMembers( 880 asyncResp, 881 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), iface, 882 "/xyz/openbmc_project/software"); 883 } 884 885 /* Fill related item links (i.e. bmc, bios) in for inventory */ 886 inline void getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 887 const std::string& purpose) 888 { 889 if (purpose == sw_util::bmcPurpose) 890 { 891 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 892 nlohmann::json::object_t item; 893 item["@odata.id"] = "/redfish/v1/Managers/bmc"; 894 relatedItem.emplace_back(std::move(item)); 895 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 896 relatedItem.size(); 897 } 898 else if (purpose == sw_util::biosPurpose) 899 { 900 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 901 nlohmann::json::object_t item; 902 item["@odata.id"] = "/redfish/v1/Systems/system/Bios"; 903 relatedItem.emplace_back(std::move(item)); 904 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 905 relatedItem.size(); 906 } 907 else 908 { 909 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); 910 } 911 } 912 913 inline void 914 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 915 const std::string& service, const std::string& path, 916 const std::string& swId) 917 { 918 sdbusplus::asio::getAllProperties( 919 *crow::connections::systemBus, service, path, 920 "xyz.openbmc_project.Software.Version", 921 [asyncResp, 922 swId](const boost::system::error_code& ec, 923 const dbus::utility::DBusPropertiesMap& propertiesList) { 924 if (ec) 925 { 926 messages::internalError(asyncResp->res); 927 return; 928 } 929 930 const std::string* swInvPurpose = nullptr; 931 const std::string* version = nullptr; 932 933 const bool success = sdbusplus::unpackPropertiesNoThrow( 934 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 935 swInvPurpose, "Version", version); 936 937 if (!success) 938 { 939 messages::internalError(asyncResp->res); 940 return; 941 } 942 943 if (swInvPurpose == nullptr) 944 { 945 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!"); 946 messages::internalError(asyncResp->res); 947 return; 948 } 949 950 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose); 951 952 if (version == nullptr) 953 { 954 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!"); 955 956 messages::internalError(asyncResp->res); 957 958 return; 959 } 960 asyncResp->res.jsonValue["Version"] = *version; 961 asyncResp->res.jsonValue["Id"] = swId; 962 963 // swInvPurpose is of format: 964 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 965 // Translate this to "ABC image" 966 size_t endDesc = swInvPurpose->rfind('.'); 967 if (endDesc == std::string::npos) 968 { 969 messages::internalError(asyncResp->res); 970 return; 971 } 972 endDesc++; 973 if (endDesc >= swInvPurpose->size()) 974 { 975 messages::internalError(asyncResp->res); 976 return; 977 } 978 979 std::string formatDesc = swInvPurpose->substr(endDesc); 980 asyncResp->res.jsonValue["Description"] = formatDesc + " image"; 981 getRelatedItems(asyncResp, *swInvPurpose); 982 }); 983 } 984 985 inline void handleUpdateServiceFirmwareInventoryGet( 986 App& app, const crow::Request& req, 987 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 988 const std::string& param) 989 { 990 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 991 { 992 return; 993 } 994 std::shared_ptr<std::string> swId = std::make_shared<std::string>(param); 995 996 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 997 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); 998 999 constexpr std::array<std::string_view, 1> interfaces = { 1000 "xyz.openbmc_project.Software.Version"}; 1001 dbus::utility::getSubTree( 1002 "/", 0, interfaces, 1003 [asyncResp, 1004 swId](const boost::system::error_code& ec, 1005 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1006 BMCWEB_LOG_DEBUG("doGet callback..."); 1007 if (ec) 1008 { 1009 messages::internalError(asyncResp->res); 1010 return; 1011 } 1012 1013 // Ensure we find our input swId, otherwise return an error 1014 bool found = false; 1015 for (const std::pair< 1016 std::string, 1017 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1018 obj : subtree) 1019 { 1020 if (!obj.first.ends_with(*swId)) 1021 { 1022 continue; 1023 } 1024 1025 if (obj.second.empty()) 1026 { 1027 continue; 1028 } 1029 1030 found = true; 1031 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first); 1032 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, 1033 *swId); 1034 } 1035 if (!found) 1036 { 1037 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId); 1038 messages::resourceMissingAtURI( 1039 asyncResp->res, 1040 boost::urls::format( 1041 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId)); 1042 return; 1043 } 1044 asyncResp->res.jsonValue["@odata.type"] = 1045 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 1046 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 1047 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 1048 1049 asyncResp->res.jsonValue["Updateable"] = false; 1050 sw_util::getSwUpdatableStatus(asyncResp, swId); 1051 }); 1052 } 1053 1054 inline void requestRoutesUpdateService(App& app) 1055 { 1056 BMCWEB_ROUTE( 1057 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 1058 .privileges(redfish::privileges::postUpdateService) 1059 .methods(boost::beast::http::verb::post)(std::bind_front( 1060 handleUpdateServiceSimpleUpdateAction, std::ref(app))); 1061 1062 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 1063 .privileges(redfish::privileges::getSoftwareInventory) 1064 .methods(boost::beast::http::verb::get)(std::bind_front( 1065 handleUpdateServiceFirmwareInventoryGet, std::ref(app))); 1066 1067 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1068 .privileges(redfish::privileges::getUpdateService) 1069 .methods(boost::beast::http::verb::get)( 1070 std::bind_front(handleUpdateServiceGet, std::ref(app))); 1071 1072 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 1073 .privileges(redfish::privileges::patchUpdateService) 1074 .methods(boost::beast::http::verb::patch)( 1075 std::bind_front(handleUpdateServicePatch, std::ref(app))); 1076 1077 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 1078 .privileges(redfish::privileges::postUpdateService) 1079 .methods(boost::beast::http::verb::post)( 1080 std::bind_front(handleUpdateServicePost, std::ref(app))); 1081 1082 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 1083 .privileges(redfish::privileges::getSoftwareInventoryCollection) 1084 .methods(boost::beast::http::verb::get)(std::bind_front( 1085 handleUpdateServiceFirmwareInventoryCollectionGet, std::ref(app))); 1086 } 1087 1088 } // namespace redfish 1089