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