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