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