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