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 "node.hpp" 19 20 #include <boost/container/flat_map.hpp> 21 #include <utils/fw_utils.hpp> 22 #include <variant> 23 24 namespace redfish 25 { 26 27 // Match signals added on software path 28 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 29 // Only allow one update at a time 30 static bool fwUpdateInProgress = false; 31 // Timer for software available 32 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; 33 34 static void cleanUp() 35 { 36 fwUpdateInProgress = false; 37 fwUpdateMatcher = nullptr; 38 } 39 static void activateImage(const std::string &objPath, 40 const std::string &service) 41 { 42 BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service; 43 crow::connections::systemBus->async_method_call( 44 [](const boost::system::error_code error_code) { 45 if (error_code) 46 { 47 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 48 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); 49 } 50 }, 51 service, objPath, "org.freedesktop.DBus.Properties", "Set", 52 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 53 std::variant<std::string>( 54 "xyz.openbmc_project.Software.Activation.RequestedActivations." 55 "Active")); 56 } 57 58 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 59 // then no asyncResp updates will occur 60 static void softwareInterfaceAdded(std::shared_ptr<AsyncResp> asyncResp, 61 sdbusplus::message::message &m) 62 { 63 std::vector<std::pair< 64 std::string, 65 std::vector<std::pair<std::string, std::variant<std::string>>>>> 66 interfacesProperties; 67 68 sdbusplus::message::object_path objPath; 69 70 m.read(objPath, interfacesProperties); 71 72 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 73 for (auto &interface : interfacesProperties) 74 { 75 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 76 77 if (interface.first == "xyz.openbmc_project.Software.Activation") 78 { 79 // Found our interface, disable callbacks 80 fwUpdateMatcher = nullptr; 81 82 // Retrieve service and activate 83 crow::connections::systemBus->async_method_call( 84 [objPath, asyncResp]( 85 const boost::system::error_code error_code, 86 const std::vector<std::pair< 87 std::string, std::vector<std::string>>> &objInfo) { 88 if (error_code) 89 { 90 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 91 BMCWEB_LOG_DEBUG << "error msg = " 92 << error_code.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 boost::container::flat_map< 133 std::string, std::variant<std::string>> 134 values; 135 msg.read(iface, values); 136 auto findActivation = 137 values.find("Activation"); 138 if (findActivation == values.end()) 139 { 140 return !task::completed; 141 } 142 std::string *state = 143 std::get_if<std::string>( 144 &(findActivation->second)); 145 146 if (state == nullptr) 147 { 148 taskData->messages.emplace_back( 149 messages::internalError()); 150 return task::completed; 151 } 152 153 if (boost::ends_with(*state, "Invalid") || 154 boost::ends_with(*state, "Failed")) 155 { 156 taskData->state = "Exception"; 157 taskData->status = "Warning"; 158 taskData->messages.emplace_back( 159 messages::invalidObject( 160 "/redfish/v1/UpdateService/")); 161 return task::completed; 162 } 163 164 if (boost::ends_with(*state, "Staged")) 165 { 166 taskData->state = "Pending"; 167 return !task::completed; 168 } 169 170 if (boost::ends_with(*state, "Active")) 171 { 172 taskData->messages.emplace_back( 173 messages::success()); 174 taskData->state = "Completed"; 175 return task::completed; 176 } 177 178 // as firmware update often results in a 179 // reboot, the task may never "complete" 180 // unless it is an error 181 182 return !task::completed; 183 }, 184 "type='signal',interface='org.freedesktop.DBus." 185 "Properties'," 186 "member='PropertiesChanged',arg0='xyz.openbmc_" 187 "project.Software.Activation',path='" + 188 objPath.str + "'"); 189 task->startTimer(std::chrono::minutes(5)); 190 task->populateResp(asyncResp->res); 191 } 192 fwUpdateInProgress = false; 193 }, 194 "xyz.openbmc_project.ObjectMapper", 195 "/xyz/openbmc_project/object_mapper", 196 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, 197 std::array<const char *, 1>{ 198 "xyz.openbmc_project.Software.Activation"}); 199 } 200 } 201 } 202 203 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 204 // then no asyncResp updates will occur 205 static void monitorForSoftwareAvailable(std::shared_ptr<AsyncResp> asyncResp, 206 const crow::Request &req, 207 int timeoutTimeSeconds = 5) 208 { 209 // Only allow one FW update at a time 210 if (fwUpdateInProgress != false) 211 { 212 if (asyncResp) 213 { 214 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 215 } 216 return; 217 } 218 219 fwAvailableTimer = 220 std::make_unique<boost::asio::steady_timer>(*req.ioService); 221 222 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 223 224 fwAvailableTimer->async_wait( 225 [asyncResp](const boost::system::error_code &ec) { 226 cleanUp(); 227 if (ec == boost::asio::error::operation_aborted) 228 { 229 // expected, we were canceled before the timer completed. 230 return; 231 } 232 BMCWEB_LOG_ERROR 233 << "Timed out waiting for firmware object being created"; 234 BMCWEB_LOG_ERROR 235 << "FW image may has already been uploaded to server"; 236 if (ec) 237 { 238 BMCWEB_LOG_ERROR << "Async_wait failed" << ec; 239 return; 240 } 241 if (asyncResp) 242 { 243 redfish::messages::internalError(asyncResp->res); 244 } 245 }); 246 247 auto callback = [asyncResp](sdbusplus::message::message &m) { 248 BMCWEB_LOG_DEBUG << "Match fired"; 249 softwareInterfaceAdded(asyncResp, m); 250 }; 251 252 fwUpdateInProgress = true; 253 254 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>( 255 *crow::connections::systemBus, 256 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 257 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 258 callback); 259 } 260 261 /** 262 * UpdateServiceActionsSimpleUpdate class supports handle POST method for 263 * SimpleUpdate action. 264 */ 265 class UpdateServiceActionsSimpleUpdate : public Node 266 { 267 public: 268 UpdateServiceActionsSimpleUpdate(CrowApp &app) : 269 Node(app, 270 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 271 { 272 entityPrivileges = { 273 {boost::beast::http::verb::get, {{"Login"}}}, 274 {boost::beast::http::verb::head, {{"Login"}}}, 275 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 276 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 277 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 278 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 279 } 280 281 private: 282 void doPost(crow::Response &res, const crow::Request &req, 283 const std::vector<std::string> ¶ms) override 284 { 285 std::optional<std::string> transferProtocol; 286 std::string imageURI; 287 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 288 289 BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost"; 290 291 // User can pass in both TransferProtocol and ImageURI parameters or 292 // they can pass in just the ImageURI with the transfer protocl embedded 293 // within it. 294 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 295 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 296 297 if (!json_util::readJson(req, asyncResp->res, "TransferProtocol", 298 transferProtocol, "ImageURI", imageURI)) 299 { 300 BMCWEB_LOG_DEBUG 301 << "Missing TransferProtocol or ImageURI parameter"; 302 return; 303 } 304 if (!transferProtocol) 305 { 306 // Must be option 2 307 // Verify ImageURI has transfer protocol in it 308 size_t separator = imageURI.find(":"); 309 if ((separator == std::string::npos) || 310 ((separator + 1) > imageURI.size())) 311 { 312 messages::actionParameterValueTypeError( 313 asyncResp->res, imageURI, "ImageURI", 314 "UpdateService.SimpleUpdate"); 315 BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: " 316 << imageURI; 317 return; 318 } 319 transferProtocol = imageURI.substr(0, separator); 320 // Ensure protocol is upper case for a common comparison path below 321 boost::to_upper(*transferProtocol); 322 BMCWEB_LOG_DEBUG << "Encoded transfer protocol " 323 << *transferProtocol; 324 325 // Adjust imageURI to not have the protocol on it for parsing 326 // below 327 // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin 328 imageURI = imageURI.substr(separator + 3); 329 BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI; 330 } 331 332 // OpenBMC currently only supports TFTP 333 if (*transferProtocol != "TFTP") 334 { 335 messages::actionParameterNotSupported(asyncResp->res, 336 "TransferProtocol", 337 "UpdateService.SimpleUpdate"); 338 BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: " 339 << *transferProtocol; 340 return; 341 } 342 343 // Format should be <IP or Hostname>/<file> for imageURI 344 size_t separator = imageURI.find("/"); 345 if ((separator == std::string::npos) || 346 ((separator + 1) > imageURI.size())) 347 { 348 messages::actionParameterValueTypeError( 349 asyncResp->res, imageURI, "ImageURI", 350 "UpdateService.SimpleUpdate"); 351 BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI; 352 return; 353 } 354 355 std::string tftpServer = imageURI.substr(0, separator); 356 std::string fwFile = imageURI.substr(separator + 1); 357 BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile; 358 359 // Setup callback for when new software detected 360 // Give TFTP 2 minutes to complete 361 monitorForSoftwareAvailable(nullptr, req, 120); 362 363 // TFTP can take up to 2 minutes depending on image size and 364 // connection speed. Return to caller as soon as the TFTP operation 365 // has been started. The callback above will ensure the activate 366 // is started once the download has completed 367 redfish::messages::success(asyncResp->res); 368 369 // Call TFTP service 370 crow::connections::systemBus->async_method_call( 371 [](const boost::system::error_code ec) { 372 if (ec) 373 { 374 // messages::internalError(asyncResp->res); 375 cleanUp(); 376 BMCWEB_LOG_DEBUG << "error_code = " << ec; 377 BMCWEB_LOG_DEBUG << "error msg = " << ec.message(); 378 } 379 else 380 { 381 BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success"; 382 } 383 }, 384 "xyz.openbmc_project.Software.Download", 385 "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP", 386 "DownloadViaTFTP", fwFile, tftpServer); 387 388 BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost"; 389 } 390 }; 391 392 class UpdateService : public Node 393 { 394 public: 395 UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/") 396 { 397 entityPrivileges = { 398 {boost::beast::http::verb::get, {{"Login"}}}, 399 {boost::beast::http::verb::head, {{"Login"}}}, 400 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 401 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 402 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 403 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 404 } 405 406 private: 407 void doGet(crow::Response &res, const crow::Request &req, 408 const std::vector<std::string> ¶ms) override 409 { 410 std::shared_ptr<AsyncResp> aResp = std::make_shared<AsyncResp>(res); 411 res.jsonValue["@odata.type"] = "#UpdateService.v1_4_0.UpdateService"; 412 res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 413 res.jsonValue["Id"] = "UpdateService"; 414 res.jsonValue["Description"] = "Service for Software Update"; 415 res.jsonValue["Name"] = "Update Service"; 416 res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; 417 // UpdateService cannot be disabled 418 res.jsonValue["ServiceEnabled"] = true; 419 res.jsonValue["FirmwareInventory"] = { 420 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}}; 421 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE 422 // Update Actions object. 423 nlohmann::json &updateSvcSimpleUpdate = 424 res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 425 updateSvcSimpleUpdate["target"] = 426 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 427 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = { 428 "TFTP"}; 429 #endif 430 // Get the current ApplyTime value 431 crow::connections::systemBus->async_method_call( 432 [aResp](const boost::system::error_code ec, 433 const std::variant<std::string> &applyTime) { 434 if (ec) 435 { 436 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 437 messages::internalError(aResp->res); 438 return; 439 } 440 441 const std::string *s = std::get_if<std::string>(&applyTime); 442 if (s == nullptr) 443 { 444 return; 445 } 446 // Store the ApplyTime Value 447 if (*s == "xyz.openbmc_project.Software.ApplyTime." 448 "RequestedApplyTimes.Immediate") 449 { 450 aResp->res.jsonValue["HttpPushUriOptions"] 451 ["HttpPushUriApplyTime"]["ApplyTime"] = 452 "Immediate"; 453 } 454 else if (*s == "xyz.openbmc_project.Software.ApplyTime." 455 "RequestedApplyTimes.OnReset") 456 { 457 aResp->res.jsonValue["HttpPushUriOptions"] 458 ["HttpPushUriApplyTime"]["ApplyTime"] = 459 "OnReset"; 460 } 461 }, 462 "xyz.openbmc_project.Settings", 463 "/xyz/openbmc_project/software/apply_time", 464 "org.freedesktop.DBus.Properties", "Get", 465 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime"); 466 } 467 468 void doPatch(crow::Response &res, const crow::Request &req, 469 const std::vector<std::string> ¶ms) override 470 { 471 BMCWEB_LOG_DEBUG << "doPatch..."; 472 473 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 474 475 std::optional<nlohmann::json> pushUriOptions; 476 if (!json_util::readJson(req, res, "HttpPushUriOptions", 477 pushUriOptions)) 478 { 479 return; 480 } 481 482 if (pushUriOptions) 483 { 484 std::optional<nlohmann::json> pushUriApplyTime; 485 if (!json_util::readJson(*pushUriOptions, res, 486 "HttpPushUriApplyTime", pushUriApplyTime)) 487 { 488 return; 489 } 490 491 if (pushUriApplyTime) 492 { 493 std::optional<std::string> applyTime; 494 if (!json_util::readJson(*pushUriApplyTime, res, "ApplyTime", 495 applyTime)) 496 { 497 return; 498 } 499 500 if (applyTime) 501 { 502 std::string applyTimeNewVal; 503 if (applyTime == "Immediate") 504 { 505 applyTimeNewVal = 506 "xyz.openbmc_project.Software.ApplyTime." 507 "RequestedApplyTimes.Immediate"; 508 } 509 else if (applyTime == "OnReset") 510 { 511 applyTimeNewVal = 512 "xyz.openbmc_project.Software.ApplyTime." 513 "RequestedApplyTimes.OnReset"; 514 } 515 else 516 { 517 BMCWEB_LOG_INFO 518 << "ApplyTime value is not in the list of " 519 "acceptable values"; 520 messages::propertyValueNotInList( 521 asyncResp->res, *applyTime, "ApplyTime"); 522 return; 523 } 524 525 // Set the requested image apply time value 526 crow::connections::systemBus->async_method_call( 527 [asyncResp](const boost::system::error_code ec) { 528 if (ec) 529 { 530 BMCWEB_LOG_ERROR << "D-Bus responses error: " 531 << ec; 532 messages::internalError(asyncResp->res); 533 return; 534 } 535 messages::success(asyncResp->res); 536 }, 537 "xyz.openbmc_project.Settings", 538 "/xyz/openbmc_project/software/apply_time", 539 "org.freedesktop.DBus.Properties", "Set", 540 "xyz.openbmc_project.Software.ApplyTime", 541 "RequestedApplyTime", 542 std::variant<std::string>{applyTimeNewVal}); 543 } 544 } 545 } 546 } 547 548 void doPost(crow::Response &res, const crow::Request &req, 549 const std::vector<std::string> ¶ms) override 550 { 551 BMCWEB_LOG_DEBUG << "doPost..."; 552 553 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 554 555 // Setup callback for when new software detected 556 monitorForSoftwareAvailable(asyncResp, req); 557 558 std::string filepath( 559 "/tmp/images/" + 560 boost::uuids::to_string(boost::uuids::random_generator()())); 561 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 562 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 563 std::ofstream::trunc); 564 out << req.body; 565 out.close(); 566 BMCWEB_LOG_DEBUG << "file upload complete!!"; 567 } 568 }; 569 570 class SoftwareInventoryCollection : public Node 571 { 572 public: 573 template <typename CrowApp> 574 SoftwareInventoryCollection(CrowApp &app) : 575 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") 576 { 577 entityPrivileges = { 578 {boost::beast::http::verb::get, {{"Login"}}}, 579 {boost::beast::http::verb::head, {{"Login"}}}, 580 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 581 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 582 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 583 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 584 } 585 586 private: 587 void doGet(crow::Response &res, const crow::Request &req, 588 const std::vector<std::string> ¶ms) override 589 { 590 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 591 res.jsonValue["@odata.type"] = 592 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 593 res.jsonValue["@odata.id"] = 594 "/redfish/v1/UpdateService/FirmwareInventory"; 595 res.jsonValue["Name"] = "Software Inventory Collection"; 596 597 crow::connections::systemBus->async_method_call( 598 [asyncResp]( 599 const boost::system::error_code ec, 600 const std::vector<std::pair< 601 std::string, std::vector<std::pair< 602 std::string, std::vector<std::string>>>>> 603 &subtree) { 604 if (ec) 605 { 606 messages::internalError(asyncResp->res); 607 return; 608 } 609 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 610 asyncResp->res.jsonValue["Members@odata.count"] = 0; 611 612 for (auto &obj : subtree) 613 { 614 // if can't parse fw id then return 615 std::size_t idPos; 616 if ((idPos = obj.first.rfind("/")) == std::string::npos) 617 { 618 messages::internalError(asyncResp->res); 619 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; 620 return; 621 } 622 std::string swId = obj.first.substr(idPos + 1); 623 624 nlohmann::json &members = 625 asyncResp->res.jsonValue["Members"]; 626 members.push_back( 627 {{"@odata.id", "/redfish/v1/UpdateService/" 628 "FirmwareInventory/" + 629 swId}}); 630 asyncResp->res.jsonValue["Members@odata.count"] = 631 members.size(); 632 } 633 }, 634 // Note that only firmware levels associated with a device are 635 // stored under /xyz/openbmc_project/software therefore to ensure 636 // only real FirmwareInventory items are returned, this full object 637 // path must be used here as input to mapper 638 "xyz.openbmc_project.ObjectMapper", 639 "/xyz/openbmc_project/object_mapper", 640 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 641 "/xyz/openbmc_project/software", static_cast<int32_t>(0), 642 std::array<const char *, 1>{ 643 "xyz.openbmc_project.Software.Version"}); 644 } 645 }; 646 647 class SoftwareInventory : public Node 648 { 649 public: 650 template <typename CrowApp> 651 SoftwareInventory(CrowApp &app) : 652 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 653 std::string()) 654 { 655 entityPrivileges = { 656 {boost::beast::http::verb::get, {{"Login"}}}, 657 {boost::beast::http::verb::head, {{"Login"}}}, 658 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 659 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 660 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 661 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 662 } 663 664 private: 665 /* Fill related item links (i.e. bmc, bios) in for inventory */ 666 static void getRelatedItems(std::shared_ptr<AsyncResp> aResp, 667 const std::string &purpose) 668 { 669 if (purpose == fw_util::bmcPurpose) 670 { 671 nlohmann::json &members = aResp->res.jsonValue["RelatedItem"]; 672 members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}}); 673 aResp->res.jsonValue["Members@odata.count"] = members.size(); 674 } 675 else if (purpose == fw_util::biosPurpose) 676 { 677 nlohmann::json &members = aResp->res.jsonValue["RelatedItem"]; 678 members.push_back( 679 {{"@odata.id", "/redfish/v1/Systems/system/Bios"}}); 680 aResp->res.jsonValue["Members@odata.count"] = members.size(); 681 } 682 else 683 { 684 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose; 685 } 686 } 687 688 void doGet(crow::Response &res, const crow::Request &req, 689 const std::vector<std::string> ¶ms) override 690 { 691 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 692 693 if (params.size() != 1) 694 { 695 messages::internalError(res); 696 res.end(); 697 return; 698 } 699 700 std::shared_ptr<std::string> swId = 701 std::make_shared<std::string>(params[0]); 702 703 res.jsonValue["@odata.id"] = 704 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 705 706 crow::connections::systemBus->async_method_call( 707 [asyncResp, swId]( 708 const boost::system::error_code ec, 709 const std::vector<std::pair< 710 std::string, std::vector<std::pair< 711 std::string, std::vector<std::string>>>>> 712 &subtree) { 713 BMCWEB_LOG_DEBUG << "doGet callback..."; 714 if (ec) 715 { 716 messages::internalError(asyncResp->res); 717 return; 718 } 719 720 // Ensure we find our input swId, otherwise return an error 721 bool found = false; 722 for (const std::pair< 723 std::string, 724 std::vector< 725 std::pair<std::string, std::vector<std::string>>>> 726 &obj : subtree) 727 { 728 if (boost::ends_with(obj.first, *swId) != true) 729 { 730 continue; 731 } 732 733 if (obj.second.size() < 1) 734 { 735 continue; 736 } 737 738 found = true; 739 fw_util::getFwStatus(asyncResp, swId, obj.second[0].first); 740 741 crow::connections::systemBus->async_method_call( 742 [asyncResp, 743 swId](const boost::system::error_code error_code, 744 const boost::container::flat_map< 745 std::string, VariantType> &propertiesList) { 746 if (error_code) 747 { 748 messages::internalError(asyncResp->res); 749 return; 750 } 751 boost::container::flat_map< 752 std::string, VariantType>::const_iterator it = 753 propertiesList.find("Purpose"); 754 if (it == propertiesList.end()) 755 { 756 BMCWEB_LOG_DEBUG 757 << "Can't find property \"Purpose\"!"; 758 messages::propertyMissing(asyncResp->res, 759 "Purpose"); 760 return; 761 } 762 const std::string *swInvPurpose = 763 std::get_if<std::string>(&it->second); 764 if (swInvPurpose == nullptr) 765 { 766 BMCWEB_LOG_DEBUG 767 << "wrong types for property\"Purpose\"!"; 768 messages::propertyValueTypeError(asyncResp->res, 769 "", "Purpose"); 770 return; 771 } 772 773 BMCWEB_LOG_DEBUG << "swInvPurpose = " 774 << *swInvPurpose; 775 it = propertiesList.find("Version"); 776 if (it == propertiesList.end()) 777 { 778 BMCWEB_LOG_DEBUG 779 << "Can't find property \"Version\"!"; 780 messages::propertyMissing(asyncResp->res, 781 "Version"); 782 return; 783 } 784 785 BMCWEB_LOG_DEBUG << "Version found!"; 786 787 const std::string *version = 788 std::get_if<std::string>(&it->second); 789 790 if (version == nullptr) 791 { 792 BMCWEB_LOG_DEBUG 793 << "Can't find property \"Version\"!"; 794 795 messages::propertyValueTypeError(asyncResp->res, 796 "", "Version"); 797 return; 798 } 799 asyncResp->res.jsonValue["Version"] = *version; 800 asyncResp->res.jsonValue["Id"] = *swId; 801 802 // swInvPurpose is of format: 803 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 804 // Translate this to "ABC image" 805 size_t endDesc = swInvPurpose->rfind("."); 806 if (endDesc == std::string::npos) 807 { 808 messages::internalError(asyncResp->res); 809 return; 810 } 811 endDesc++; 812 if (endDesc >= swInvPurpose->size()) 813 { 814 messages::internalError(asyncResp->res); 815 return; 816 } 817 818 std::string formatDesc = 819 swInvPurpose->substr(endDesc); 820 asyncResp->res.jsonValue["Description"] = 821 formatDesc + " image"; 822 getRelatedItems(asyncResp, *swInvPurpose); 823 }, 824 obj.second[0].first, obj.first, 825 "org.freedesktop.DBus.Properties", "GetAll", 826 "xyz.openbmc_project.Software.Version"); 827 } 828 if (!found) 829 { 830 BMCWEB_LOG_ERROR << "Input swID " + *swId + " not found!"; 831 messages::resourceMissingAtURI( 832 asyncResp->res, 833 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId); 834 return; 835 } 836 asyncResp->res.jsonValue["@odata.type"] = 837 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 838 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 839 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 840 841 asyncResp->res.jsonValue["Updateable"] = false; 842 fw_util::getFwUpdateableStatus(asyncResp, swId); 843 }, 844 "xyz.openbmc_project.ObjectMapper", 845 "/xyz/openbmc_project/object_mapper", 846 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 847 static_cast<int32_t>(0), 848 std::array<const char *, 1>{ 849 "xyz.openbmc_project.Software.Version"}); 850 } 851 }; 852 853 } // namespace redfish 854