1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "node.hpp" 19 20 #include <boost/container/flat_map.hpp> 21 #include <utils/fw_utils.hpp> 22 23 #include <variant> 24 25 namespace redfish 26 { 27 28 // Match signals added on software path 29 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 30 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher; 31 // Only allow one update at a time 32 static bool fwUpdateInProgress = false; 33 // Timer for software available 34 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; 35 36 static void cleanUp() 37 { 38 fwUpdateInProgress = false; 39 fwUpdateMatcher = nullptr; 40 fwUpdateErrorMatcher = nullptr; 41 } 42 static void activateImage(const std::string& objPath, 43 const std::string& service) 44 { 45 BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service; 46 crow::connections::systemBus->async_method_call( 47 [](const boost::system::error_code error_code) { 48 if (error_code) 49 { 50 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 51 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); 52 } 53 }, 54 service, objPath, "org.freedesktop.DBus.Properties", "Set", 55 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 56 std::variant<std::string>( 57 "xyz.openbmc_project.Software.Activation.RequestedActivations." 58 "Active")); 59 } 60 61 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 62 // then no asyncResp updates will occur 63 static void softwareInterfaceAdded(const std::shared_ptr<AsyncResp>& asyncResp, 64 sdbusplus::message::message& m, 65 const crow::Request& req) 66 { 67 std::vector<std::pair< 68 std::string, 69 std::vector<std::pair<std::string, std::variant<std::string>>>>> 70 interfacesProperties; 71 72 sdbusplus::message::object_path objPath; 73 74 m.read(objPath, interfacesProperties); 75 76 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 77 for (auto& interface : interfacesProperties) 78 { 79 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 80 81 if (interface.first == "xyz.openbmc_project.Software.Activation") 82 { 83 // Found our interface, disable callbacks 84 fwUpdateMatcher = nullptr; 85 86 // Retrieve service and activate 87 crow::connections::systemBus->async_method_call( 88 [objPath, asyncResp, 89 req](const boost::system::error_code error_code, 90 const std::vector<std::pair< 91 std::string, std::vector<std::string>>>& objInfo) { 92 if (error_code) 93 { 94 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 95 BMCWEB_LOG_DEBUG << "error msg = " 96 << error_code.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->messages.emplace_back( 222 messages::taskProgressChanged( 223 index, static_cast<size_t>( 224 *progress))); 225 226 // if we're getting status updates it's 227 // still alive, update timer 228 taskData->extendTimer( 229 std::chrono::minutes(5)); 230 } 231 232 // as firmware update often results in a 233 // reboot, the task may never "complete" 234 // unless it is an error 235 236 return !task::completed; 237 }, 238 "type='signal',interface='org.freedesktop.DBus." 239 "Properties'," 240 "member='PropertiesChanged',path='" + 241 objPath.str + "'"); 242 task->startTimer(std::chrono::minutes(5)); 243 task->populateResp(asyncResp->res); 244 task->payload.emplace(req); 245 } 246 fwUpdateInProgress = false; 247 }, 248 "xyz.openbmc_project.ObjectMapper", 249 "/xyz/openbmc_project/object_mapper", 250 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, 251 std::array<const char*, 1>{ 252 "xyz.openbmc_project.Software.Activation"}); 253 } 254 } 255 } 256 257 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 258 // then no asyncResp updates will occur 259 static void monitorForSoftwareAvailable( 260 const std::shared_ptr<AsyncResp>& asyncResp, const crow::Request& req, 261 const std::string& url, 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, 336 "xyz.openbmc_project.Software.Image.Error")) 337 { 338 return; 339 } 340 if (*type == 341 "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 342 { 343 redfish::messages::invalidUpload(asyncResp->res, url, 344 "Invalid archive"); 345 } 346 else if (*type == "xyz.openbmc_project.Software.Image.Error." 347 "ManifestFileFailure") 348 { 349 redfish::messages::invalidUpload(asyncResp->res, url, 350 "Invalid manifest"); 351 } 352 else if (*type == 353 "xyz.openbmc_project.Software.Image.Error.ImageFailure") 354 { 355 redfish::messages::invalidUpload(asyncResp->res, url, 356 "Invalid image format"); 357 } 358 else if (*type == 359 "xyz.openbmc_project.Software.Image.Error.BusyFailure") 360 { 361 redfish::messages::resourceExhaustion(asyncResp->res, url); 362 } 363 else 364 { 365 redfish::messages::internalError(asyncResp->res); 366 } 367 fwAvailableTimer = nullptr; 368 }); 369 } 370 371 /** 372 * UpdateServiceActionsSimpleUpdate class supports handle POST method for 373 * SimpleUpdate action. 374 */ 375 class UpdateServiceActionsSimpleUpdate : public Node 376 { 377 public: 378 UpdateServiceActionsSimpleUpdate(App& app) : 379 Node(app, 380 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 381 { 382 entityPrivileges = { 383 {boost::beast::http::verb::get, {{"Login"}}}, 384 {boost::beast::http::verb::head, {{"Login"}}}, 385 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 386 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 387 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 388 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 389 } 390 391 private: 392 void doPost(crow::Response& res, const crow::Request& req, 393 const std::vector<std::string>&) override 394 { 395 std::optional<std::string> transferProtocol; 396 std::string imageURI; 397 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 398 399 BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost"; 400 401 // User can pass in both TransferProtocol and ImageURI parameters or 402 // they can pass in just the ImageURI with the transfer protocol 403 // embedded within it. 404 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 405 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 406 407 if (!json_util::readJson(req, asyncResp->res, "TransferProtocol", 408 transferProtocol, "ImageURI", imageURI)) 409 { 410 BMCWEB_LOG_DEBUG 411 << "Missing TransferProtocol or ImageURI parameter"; 412 return; 413 } 414 if (!transferProtocol) 415 { 416 // Must be option 2 417 // Verify ImageURI has transfer protocol in it 418 size_t separator = imageURI.find(':'); 419 if ((separator == std::string::npos) || 420 ((separator + 1) > imageURI.size())) 421 { 422 messages::actionParameterValueTypeError( 423 asyncResp->res, imageURI, "ImageURI", 424 "UpdateService.SimpleUpdate"); 425 BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: " 426 << imageURI; 427 return; 428 } 429 transferProtocol = imageURI.substr(0, separator); 430 // Ensure protocol is upper case for a common comparison path below 431 boost::to_upper(*transferProtocol); 432 BMCWEB_LOG_DEBUG << "Encoded transfer protocol " 433 << *transferProtocol; 434 435 // Adjust imageURI to not have the protocol on it for parsing 436 // below 437 // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin 438 imageURI = imageURI.substr(separator + 3); 439 BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI; 440 } 441 442 // OpenBMC currently only supports TFTP 443 if (*transferProtocol != "TFTP") 444 { 445 messages::actionParameterNotSupported(asyncResp->res, 446 "TransferProtocol", 447 "UpdateService.SimpleUpdate"); 448 BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: " 449 << *transferProtocol; 450 return; 451 } 452 453 // Format should be <IP or Hostname>/<file> for imageURI 454 size_t separator = imageURI.find('/'); 455 if ((separator == std::string::npos) || 456 ((separator + 1) > imageURI.size())) 457 { 458 messages::actionParameterValueTypeError( 459 asyncResp->res, imageURI, "ImageURI", 460 "UpdateService.SimpleUpdate"); 461 BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI; 462 return; 463 } 464 465 std::string tftpServer = imageURI.substr(0, separator); 466 std::string fwFile = imageURI.substr(separator + 1); 467 BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile; 468 469 // Setup callback for when new software detected 470 // Give TFTP 10 minutes to complete 471 monitorForSoftwareAvailable( 472 nullptr, req, 473 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 474 600); 475 476 // TFTP can take up to 10 minutes depending on image size and 477 // connection speed. Return to caller as soon as the TFTP operation 478 // has been started. The callback above will ensure the activate 479 // is started once the download has completed 480 redfish::messages::success(asyncResp->res); 481 482 // Call TFTP service 483 crow::connections::systemBus->async_method_call( 484 [](const boost::system::error_code ec) { 485 if (ec) 486 { 487 // messages::internalError(asyncResp->res); 488 cleanUp(); 489 BMCWEB_LOG_DEBUG << "error_code = " << ec; 490 BMCWEB_LOG_DEBUG << "error msg = " << ec.message(); 491 } 492 else 493 { 494 BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success"; 495 } 496 }, 497 "xyz.openbmc_project.Software.Download", 498 "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP", 499 "DownloadViaTFTP", fwFile, tftpServer); 500 501 BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost"; 502 } 503 }; 504 505 class UpdateService : public Node 506 { 507 public: 508 UpdateService(App& app) : Node(app, "/redfish/v1/UpdateService/") 509 { 510 entityPrivileges = { 511 {boost::beast::http::verb::get, {{"Login"}}}, 512 {boost::beast::http::verb::head, {{"Login"}}}, 513 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 514 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 515 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 516 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 517 } 518 519 private: 520 void doGet(crow::Response& res, const crow::Request&, 521 const std::vector<std::string>&) override 522 { 523 std::shared_ptr<AsyncResp> aResp = std::make_shared<AsyncResp>(res); 524 res.jsonValue["@odata.type"] = "#UpdateService.v1_4_0.UpdateService"; 525 res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 526 res.jsonValue["Id"] = "UpdateService"; 527 res.jsonValue["Description"] = "Service for Software Update"; 528 res.jsonValue["Name"] = "Update Service"; 529 res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; 530 // UpdateService cannot be disabled 531 res.jsonValue["ServiceEnabled"] = true; 532 res.jsonValue["FirmwareInventory"] = { 533 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}}; 534 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE 535 // Update Actions object. 536 nlohmann::json& updateSvcSimpleUpdate = 537 res.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 [aResp](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(aResp->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 aResp->res.jsonValue["HttpPushUriOptions"] 564 ["HttpPushUriApplyTime"]["ApplyTime"] = 565 "Immediate"; 566 } 567 else if (*s == "xyz.openbmc_project.Software.ApplyTime." 568 "RequestedApplyTimes.OnReset") 569 { 570 aResp->res.jsonValue["HttpPushUriOptions"] 571 ["HttpPushUriApplyTime"]["ApplyTime"] = 572 "OnReset"; 573 } 574 }, 575 "xyz.openbmc_project.Settings", 576 "/xyz/openbmc_project/software/apply_time", 577 "org.freedesktop.DBus.Properties", "Get", 578 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime"); 579 } 580 581 void doPatch(crow::Response& res, const crow::Request& req, 582 const std::vector<std::string>&) override 583 { 584 BMCWEB_LOG_DEBUG << "doPatch..."; 585 586 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 587 588 std::optional<nlohmann::json> pushUriOptions; 589 if (!json_util::readJson(req, res, "HttpPushUriOptions", 590 pushUriOptions)) 591 { 592 return; 593 } 594 595 if (pushUriOptions) 596 { 597 std::optional<nlohmann::json> pushUriApplyTime; 598 if (!json_util::readJson(*pushUriOptions, res, 599 "HttpPushUriApplyTime", pushUriApplyTime)) 600 { 601 return; 602 } 603 604 if (pushUriApplyTime) 605 { 606 std::optional<std::string> applyTime; 607 if (!json_util::readJson(*pushUriApplyTime, res, "ApplyTime", 608 applyTime)) 609 { 610 return; 611 } 612 613 if (applyTime) 614 { 615 std::string applyTimeNewVal; 616 if (applyTime == "Immediate") 617 { 618 applyTimeNewVal = 619 "xyz.openbmc_project.Software.ApplyTime." 620 "RequestedApplyTimes.Immediate"; 621 } 622 else if (applyTime == "OnReset") 623 { 624 applyTimeNewVal = 625 "xyz.openbmc_project.Software.ApplyTime." 626 "RequestedApplyTimes.OnReset"; 627 } 628 else 629 { 630 BMCWEB_LOG_INFO 631 << "ApplyTime value is not in the list of " 632 "acceptable values"; 633 messages::propertyValueNotInList( 634 asyncResp->res, *applyTime, "ApplyTime"); 635 return; 636 } 637 638 // Set the requested image apply time value 639 crow::connections::systemBus->async_method_call( 640 [asyncResp](const boost::system::error_code ec) { 641 if (ec) 642 { 643 BMCWEB_LOG_ERROR << "D-Bus responses error: " 644 << ec; 645 messages::internalError(asyncResp->res); 646 return; 647 } 648 messages::success(asyncResp->res); 649 }, 650 "xyz.openbmc_project.Settings", 651 "/xyz/openbmc_project/software/apply_time", 652 "org.freedesktop.DBus.Properties", "Set", 653 "xyz.openbmc_project.Software.ApplyTime", 654 "RequestedApplyTime", 655 std::variant<std::string>{applyTimeNewVal}); 656 } 657 } 658 } 659 } 660 661 void doPost(crow::Response& res, const crow::Request& req, 662 const std::vector<std::string>&) override 663 { 664 BMCWEB_LOG_DEBUG << "doPost..."; 665 666 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 667 668 // Setup callback for when new software detected 669 monitorForSoftwareAvailable(asyncResp, req, 670 "/redfish/v1/UpdateService"); 671 672 std::string filepath( 673 "/tmp/images/" + 674 boost::uuids::to_string(boost::uuids::random_generator()())); 675 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 676 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 677 std::ofstream::trunc); 678 out << req.body; 679 out.close(); 680 BMCWEB_LOG_DEBUG << "file upload complete!!"; 681 } 682 }; 683 684 class SoftwareInventoryCollection : public Node 685 { 686 public: 687 SoftwareInventoryCollection(App& app) : 688 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") 689 { 690 entityPrivileges = { 691 {boost::beast::http::verb::get, {{"Login"}}}, 692 {boost::beast::http::verb::head, {{"Login"}}}, 693 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 694 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 695 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 696 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 697 } 698 699 private: 700 void doGet(crow::Response& res, const crow::Request&, 701 const std::vector<std::string>&) override 702 { 703 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 704 res.jsonValue["@odata.type"] = 705 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 706 res.jsonValue["@odata.id"] = 707 "/redfish/v1/UpdateService/FirmwareInventory"; 708 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<std::pair< 714 std::string, std::vector<std::pair< 715 std::string, std::vector<std::string>>>>>& 716 subtree) { 717 if (ec) 718 { 719 messages::internalError(asyncResp->res); 720 return; 721 } 722 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 723 asyncResp->res.jsonValue["Members@odata.count"] = 0; 724 725 for (auto& obj : subtree) 726 { 727 // if can't parse fw id then return 728 std::size_t idPos; 729 if ((idPos = obj.first.rfind('/')) == std::string::npos) 730 { 731 messages::internalError(asyncResp->res); 732 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; 733 return; 734 } 735 std::string swId = obj.first.substr(idPos + 1); 736 737 nlohmann::json& members = 738 asyncResp->res.jsonValue["Members"]; 739 members.push_back( 740 {{"@odata.id", "/redfish/v1/UpdateService/" 741 "FirmwareInventory/" + 742 swId}}); 743 asyncResp->res.jsonValue["Members@odata.count"] = 744 members.size(); 745 } 746 }, 747 // Note that only firmware levels associated with a device are 748 // stored under /xyz/openbmc_project/software therefore to ensure 749 // only real FirmwareInventory items are returned, this full object 750 // path must be used here as input to mapper 751 "xyz.openbmc_project.ObjectMapper", 752 "/xyz/openbmc_project/object_mapper", 753 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 754 "/xyz/openbmc_project/software", static_cast<int32_t>(0), 755 std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"}); 756 } 757 }; 758 759 class SoftwareInventory : public Node 760 { 761 public: 762 SoftwareInventory(App& app) : 763 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 764 std::string()) 765 { 766 entityPrivileges = { 767 {boost::beast::http::verb::get, {{"Login"}}}, 768 {boost::beast::http::verb::head, {{"Login"}}}, 769 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 770 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 771 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 772 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 773 } 774 775 private: 776 /* Fill related item links (i.e. bmc, bios) in for inventory */ 777 static void getRelatedItems(const std::shared_ptr<AsyncResp>& aResp, 778 const std::string& purpose) 779 { 780 if (purpose == fw_util::bmcPurpose) 781 { 782 nlohmann::json& members = aResp->res.jsonValue["RelatedItem"]; 783 members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}}); 784 aResp->res.jsonValue["Members@odata.count"] = members.size(); 785 } 786 else if (purpose == fw_util::biosPurpose) 787 { 788 nlohmann::json& members = aResp->res.jsonValue["RelatedItem"]; 789 members.push_back( 790 {{"@odata.id", "/redfish/v1/Systems/system/Bios"}}); 791 aResp->res.jsonValue["Members@odata.count"] = members.size(); 792 } 793 else 794 { 795 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose; 796 } 797 } 798 799 void doGet(crow::Response& res, const crow::Request&, 800 const std::vector<std::string>& params) override 801 { 802 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 803 804 if (params.size() != 1) 805 { 806 messages::internalError(res); 807 res.end(); 808 return; 809 } 810 811 std::shared_ptr<std::string> swId = 812 std::make_shared<std::string>(params[0]); 813 814 res.jsonValue["@odata.id"] = 815 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 816 817 crow::connections::systemBus->async_method_call( 818 [asyncResp, swId]( 819 const boost::system::error_code ec, 820 const std::vector<std::pair< 821 std::string, std::vector<std::pair< 822 std::string, std::vector<std::string>>>>>& 823 subtree) { 824 BMCWEB_LOG_DEBUG << "doGet callback..."; 825 if (ec) 826 { 827 messages::internalError(asyncResp->res); 828 return; 829 } 830 831 // Ensure we find our input swId, otherwise return an error 832 bool found = false; 833 for (const std::pair< 834 std::string, 835 std::vector< 836 std::pair<std::string, std::vector<std::string>>>>& 837 obj : subtree) 838 { 839 if (boost::ends_with(obj.first, *swId) != true) 840 { 841 continue; 842 } 843 844 if (obj.second.size() < 1) 845 { 846 continue; 847 } 848 849 found = true; 850 fw_util::getFwStatus(asyncResp, swId, obj.second[0].first); 851 852 crow::connections::systemBus->async_method_call( 853 [asyncResp, 854 swId](const boost::system::error_code error_code, 855 const boost::container::flat_map< 856 std::string, VariantType>& propertiesList) { 857 if (error_code) 858 { 859 messages::internalError(asyncResp->res); 860 return; 861 } 862 boost::container::flat_map< 863 std::string, VariantType>::const_iterator it = 864 propertiesList.find("Purpose"); 865 if (it == propertiesList.end()) 866 { 867 BMCWEB_LOG_DEBUG 868 << "Can't find property \"Purpose\"!"; 869 messages::propertyMissing(asyncResp->res, 870 "Purpose"); 871 return; 872 } 873 const std::string* swInvPurpose = 874 std::get_if<std::string>(&it->second); 875 if (swInvPurpose == nullptr) 876 { 877 BMCWEB_LOG_DEBUG 878 << "wrong types for property\"Purpose\"!"; 879 messages::propertyValueTypeError(asyncResp->res, 880 "", "Purpose"); 881 return; 882 } 883 884 BMCWEB_LOG_DEBUG << "swInvPurpose = " 885 << *swInvPurpose; 886 it = propertiesList.find("Version"); 887 if (it == propertiesList.end()) 888 { 889 BMCWEB_LOG_DEBUG 890 << "Can't find property \"Version\"!"; 891 messages::propertyMissing(asyncResp->res, 892 "Version"); 893 return; 894 } 895 896 BMCWEB_LOG_DEBUG << "Version found!"; 897 898 const std::string* version = 899 std::get_if<std::string>(&it->second); 900 901 if (version == nullptr) 902 { 903 BMCWEB_LOG_DEBUG 904 << "Can't find property \"Version\"!"; 905 906 messages::propertyValueTypeError(asyncResp->res, 907 "", "Version"); 908 return; 909 } 910 asyncResp->res.jsonValue["Version"] = *version; 911 asyncResp->res.jsonValue["Id"] = *swId; 912 913 // swInvPurpose is of format: 914 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 915 // Translate this to "ABC image" 916 size_t endDesc = swInvPurpose->rfind('.'); 917 if (endDesc == std::string::npos) 918 { 919 messages::internalError(asyncResp->res); 920 return; 921 } 922 endDesc++; 923 if (endDesc >= swInvPurpose->size()) 924 { 925 messages::internalError(asyncResp->res); 926 return; 927 } 928 929 std::string formatDesc = 930 swInvPurpose->substr(endDesc); 931 asyncResp->res.jsonValue["Description"] = 932 formatDesc + " image"; 933 getRelatedItems(asyncResp, *swInvPurpose); 934 }, 935 obj.second[0].first, obj.first, 936 "org.freedesktop.DBus.Properties", "GetAll", 937 "xyz.openbmc_project.Software.Version"); 938 } 939 if (!found) 940 { 941 BMCWEB_LOG_ERROR << "Input swID " + *swId + " not found!"; 942 messages::resourceMissingAtURI( 943 asyncResp->res, 944 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId); 945 return; 946 } 947 asyncResp->res.jsonValue["@odata.type"] = 948 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 949 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 950 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 951 952 asyncResp->res.jsonValue["Updateable"] = false; 953 fw_util::getFwUpdateableStatus(asyncResp, swId); 954 }, 955 "xyz.openbmc_project.ObjectMapper", 956 "/xyz/openbmc_project/object_mapper", 957 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 958 static_cast<int32_t>(0), 959 std::array<const char*, 1>{"xyz.openbmc_project.Software.Version"}); 960 } 961 }; 962 963 } // namespace redfish 964