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