1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "bmcweb_config.h" 19 20 #include <app.hpp> 21 #include <dbus_utility.hpp> 22 #include <query.hpp> 23 #include <registries/privilege_registry.hpp> 24 #include <sdbusplus/asio/property.hpp> 25 #include <utils/fw_utils.hpp> 26 27 namespace redfish 28 { 29 30 // Match signals added on software path 31 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 32 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateErrorMatcher; 33 // Only allow one update at a time 34 static bool fwUpdateInProgress = false; 35 // Timer for software available 36 static std::unique_ptr<boost::asio::steady_timer> fwAvailableTimer; 37 38 inline static void cleanUp() 39 { 40 fwUpdateInProgress = false; 41 fwUpdateMatcher = nullptr; 42 fwUpdateErrorMatcher = nullptr; 43 } 44 inline static void activateImage(const std::string& objPath, 45 const std::string& service) 46 { 47 BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service; 48 crow::connections::systemBus->async_method_call( 49 [](const boost::system::error_code errorCode) { 50 if (errorCode) 51 { 52 BMCWEB_LOG_DEBUG << "error_code = " << errorCode; 53 BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message(); 54 } 55 }, 56 service, objPath, "org.freedesktop.DBus.Properties", "Set", 57 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 58 dbus::utility::DbusVariantType( 59 "xyz.openbmc_project.Software.Activation.RequestedActivations.Active")); 60 } 61 62 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 63 // then no asyncResp updates will occur 64 static void 65 softwareInterfaceAdded(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 66 sdbusplus::message::message& m, 67 task::Payload&& payload) 68 { 69 dbus::utility::DBusInteracesMap interfacesProperties; 70 71 sdbusplus::message::object_path objPath; 72 73 m.read(objPath, interfacesProperties); 74 75 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 76 for (auto& interface : interfacesProperties) 77 { 78 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 79 80 if (interface.first == "xyz.openbmc_project.Software.Activation") 81 { 82 // Retrieve service and activate 83 crow::connections::systemBus->async_method_call( 84 [objPath, asyncResp, payload(std::move(payload))]( 85 const boost::system::error_code errorCode, 86 const std::vector< 87 std::pair<std::string, std::vector<std::string>>>& 88 objInfo) mutable { 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 dbus::utility::DBusPropertiesMap values; 134 135 std::string index = 136 std::to_string(taskData->index); 137 msg.read(iface, values); 138 139 if (iface == 140 "xyz.openbmc_project.Software.Activation") 141 { 142 std::string* state = nullptr; 143 for (const auto& property : values) 144 { 145 if (property.first == "Activation") 146 { 147 const std::string* state = 148 std::get_if<std::string>( 149 &property.second); 150 if (state == nullptr) 151 { 152 taskData->messages 153 .emplace_back( 154 messages:: 155 internalError()); 156 return task::completed; 157 } 158 } 159 } 160 161 if (state == nullptr) 162 { 163 return !task::completed; 164 } 165 166 if (boost::ends_with(*state, 167 "Invalid") || 168 boost::ends_with(*state, "Failed")) 169 { 170 taskData->state = "Exception"; 171 taskData->status = "Warning"; 172 taskData->messages.emplace_back( 173 messages::taskAborted(index)); 174 return task::completed; 175 } 176 177 if (boost::ends_with(*state, "Staged")) 178 { 179 taskData->state = "Stopping"; 180 taskData->messages.emplace_back( 181 messages::taskPaused(index)); 182 183 // its staged, set a long timer to 184 // allow them time to complete the 185 // update (probably cycle the 186 // system) if this expires then 187 // task will be cancelled 188 taskData->extendTimer( 189 std::chrono::hours(5)); 190 return !task::completed; 191 } 192 193 if (boost::ends_with(*state, "Active")) 194 { 195 taskData->messages.emplace_back( 196 messages::taskCompletedOK( 197 index)); 198 taskData->state = "Completed"; 199 return task::completed; 200 } 201 } 202 else if ( 203 iface == 204 "xyz.openbmc_project.Software.ActivationProgress") 205 { 206 207 const uint8_t* progress = nullptr; 208 for (const auto& property : values) 209 { 210 if (property.first == "Progress") 211 { 212 const std::string* progress = 213 std::get_if<std::string>( 214 &property.second); 215 if (progress == nullptr) 216 { 217 taskData->messages 218 .emplace_back( 219 messages:: 220 internalError()); 221 return task::completed; 222 } 223 } 224 } 225 226 if (progress == nullptr) 227 { 228 return !task::completed; 229 } 230 taskData->percentComplete = 231 static_cast<int>(*progress); 232 taskData->messages.emplace_back( 233 messages::taskProgressChanged( 234 index, static_cast<size_t>( 235 *progress))); 236 237 // if we're getting status updates it's 238 // still alive, update timer 239 taskData->extendTimer( 240 std::chrono::minutes(5)); 241 } 242 243 // as firmware update often results in a 244 // reboot, the task may never "complete" 245 // unless it is an error 246 247 return !task::completed; 248 }, 249 "type='signal',interface='org.freedesktop.DBus.Properties'," 250 "member='PropertiesChanged',path='" + 251 objPath.str + "'"); 252 task->startTimer(std::chrono::minutes(5)); 253 task->populateResp(asyncResp->res); 254 task->payload.emplace(std::move(payload)); 255 } 256 fwUpdateInProgress = false; 257 }, 258 "xyz.openbmc_project.ObjectMapper", 259 "/xyz/openbmc_project/object_mapper", 260 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, 261 std::array<const char*, 1>{ 262 "xyz.openbmc_project.Software.Activation"}); 263 } 264 } 265 } 266 267 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 268 // then no asyncResp updates will occur 269 static void monitorForSoftwareAvailable( 270 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 271 const crow::Request& req, const std::string& url, 272 int timeoutTimeSeconds = 10) 273 { 274 // Only allow one FW update at a time 275 if (fwUpdateInProgress) 276 { 277 if (asyncResp) 278 { 279 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 280 } 281 return; 282 } 283 284 fwAvailableTimer = 285 std::make_unique<boost::asio::steady_timer>(*req.ioService); 286 287 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 288 289 fwAvailableTimer->async_wait( 290 [asyncResp](const boost::system::error_code& ec) { 291 cleanUp(); 292 if (ec == boost::asio::error::operation_aborted) 293 { 294 // expected, we were canceled before the timer completed. 295 return; 296 } 297 BMCWEB_LOG_ERROR 298 << "Timed out waiting for firmware object being created"; 299 BMCWEB_LOG_ERROR 300 << "FW image may has already been uploaded to server"; 301 if (ec) 302 { 303 BMCWEB_LOG_ERROR << "Async_wait failed" << ec; 304 return; 305 } 306 if (asyncResp) 307 { 308 redfish::messages::internalError(asyncResp->res); 309 } 310 }); 311 task::Payload payload(req); 312 auto callback = [asyncResp, 313 payload](sdbusplus::message::message& m) mutable { 314 BMCWEB_LOG_DEBUG << "Match fired"; 315 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 316 }; 317 318 fwUpdateInProgress = true; 319 320 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>( 321 *crow::connections::systemBus, 322 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 323 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 324 callback); 325 326 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match::match>( 327 *crow::connections::systemBus, 328 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 329 "member='InterfacesAdded'," 330 "path='/xyz/openbmc_project/logging'", 331 [asyncResp, url](sdbusplus::message::message& m) { 332 std::vector< 333 std::pair<std::string, dbus::utility::DBusPropertiesMap>> 334 interfacesProperties; 335 sdbusplus::message::object_path objPath; 336 m.read(objPath, interfacesProperties); 337 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 338 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 339 interface : interfacesProperties) 340 { 341 if (interface.first == "xyz.openbmc_project.Logging.Entry") 342 { 343 for (const std::pair<std::string, 344 dbus::utility::DbusVariantType>& 345 value : interface.second) 346 { 347 if (value.first != "Message") 348 { 349 continue; 350 } 351 const std::string* type = 352 std::get_if<std::string>(&value.second); 353 if (type == nullptr) 354 { 355 // if this was our message, timeout will cover it 356 return; 357 } 358 fwAvailableTimer = nullptr; 359 if (*type == 360 "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 361 { 362 redfish::messages::invalidUpload( 363 asyncResp->res, url, "Invalid archive"); 364 } 365 else if (*type == 366 "xyz.openbmc_project.Software.Image.Error." 367 "ManifestFileFailure") 368 { 369 redfish::messages::invalidUpload( 370 asyncResp->res, url, "Invalid manifest"); 371 } 372 else if ( 373 *type == 374 "xyz.openbmc_project.Software.Image.Error.ImageFailure") 375 { 376 redfish::messages::invalidUpload( 377 asyncResp->res, url, "Invalid image format"); 378 } 379 else if ( 380 *type == 381 "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 382 { 383 redfish::messages::invalidUpload( 384 asyncResp->res, url, 385 "Image version already exists"); 386 387 redfish::messages::resourceAlreadyExists( 388 asyncResp->res, 389 "UpdateService.v1_5_0.UpdateService", "Version", 390 "uploaded version"); 391 } 392 else if ( 393 *type == 394 "xyz.openbmc_project.Software.Image.Error.BusyFailure") 395 { 396 redfish::messages::resourceExhaustion( 397 asyncResp->res, url); 398 } 399 else 400 { 401 redfish::messages::internalError(asyncResp->res); 402 } 403 } 404 } 405 } 406 }); 407 } 408 409 /** 410 * UpdateServiceActionsSimpleUpdate class supports handle POST method for 411 * SimpleUpdate action. 412 */ 413 inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app) 414 { 415 BMCWEB_ROUTE( 416 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 417 .privileges(redfish::privileges::postUpdateService) 418 .methods( 419 boost::beast::http::verb:: 420 post)([&app]( 421 const crow::Request& req, 422 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 423 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 424 { 425 return; 426 } 427 428 std::optional<std::string> transferProtocol; 429 std::string imageURI; 430 431 BMCWEB_LOG_DEBUG << "Enter UpdateService.SimpleUpdate doPost"; 432 433 // User can pass in both TransferProtocol and ImageURI parameters or 434 // they can pass in just the ImageURI with the transfer protocol 435 // embedded within it. 436 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 437 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 438 439 if (!json_util::readJsonAction(req, asyncResp->res, 440 "TransferProtocol", transferProtocol, 441 "ImageURI", imageURI)) 442 { 443 BMCWEB_LOG_DEBUG 444 << "Missing TransferProtocol or ImageURI parameter"; 445 return; 446 } 447 if (!transferProtocol) 448 { 449 // Must be option 2 450 // Verify ImageURI has transfer protocol in it 451 size_t separator = imageURI.find(':'); 452 if ((separator == std::string::npos) || 453 ((separator + 1) > imageURI.size())) 454 { 455 messages::actionParameterValueTypeError( 456 asyncResp->res, imageURI, "ImageURI", 457 "UpdateService.SimpleUpdate"); 458 BMCWEB_LOG_ERROR << "ImageURI missing transfer protocol: " 459 << imageURI; 460 return; 461 } 462 transferProtocol = imageURI.substr(0, separator); 463 // Ensure protocol is upper case for a common comparison path 464 // below 465 boost::to_upper(*transferProtocol); 466 BMCWEB_LOG_DEBUG << "Encoded transfer protocol " 467 << *transferProtocol; 468 469 // Adjust imageURI to not have the protocol on it for parsing 470 // below 471 // ex. tftp://1.1.1.1/myfile.bin -> 1.1.1.1/myfile.bin 472 imageURI = imageURI.substr(separator + 3); 473 BMCWEB_LOG_DEBUG << "Adjusted imageUri " << imageURI; 474 } 475 476 // OpenBMC currently only supports TFTP 477 if (*transferProtocol != "TFTP") 478 { 479 messages::actionParameterNotSupported( 480 asyncResp->res, "TransferProtocol", 481 "UpdateService.SimpleUpdate"); 482 BMCWEB_LOG_ERROR << "Request incorrect protocol parameter: " 483 << *transferProtocol; 484 return; 485 } 486 487 // Format should be <IP or Hostname>/<file> for imageURI 488 size_t separator = imageURI.find('/'); 489 if ((separator == std::string::npos) || 490 ((separator + 1) > imageURI.size())) 491 { 492 messages::actionParameterValueTypeError( 493 asyncResp->res, imageURI, "ImageURI", 494 "UpdateService.SimpleUpdate"); 495 BMCWEB_LOG_ERROR << "Invalid ImageURI: " << imageURI; 496 return; 497 } 498 499 std::string tftpServer = imageURI.substr(0, separator); 500 std::string fwFile = imageURI.substr(separator + 1); 501 BMCWEB_LOG_DEBUG << "Server: " << tftpServer + " File: " << fwFile; 502 503 // Setup callback for when new software detected 504 // Give TFTP 10 minutes to complete 505 monitorForSoftwareAvailable( 506 asyncResp, req, 507 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 508 600); 509 510 // TFTP can take up to 10 minutes depending on image size and 511 // connection speed. Return to caller as soon as the TFTP operation 512 // has been started. The callback above will ensure the activate 513 // is started once the download has completed 514 redfish::messages::success(asyncResp->res); 515 516 // Call TFTP service 517 crow::connections::systemBus->async_method_call( 518 [](const boost::system::error_code ec) { 519 if (ec) 520 { 521 // messages::internalError(asyncResp->res); 522 cleanUp(); 523 BMCWEB_LOG_DEBUG << "error_code = " << ec; 524 BMCWEB_LOG_DEBUG << "error msg = " << ec.message(); 525 } 526 else 527 { 528 BMCWEB_LOG_DEBUG << "Call to DownloaViaTFTP Success"; 529 } 530 }, 531 "xyz.openbmc_project.Software.Download", 532 "/xyz/openbmc_project/software", 533 "xyz.openbmc_project.Common.TFTP", "DownloadViaTFTP", fwFile, 534 tftpServer); 535 536 BMCWEB_LOG_DEBUG << "Exit UpdateService.SimpleUpdate doPost"; 537 }); 538 } 539 540 inline void 541 handleUpdateServicePost(App& app, const crow::Request& req, 542 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 543 { 544 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 545 { 546 return; 547 } 548 BMCWEB_LOG_DEBUG << "doPost..."; 549 550 // Setup callback for when new software detected 551 monitorForSoftwareAvailable(asyncResp, req, "/redfish/v1/UpdateService"); 552 553 std::string filepath( 554 "/tmp/images/" + 555 boost::uuids::to_string(boost::uuids::random_generator()())); 556 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 557 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 558 std::ofstream::trunc); 559 out << req.body; 560 out.close(); 561 BMCWEB_LOG_DEBUG << "file upload complete!!"; 562 } 563 564 inline void requestRoutesUpdateService(App& app) 565 { 566 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 567 .privileges(redfish::privileges::getUpdateService) 568 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req, 569 const std::shared_ptr< 570 bmcweb::AsyncResp>& 571 asyncResp) { 572 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 573 { 574 return; 575 } 576 asyncResp->res.jsonValue["@odata.type"] = 577 "#UpdateService.v1_5_0.UpdateService"; 578 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 579 asyncResp->res.jsonValue["Id"] = "UpdateService"; 580 asyncResp->res.jsonValue["Description"] = 581 "Service for Software Update"; 582 asyncResp->res.jsonValue["Name"] = "Update Service"; 583 584 #ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL 585 // See note about later on in this file about why this is neccesary 586 // This is "Wrong" per the standard, but is done temporarily to 587 // avoid noise in failing tests as people transition to having this 588 // option disabled 589 asyncResp->res.addHeader(boost::beast::http::field::allow, 590 "GET, PATCH, HEAD"); 591 #endif 592 593 asyncResp->res.jsonValue["HttpPushUri"] = 594 "/redfish/v1/UpdateService/update"; 595 596 // UpdateService cannot be disabled 597 asyncResp->res.jsonValue["ServiceEnabled"] = true; 598 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 599 "/redfish/v1/UpdateService/FirmwareInventory"; 600 // Get the MaxImageSizeBytes 601 asyncResp->res.jsonValue["MaxImageSizeBytes"] = 602 bmcwebHttpReqBodyLimitMb * 1024 * 1024; 603 604 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE 605 // Update Actions object. 606 nlohmann::json& updateSvcSimpleUpdate = 607 asyncResp->res 608 .jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 609 updateSvcSimpleUpdate["target"] = 610 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 611 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = 612 {"TFTP"}; 613 #endif 614 // Get the current ApplyTime value 615 sdbusplus::asio::getProperty<std::string>( 616 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 617 "/xyz/openbmc_project/software/apply_time", 618 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime", 619 [asyncResp](const boost::system::error_code ec, 620 const std::string& applyTime) { 621 if (ec) 622 { 623 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 624 messages::internalError(asyncResp->res); 625 return; 626 } 627 628 // Store the ApplyTime Value 629 if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 630 "RequestedApplyTimes.Immediate") 631 { 632 asyncResp->res 633 .jsonValue["HttpPushUriOptions"] 634 ["HttpPushUriApplyTime"]["ApplyTime"] = 635 "Immediate"; 636 } 637 else if (applyTime == 638 "xyz.openbmc_project.Software.ApplyTime." 639 "RequestedApplyTimes.OnReset") 640 { 641 asyncResp->res 642 .jsonValue["HttpPushUriOptions"] 643 ["HttpPushUriApplyTime"]["ApplyTime"] = 644 "OnReset"; 645 } 646 }); 647 }); 648 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 649 .privileges(redfish::privileges::patchUpdateService) 650 .methods( 651 boost::beast::http::verb::patch)([&app](const crow::Request& req, 652 const std::shared_ptr< 653 bmcweb::AsyncResp>& 654 asyncResp) { 655 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 656 { 657 return; 658 } 659 BMCWEB_LOG_DEBUG << "doPatch..."; 660 661 std::optional<nlohmann::json> pushUriOptions; 662 if (!json_util::readJsonPatch(req, asyncResp->res, 663 "HttpPushUriOptions", pushUriOptions)) 664 { 665 return; 666 } 667 668 if (pushUriOptions) 669 { 670 std::optional<nlohmann::json> pushUriApplyTime; 671 if (!json_util::readJson(*pushUriOptions, asyncResp->res, 672 "HttpPushUriApplyTime", 673 pushUriApplyTime)) 674 { 675 return; 676 } 677 678 if (pushUriApplyTime) 679 { 680 std::optional<std::string> applyTime; 681 if (!json_util::readJson(*pushUriApplyTime, asyncResp->res, 682 "ApplyTime", applyTime)) 683 { 684 return; 685 } 686 687 if (applyTime) 688 { 689 std::string applyTimeNewVal; 690 if (applyTime == "Immediate") 691 { 692 applyTimeNewVal = 693 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 694 } 695 else if (applyTime == "OnReset") 696 { 697 applyTimeNewVal = 698 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 699 } 700 else 701 { 702 BMCWEB_LOG_INFO 703 << "ApplyTime value is not in the list of acceptable values"; 704 messages::propertyValueNotInList( 705 asyncResp->res, *applyTime, "ApplyTime"); 706 return; 707 } 708 709 // Set the requested image apply time value 710 crow::connections::systemBus->async_method_call( 711 [asyncResp](const boost::system::error_code ec) { 712 if (ec) 713 { 714 BMCWEB_LOG_ERROR 715 << "D-Bus responses error: " << ec; 716 messages::internalError(asyncResp->res); 717 return; 718 } 719 messages::success(asyncResp->res); 720 }, 721 "xyz.openbmc_project.Settings", 722 "/xyz/openbmc_project/software/apply_time", 723 "org.freedesktop.DBus.Properties", "Set", 724 "xyz.openbmc_project.Software.ApplyTime", 725 "RequestedApplyTime", 726 dbus::utility::DbusVariantType{applyTimeNewVal}); 727 } 728 } 729 } 730 }); 731 732 // The "old" behavior of the update service URI causes redfish-service validator 733 // failures when the Allow header is supported, given that in the spec, 734 // UpdateService does not allow POST. in openbmc, we unfortunately reused that 735 // resource as our HttpPushUri as well. A number of services, including the 736 // openbmc tests, and documentation have hardcoded that erroneous API, instead 737 // of relying on HttpPushUri as the spec requires. This option will exist 738 // temporarily to allow the old behavior until Q4 2022, at which time it will be 739 // removed. 740 #ifdef BMCWEB_ENABLE_REDFISH_UPDATESERVICE_OLD_POST_URL 741 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 742 .privileges(redfish::privileges::postUpdateService) 743 .methods( 744 boost::beast::http::verb:: 745 post)([&app]( 746 const crow::Request& req, 747 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 748 asyncResp->res.addHeader( 749 boost::beast::http::field::warning, 750 "299 - \"POST to /redfish/v1/UpdateService is deprecated. Use " 751 "the value contained within HttpPushUri.\""); 752 handleUpdateServicePost(app, req, asyncResp); 753 }); 754 #endif 755 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 756 .privileges(redfish::privileges::postUpdateService) 757 .methods(boost::beast::http::verb::post)( 758 std::bind_front(handleUpdateServicePost, std::ref(app))); 759 } 760 761 inline void requestRoutesSoftwareInventoryCollection(App& app) 762 { 763 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 764 .privileges(redfish::privileges::getSoftwareInventoryCollection) 765 .methods(boost::beast::http::verb::get)( 766 [&app](const crow::Request& req, 767 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 768 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 769 { 770 return; 771 } 772 asyncResp->res.jsonValue["@odata.type"] = 773 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 774 asyncResp->res.jsonValue["@odata.id"] = 775 "/redfish/v1/UpdateService/FirmwareInventory"; 776 asyncResp->res.jsonValue["Name"] = 777 "Software Inventory Collection"; 778 779 crow::connections::systemBus->async_method_call( 780 [asyncResp](const boost::system::error_code ec, 781 const dbus::utility::MapperGetSubTreeResponse& 782 subtree) { 783 if (ec) 784 { 785 messages::internalError(asyncResp->res); 786 return; 787 } 788 asyncResp->res.jsonValue["Members"] = 789 nlohmann::json::array(); 790 asyncResp->res.jsonValue["Members@odata.count"] = 0; 791 792 for (const auto& obj : subtree) 793 { 794 sdbusplus::message::object_path path(obj.first); 795 std::string swId = path.filename(); 796 if (swId.empty()) 797 { 798 messages::internalError(asyncResp->res); 799 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; 800 return; 801 } 802 803 nlohmann::json& members = 804 asyncResp->res.jsonValue["Members"]; 805 nlohmann::json::object_t member; 806 member["@odata.id"] = 807 "/redfish/v1/UpdateService/FirmwareInventory/" + 808 swId; 809 members.push_back(std::move(member)); 810 asyncResp->res.jsonValue["Members@odata.count"] = 811 members.size(); 812 } 813 }, 814 // Note that only firmware levels associated with a device 815 // are stored under /xyz/openbmc_project/software therefore 816 // to ensure only real FirmwareInventory items are returned, 817 // this full object path must be used here as input to 818 // mapper 819 "xyz.openbmc_project.ObjectMapper", 820 "/xyz/openbmc_project/object_mapper", 821 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 822 "/xyz/openbmc_project/software", static_cast<int32_t>(0), 823 std::array<const char*, 1>{ 824 "xyz.openbmc_project.Software.Version"}); 825 }); 826 } 827 /* Fill related item links (i.e. bmc, bios) in for inventory */ 828 inline static void 829 getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 830 const std::string& purpose) 831 { 832 if (purpose == fw_util::bmcPurpose) 833 { 834 nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"]; 835 nlohmann::json::object_t item; 836 item["@odata.id"] = "/redfish/v1/Managers/bmc"; 837 relatedItem.push_back(std::move(item)); 838 aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size(); 839 } 840 else if (purpose == fw_util::biosPurpose) 841 { 842 nlohmann::json& relatedItem = aResp->res.jsonValue["RelatedItem"]; 843 nlohmann::json::object_t item; 844 item["@odata.id"] = "/redfish/v1/Systems/system/Bios"; 845 relatedItem.push_back(std::move(item)); 846 aResp->res.jsonValue["RelatedItem@odata.count"] = relatedItem.size(); 847 } 848 else 849 { 850 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose; 851 } 852 } 853 854 inline void requestRoutesSoftwareInventory(App& app) 855 { 856 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 857 .privileges(redfish::privileges::getSoftwareInventory) 858 .methods( 859 boost::beast::http::verb:: 860 get)([&app](const crow::Request& req, 861 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 862 const std::string& param) { 863 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 864 { 865 return; 866 } 867 std::shared_ptr<std::string> swId = 868 std::make_shared<std::string>(param); 869 870 asyncResp->res.jsonValue["@odata.id"] = 871 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 872 873 crow::connections::systemBus->async_method_call( 874 [asyncResp, 875 swId](const boost::system::error_code ec, 876 const dbus::utility::MapperGetSubTreeResponse& subtree) { 877 BMCWEB_LOG_DEBUG << "doGet callback..."; 878 if (ec) 879 { 880 messages::internalError(asyncResp->res); 881 return; 882 } 883 884 // Ensure we find our input swId, otherwise return an error 885 bool found = false; 886 for (const std::pair< 887 std::string, 888 std::vector<std::pair< 889 std::string, std::vector<std::string>>>>& obj : 890 subtree) 891 { 892 if (!boost::ends_with(obj.first, *swId)) 893 { 894 continue; 895 } 896 897 if (obj.second.empty()) 898 { 899 continue; 900 } 901 902 found = true; 903 fw_util::getFwStatus(asyncResp, swId, 904 obj.second[0].first); 905 906 crow::connections::systemBus->async_method_call( 907 [asyncResp, 908 swId](const boost::system::error_code errorCode, 909 const dbus::utility::DBusPropertiesMap& 910 propertiesList) { 911 if (errorCode) 912 { 913 messages::internalError(asyncResp->res); 914 return; 915 } 916 const std::string* swInvPurpose = nullptr; 917 const std::string* version = nullptr; 918 for (const auto& property : propertiesList) 919 { 920 if (property.first == "Purpose") 921 { 922 swInvPurpose = std::get_if<std::string>( 923 &property.second); 924 } 925 if (property.first == "Version") 926 { 927 version = std::get_if<std::string>( 928 &property.second); 929 } 930 } 931 932 if (swInvPurpose == nullptr) 933 { 934 BMCWEB_LOG_DEBUG 935 << "Can't find property \"Purpose\"!"; 936 messages::internalError(asyncResp->res); 937 return; 938 } 939 940 BMCWEB_LOG_DEBUG << "swInvPurpose = " 941 << *swInvPurpose; 942 943 if (version == nullptr) 944 { 945 BMCWEB_LOG_DEBUG 946 << "Can't find property \"Version\"!"; 947 948 messages::internalError(asyncResp->res); 949 950 return; 951 } 952 asyncResp->res.jsonValue["Version"] = *version; 953 asyncResp->res.jsonValue["Id"] = *swId; 954 955 // swInvPurpose is of format: 956 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 957 // Translate this to "ABC image" 958 size_t endDesc = swInvPurpose->rfind('.'); 959 if (endDesc == std::string::npos) 960 { 961 messages::internalError(asyncResp->res); 962 return; 963 } 964 endDesc++; 965 if (endDesc >= swInvPurpose->size()) 966 { 967 messages::internalError(asyncResp->res); 968 return; 969 } 970 971 std::string formatDesc = 972 swInvPurpose->substr(endDesc); 973 asyncResp->res.jsonValue["Description"] = 974 formatDesc + " image"; 975 getRelatedItems(asyncResp, *swInvPurpose); 976 }, 977 obj.second[0].first, obj.first, 978 "org.freedesktop.DBus.Properties", "GetAll", 979 "xyz.openbmc_project.Software.Version"); 980 } 981 if (!found) 982 { 983 BMCWEB_LOG_ERROR << "Input swID " << *swId 984 << " not found!"; 985 messages::resourceMissingAtURI( 986 asyncResp->res, 987 crow::utility::urlFromPieces( 988 "redfish", "v1", "UpdateService", 989 "FirmwareInventory", *swId)); 990 return; 991 } 992 asyncResp->res.jsonValue["@odata.type"] = 993 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 994 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 995 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 996 997 asyncResp->res.jsonValue["Updateable"] = false; 998 fw_util::getFwUpdateableStatus(asyncResp, swId); 999 }, 1000 "xyz.openbmc_project.ObjectMapper", 1001 "/xyz/openbmc_project/object_mapper", 1002 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 1003 static_cast<int32_t>(0), 1004 std::array<const char*, 1>{ 1005 "xyz.openbmc_project.Software.Version"}); 1006 }); 1007 } 1008 1009 } // namespace redfish 1010