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