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