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