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