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