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