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