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