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