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