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