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