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