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/sw_utils.hpp> 26 27 namespace redfish 28 { 29 30 // Match signals added on software path 31 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher; 32 static std::unique_ptr<sdbusplus::bus::match_t> 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_t& m, task::Payload&& payload) 67 { 68 dbus::utility::DBusInteracesMap interfacesProperties; 69 70 sdbusplus::message::object_path objPath; 71 72 m.read(objPath, interfacesProperties); 73 74 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 75 for (auto& interface : interfacesProperties) 76 { 77 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 78 79 if (interface.first == "xyz.openbmc_project.Software.Activation") 80 { 81 // Retrieve service and activate 82 crow::connections::systemBus->async_method_call( 83 [objPath, asyncResp, payload(std::move(payload))]( 84 const boost::system::error_code errorCode, 85 const std::vector< 86 std::pair<std::string, std::vector<std::string>>>& 87 objInfo) mutable { 88 if (errorCode) 89 { 90 BMCWEB_LOG_DEBUG << "error_code = " << errorCode; 91 BMCWEB_LOG_DEBUG << "error msg = " << errorCode.message(); 92 if (asyncResp) 93 { 94 messages::internalError(asyncResp->res); 95 } 96 cleanUp(); 97 return; 98 } 99 // Ensure we only got one service back 100 if (objInfo.size() != 1) 101 { 102 BMCWEB_LOG_ERROR << "Invalid Object Size " 103 << objInfo.size(); 104 if (asyncResp) 105 { 106 messages::internalError(asyncResp->res); 107 } 108 cleanUp(); 109 return; 110 } 111 // cancel timer only when 112 // xyz.openbmc_project.Software.Activation interface 113 // is added 114 fwAvailableTimer = nullptr; 115 116 activateImage(objPath.str, objInfo[0].first); 117 if (asyncResp) 118 { 119 std::shared_ptr<task::TaskData> task = 120 task::TaskData::createTask( 121 [](boost::system::error_code ec, 122 sdbusplus::message_t& msg, 123 const std::shared_ptr<task::TaskData>& 124 taskData) { 125 if (ec) 126 { 127 return task::completed; 128 } 129 130 std::string iface; 131 dbus::utility::DBusPropertiesMap values; 132 133 std::string index = std::to_string(taskData->index); 134 msg.read(iface, values); 135 136 if (iface == "xyz.openbmc_project.Software.Activation") 137 { 138 std::string* state = nullptr; 139 for (const auto& property : values) 140 { 141 if (property.first == "Activation") 142 { 143 const std::string* activationState = 144 std::get_if<std::string>( 145 &property.second); 146 if (activationState == nullptr) 147 { 148 taskData->messages.emplace_back( 149 messages::internalError()); 150 return task::completed; 151 } 152 } 153 } 154 155 if (state == nullptr) 156 { 157 return !task::completed; 158 } 159 160 if (state->ends_with("Invalid") || 161 state->ends_with("Failed")) 162 { 163 taskData->state = "Exception"; 164 taskData->status = "Warning"; 165 taskData->messages.emplace_back( 166 messages::taskAborted(index)); 167 return task::completed; 168 } 169 170 if (state->ends_with("Staged")) 171 { 172 taskData->state = "Stopping"; 173 taskData->messages.emplace_back( 174 messages::taskPaused(index)); 175 176 // its staged, set a long timer to 177 // allow them time to complete the 178 // update (probably cycle the 179 // system) if this expires then 180 // task will be cancelled 181 taskData->extendTimer(std::chrono::hours(5)); 182 return !task::completed; 183 } 184 185 if (state->ends_with("Active")) 186 { 187 taskData->messages.emplace_back( 188 messages::taskCompletedOK(index)); 189 taskData->state = "Completed"; 190 return task::completed; 191 } 192 } 193 else if ( 194 iface == 195 "xyz.openbmc_project.Software.ActivationProgress") 196 { 197 198 const uint8_t* progress = nullptr; 199 for (const auto& property : values) 200 { 201 if (property.first == "Progress") 202 { 203 const std::string* progressStr = 204 std::get_if<std::string>( 205 &property.second); 206 if (progressStr == 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 = 220 static_cast<int>(*progress); 221 taskData->messages.emplace_back( 222 messages::taskProgressChanged( 223 index, static_cast<size_t>(*progress))); 224 225 // if we're getting status updates it's 226 // still alive, update timer 227 taskData->extendTimer(std::chrono::minutes(5)); 228 } 229 230 // as firmware update often results in a 231 // reboot, the task may never "complete" 232 // unless it is an error 233 234 return !task::completed; 235 }, 236 "type='signal',interface='org.freedesktop.DBus.Properties'," 237 "member='PropertiesChanged',path='" + 238 objPath.str + "'"); 239 task->startTimer(std::chrono::minutes(5)); 240 task->populateResp(asyncResp->res); 241 task->payload.emplace(std::move(payload)); 242 } 243 fwUpdateInProgress = false; 244 }, 245 "xyz.openbmc_project.ObjectMapper", 246 "/xyz/openbmc_project/object_mapper", 247 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, 248 std::array<const char*, 1>{ 249 "xyz.openbmc_project.Software.Activation"}); 250 } 251 } 252 } 253 254 // Note that asyncResp can be either a valid pointer or nullptr. If nullptr 255 // then no asyncResp updates will occur 256 static void monitorForSoftwareAvailable( 257 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 258 const crow::Request& req, const std::string& url, 259 int timeoutTimeSeconds = 10) 260 { 261 // Only allow one FW update at a time 262 if (fwUpdateInProgress) 263 { 264 if (asyncResp) 265 { 266 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 267 } 268 return; 269 } 270 271 fwAvailableTimer = 272 std::make_unique<boost::asio::steady_timer>(*req.ioService); 273 274 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 275 276 fwAvailableTimer->async_wait( 277 [asyncResp](const boost::system::error_code& ec) { 278 cleanUp(); 279 if (ec == boost::asio::error::operation_aborted) 280 { 281 // expected, we were canceled before the timer completed. 282 return; 283 } 284 BMCWEB_LOG_ERROR 285 << "Timed out waiting for firmware object being created"; 286 BMCWEB_LOG_ERROR << "FW image may has already been uploaded to server"; 287 if (ec) 288 { 289 BMCWEB_LOG_ERROR << "Async_wait failed" << ec; 290 return; 291 } 292 if (asyncResp) 293 { 294 redfish::messages::internalError(asyncResp->res); 295 } 296 }); 297 task::Payload payload(req); 298 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 299 BMCWEB_LOG_DEBUG << "Match fired"; 300 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 301 }; 302 303 fwUpdateInProgress = true; 304 305 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 306 *crow::connections::systemBus, 307 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 308 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 309 callback); 310 311 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 312 *crow::connections::systemBus, 313 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 314 "member='InterfacesAdded'," 315 "path='/xyz/openbmc_project/logging'", 316 [asyncResp, url](sdbusplus::message_t& m) { 317 std::vector<std::pair<std::string, dbus::utility::DBusPropertiesMap>> 318 interfacesProperties; 319 sdbusplus::message::object_path objPath; 320 m.read(objPath, interfacesProperties); 321 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 322 for (const std::pair<std::string, dbus::utility::DBusPropertiesMap>& 323 interface : interfacesProperties) 324 { 325 if (interface.first == "xyz.openbmc_project.Logging.Entry") 326 { 327 for (const std::pair<std::string, 328 dbus::utility::DbusVariantType>& value : 329 interface.second) 330 { 331 if (value.first != "Message") 332 { 333 continue; 334 } 335 const std::string* type = 336 std::get_if<std::string>(&value.second); 337 if (type == nullptr) 338 { 339 // if this was our message, timeout will cover it 340 return; 341 } 342 fwAvailableTimer = nullptr; 343 if (*type == 344 "xyz.openbmc_project.Software.Image.Error.UnTarFailure") 345 { 346 redfish::messages::invalidUpload(asyncResp->res, url, 347 "Invalid archive"); 348 } 349 else if (*type == 350 "xyz.openbmc_project.Software.Image.Error." 351 "ManifestFileFailure") 352 { 353 redfish::messages::invalidUpload(asyncResp->res, url, 354 "Invalid manifest"); 355 } 356 else if ( 357 *type == 358 "xyz.openbmc_project.Software.Image.Error.ImageFailure") 359 { 360 redfish::messages::invalidUpload( 361 asyncResp->res, url, "Invalid image format"); 362 } 363 else if ( 364 *type == 365 "xyz.openbmc_project.Software.Version.Error.AlreadyExists") 366 { 367 redfish::messages::invalidUpload( 368 asyncResp->res, url, 369 "Image version already exists"); 370 371 redfish::messages::resourceAlreadyExists( 372 asyncResp->res, 373 "UpdateService.v1_5_0.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 crow::connections::systemBus->async_method_call( 823 [asyncResp, 824 swId](const boost::system::error_code errorCode, 825 const dbus::utility::DBusPropertiesMap& propertiesList) { 826 if (errorCode) 827 { 828 messages::internalError(asyncResp->res); 829 return; 830 } 831 const std::string* swInvPurpose = nullptr; 832 const std::string* version = nullptr; 833 for (const auto& property : propertiesList) 834 { 835 if (property.first == "Purpose") 836 { 837 swInvPurpose = std::get_if<std::string>(&property.second); 838 } 839 else if (property.first == "Version") 840 { 841 version = std::get_if<std::string>(&property.second); 842 } 843 } 844 845 if (swInvPurpose == nullptr) 846 { 847 BMCWEB_LOG_DEBUG << "Can't find property \"Purpose\"!"; 848 messages::internalError(asyncResp->res); 849 return; 850 } 851 852 BMCWEB_LOG_DEBUG << "swInvPurpose = " << *swInvPurpose; 853 854 if (version == nullptr) 855 { 856 BMCWEB_LOG_DEBUG << "Can't find property \"Version\"!"; 857 858 messages::internalError(asyncResp->res); 859 860 return; 861 } 862 asyncResp->res.jsonValue["Version"] = *version; 863 asyncResp->res.jsonValue["Id"] = swId; 864 865 // swInvPurpose is of format: 866 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 867 // Translate this to "ABC image" 868 size_t endDesc = swInvPurpose->rfind('.'); 869 if (endDesc == std::string::npos) 870 { 871 messages::internalError(asyncResp->res); 872 return; 873 } 874 endDesc++; 875 if (endDesc >= swInvPurpose->size()) 876 { 877 messages::internalError(asyncResp->res); 878 return; 879 } 880 881 std::string formatDesc = swInvPurpose->substr(endDesc); 882 asyncResp->res.jsonValue["Description"] = formatDesc + " image"; 883 getRelatedItems(asyncResp, *swInvPurpose); 884 }, 885 service, path, "org.freedesktop.DBus.Properties", "GetAll", 886 "xyz.openbmc_project.Software.Version"); 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