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