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