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