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