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