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