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