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