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(App& 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(App& 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 SoftwareInventoryCollection(App& app) : 689 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") 690 { 691 entityPrivileges = { 692 {boost::beast::http::verb::get, {{"Login"}}}, 693 {boost::beast::http::verb::head, {{"Login"}}}, 694 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 695 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 696 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 697 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 698 } 699 700 private: 701 void doGet(crow::Response& res, const crow::Request& req, 702 const std::vector<std::string>& params) override 703 { 704 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 705 res.jsonValue["@odata.type"] = 706 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 707 res.jsonValue["@odata.id"] = 708 "/redfish/v1/UpdateService/FirmwareInventory"; 709 res.jsonValue["Name"] = "Software Inventory Collection"; 710 711 crow::connections::systemBus->async_method_call( 712 [asyncResp]( 713 const boost::system::error_code ec, 714 const std::vector<std::pair< 715 std::string, std::vector<std::pair< 716 std::string, std::vector<std::string>>>>>& 717 subtree) { 718 if (ec) 719 { 720 messages::internalError(asyncResp->res); 721 return; 722 } 723 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 724 asyncResp->res.jsonValue["Members@odata.count"] = 0; 725 726 for (auto& obj : subtree) 727 { 728 // if can't parse fw id then return 729 std::size_t idPos; 730 if ((idPos = obj.first.rfind("/")) == std::string::npos) 731 { 732 messages::internalError(asyncResp->res); 733 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; 734 return; 735 } 736 std::string swId = obj.first.substr(idPos + 1); 737 738 nlohmann::json& members = 739 asyncResp->res.jsonValue["Members"]; 740 members.push_back( 741 {{"@odata.id", "/redfish/v1/UpdateService/" 742 "FirmwareInventory/" + 743 swId}}); 744 asyncResp->res.jsonValue["Members@odata.count"] = 745 members.size(); 746 } 747 }, 748 // Note that only firmware levels associated with a device are 749 // stored under /xyz/openbmc_project/software therefore to ensure 750 // only real FirmwareInventory items are returned, this full object 751 // path must be used here as input to mapper 752 "xyz.openbmc_project.ObjectMapper", 753 "/xyz/openbmc_project/object_mapper", 754 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 755 "/xyz/openbmc_project/software", static_cast<int32_t>(0), 756 std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"}); 757 } 758 }; 759 760 class SoftwareInventory : public Node 761 { 762 public: 763 SoftwareInventory(App& app) : 764 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 765 std::string()) 766 { 767 entityPrivileges = { 768 {boost::beast::http::verb::get, {{"Login"}}}, 769 {boost::beast::http::verb::head, {{"Login"}}}, 770 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 771 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 772 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 773 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 774 } 775 776 private: 777 /* Fill related item links (i.e. bmc, bios) in for inventory */ 778 static void getRelatedItems(std::shared_ptr<AsyncResp> aResp, 779 const std::string& purpose) 780 { 781 if (purpose == fw_util::bmcPurpose) 782 { 783 nlohmann::json& members = aResp->res.jsonValue["RelatedItem"]; 784 members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}}); 785 aResp->res.jsonValue["Members@odata.count"] = members.size(); 786 } 787 else if (purpose == fw_util::biosPurpose) 788 { 789 nlohmann::json& members = aResp->res.jsonValue["RelatedItem"]; 790 members.push_back( 791 {{"@odata.id", "/redfish/v1/Systems/system/Bios"}}); 792 aResp->res.jsonValue["Members@odata.count"] = members.size(); 793 } 794 else 795 { 796 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose; 797 } 798 } 799 800 void doGet(crow::Response& res, const crow::Request& req, 801 const std::vector<std::string>& params) override 802 { 803 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 804 805 if (params.size() != 1) 806 { 807 messages::internalError(res); 808 res.end(); 809 return; 810 } 811 812 std::shared_ptr<std::string> swId = 813 std::make_shared<std::string>(params[0]); 814 815 res.jsonValue["@odata.id"] = 816 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 817 818 crow::connections::systemBus->async_method_call( 819 [asyncResp, swId]( 820 const boost::system::error_code ec, 821 const std::vector<std::pair< 822 std::string, std::vector<std::pair< 823 std::string, std::vector<std::string>>>>>& 824 subtree) { 825 BMCWEB_LOG_DEBUG << "doGet callback..."; 826 if (ec) 827 { 828 messages::internalError(asyncResp->res); 829 return; 830 } 831 832 // Ensure we find our input swId, otherwise return an error 833 bool found = false; 834 for (const std::pair< 835 std::string, 836 std::vector< 837 std::pair<std::string, std::vector<std::string>>>>& 838 obj : subtree) 839 { 840 if (boost::ends_with(obj.first, *swId) != true) 841 { 842 continue; 843 } 844 845 if (obj.second.size() < 1) 846 { 847 continue; 848 } 849 850 found = true; 851 fw_util::getFwStatus(asyncResp, swId, obj.second[0].first); 852 853 crow::connections::systemBus->async_method_call( 854 [asyncResp, 855 swId](const boost::system::error_code error_code, 856 const boost::container::flat_map< 857 std::string, VariantType>& propertiesList) { 858 if (error_code) 859 { 860 messages::internalError(asyncResp->res); 861 return; 862 } 863 boost::container::flat_map< 864 std::string, VariantType>::const_iterator it = 865 propertiesList.find("Purpose"); 866 if (it == propertiesList.end()) 867 { 868 BMCWEB_LOG_DEBUG 869 << "Can't find property \"Purpose\"!"; 870 messages::propertyMissing(asyncResp->res, 871 "Purpose"); 872 return; 873 } 874 const std::string* swInvPurpose = 875 std::get_if<std::string>(&it->second); 876 if (swInvPurpose == nullptr) 877 { 878 BMCWEB_LOG_DEBUG 879 << "wrong types for property\"Purpose\"!"; 880 messages::propertyValueTypeError(asyncResp->res, 881 "", "Purpose"); 882 return; 883 } 884 885 BMCWEB_LOG_DEBUG << "swInvPurpose = " 886 << *swInvPurpose; 887 it = propertiesList.find("Version"); 888 if (it == propertiesList.end()) 889 { 890 BMCWEB_LOG_DEBUG 891 << "Can't find property \"Version\"!"; 892 messages::propertyMissing(asyncResp->res, 893 "Version"); 894 return; 895 } 896 897 BMCWEB_LOG_DEBUG << "Version found!"; 898 899 const std::string* version = 900 std::get_if<std::string>(&it->second); 901 902 if (version == nullptr) 903 { 904 BMCWEB_LOG_DEBUG 905 << "Can't find property \"Version\"!"; 906 907 messages::propertyValueTypeError(asyncResp->res, 908 "", "Version"); 909 return; 910 } 911 asyncResp->res.jsonValue["Version"] = *version; 912 asyncResp->res.jsonValue["Id"] = *swId; 913 914 // swInvPurpose is of format: 915 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 916 // Translate this to "ABC image" 917 size_t endDesc = swInvPurpose->rfind("."); 918 if (endDesc == std::string::npos) 919 { 920 messages::internalError(asyncResp->res); 921 return; 922 } 923 endDesc++; 924 if (endDesc >= swInvPurpose->size()) 925 { 926 messages::internalError(asyncResp->res); 927 return; 928 } 929 930 std::string formatDesc = 931 swInvPurpose->substr(endDesc); 932 asyncResp->res.jsonValue["Description"] = 933 formatDesc + " image"; 934 getRelatedItems(asyncResp, *swInvPurpose); 935 }, 936 obj.second[0].first, obj.first, 937 "org.freedesktop.DBus.Properties", "GetAll", 938 "xyz.openbmc_project.Software.Version"); 939 } 940 if (!found) 941 { 942 BMCWEB_LOG_ERROR << "Input swID " + *swId + " not found!"; 943 messages::resourceMissingAtURI( 944 asyncResp->res, 945 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId); 946 return; 947 } 948 asyncResp->res.jsonValue["@odata.type"] = 949 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 950 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 951 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 952 953 asyncResp->res.jsonValue["Updateable"] = false; 954 fw_util::getFwUpdateableStatus(asyncResp, swId); 955 }, 956 "xyz.openbmc_project.ObjectMapper", 957 "/xyz/openbmc_project/object_mapper", 958 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 959 static_cast<int32_t>(0), 960 std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"}); 961 } 962 }; 963 964 } // namespace redfish 965