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