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