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