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