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