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 fwAvailableTimer = 409 std::make_unique<boost::asio::steady_timer>(*req.ioService); 410 411 fwAvailableTimer->expires_after(std::chrono::seconds(timeoutTimeSeconds)); 412 413 fwAvailableTimer->async_wait( 414 std::bind_front(afterAvailbleTimerAsyncWait, asyncResp)); 415 416 task::Payload payload(req); 417 auto callback = [asyncResp, payload](sdbusplus::message_t& m) mutable { 418 BMCWEB_LOG_DEBUG("Match fired"); 419 softwareInterfaceAdded(asyncResp, m, std::move(payload)); 420 }; 421 422 fwUpdateInProgress = true; 423 424 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 425 *crow::connections::systemBus, 426 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 427 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 428 callback); 429 430 fwUpdateErrorMatcher = std::make_unique<sdbusplus::bus::match_t>( 431 *crow::connections::systemBus, 432 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 433 "member='InterfacesAdded'," 434 "path='/xyz/openbmc_project/logging'", 435 std::bind_front(afterUpdateErrorMatcher, asyncResp, url)); 436 } 437 438 struct TftpUrl 439 { 440 std::string fwFile; 441 std::string tftpServer; 442 }; 443 444 inline std::optional<TftpUrl> 445 parseTftpUrl(std::string imageURI, 446 std::optional<std::string> transferProtocol, 447 crow::Response& res) 448 { 449 if (imageURI.find("://") == std::string::npos) 450 { 451 if (imageURI.starts_with("/")) 452 { 453 messages::actionParameterValueTypeError( 454 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 455 return std::nullopt; 456 } 457 if (!transferProtocol) 458 { 459 messages::actionParameterValueTypeError( 460 res, imageURI, "ImageURI", "UpdateService.SimpleUpdate"); 461 return std::nullopt; 462 } 463 // OpenBMC currently only supports TFTP 464 if (*transferProtocol != "TFTP") 465 { 466 messages::actionParameterNotSupported(res, "TransferProtocol", 467 *transferProtocol); 468 BMCWEB_LOG_ERROR("Request incorrect protocol parameter: {}", 469 *transferProtocol); 470 return std::nullopt; 471 } 472 imageURI = "tftp://" + imageURI; 473 } 474 475 boost::system::result<boost::urls::url> url = 476 boost::urls::parse_absolute_uri(imageURI); 477 if (!url) 478 { 479 messages::actionParameterValueTypeError(res, imageURI, "ImageURI", 480 "UpdateService.SimpleUpdate"); 481 482 return std::nullopt; 483 } 484 url->normalize(); 485 486 if (url->scheme() != "tftp") 487 { 488 messages::actionParameterNotSupported(res, "ImageURI", imageURI); 489 return std::nullopt; 490 } 491 std::string path(url->encoded_path()); 492 if (path.size() < 2) 493 { 494 messages::actionParameterNotSupported(res, "ImageURI", imageURI); 495 return std::nullopt; 496 } 497 path.erase(0, 1); 498 std::string host(url->encoded_host_and_port()); 499 return TftpUrl{path, host}; 500 } 501 502 /** 503 * UpdateServiceActionsSimpleUpdate class supports handle POST method for 504 * SimpleUpdate action. 505 */ 506 inline void requestRoutesUpdateServiceActionsSimpleUpdate(App& app) 507 { 508 BMCWEB_ROUTE( 509 app, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate/") 510 .privileges(redfish::privileges::postUpdateService) 511 .methods(boost::beast::http::verb::post)( 512 [&app](const crow::Request& req, 513 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 514 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 515 { 516 return; 517 } 518 519 std::optional<std::string> transferProtocol; 520 std::string imageURI; 521 522 BMCWEB_LOG_DEBUG("Enter UpdateService.SimpleUpdate doPost"); 523 524 // User can pass in both TransferProtocol and ImageURI parameters or 525 // they can pass in just the ImageURI with the transfer protocol 526 // embedded within it. 527 // 1) TransferProtocol:TFTP ImageURI:1.1.1.1/myfile.bin 528 // 2) ImageURI:tftp://1.1.1.1/myfile.bin 529 530 if (!json_util::readJsonAction(req, asyncResp->res, "TransferProtocol", 531 transferProtocol, "ImageURI", imageURI)) 532 { 533 BMCWEB_LOG_DEBUG("Missing TransferProtocol or ImageURI parameter"); 534 return; 535 } 536 std::optional<TftpUrl> ret = parseTftpUrl(imageURI, transferProtocol, 537 asyncResp->res); 538 if (!ret) 539 { 540 return; 541 } 542 543 BMCWEB_LOG_DEBUG("Server: {} File: {}", ret->tftpServer, ret->fwFile); 544 545 // Setup callback for when new software detected 546 // Give TFTP 10 minutes to complete 547 monitorForSoftwareAvailable( 548 asyncResp, req, 549 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", 550 600); 551 552 // TFTP can take up to 10 minutes depending on image size and 553 // connection speed. Return to caller as soon as the TFTP operation 554 // has been started. The callback above will ensure the activate 555 // is started once the download has completed 556 redfish::messages::success(asyncResp->res); 557 558 // Call TFTP service 559 crow::connections::systemBus->async_method_call( 560 [](const boost::system::error_code& ec) { 561 if (ec) 562 { 563 // messages::internalError(asyncResp->res); 564 cleanUp(); 565 BMCWEB_LOG_DEBUG("error_code = {}", ec); 566 BMCWEB_LOG_DEBUG("error msg = {}", ec.message()); 567 } 568 else 569 { 570 BMCWEB_LOG_DEBUG("Call to DownloaViaTFTP Success"); 571 } 572 }, 573 "xyz.openbmc_project.Software.Download", 574 "/xyz/openbmc_project/software", "xyz.openbmc_project.Common.TFTP", 575 "DownloadViaTFTP", ret->fwFile, ret->tftpServer); 576 577 BMCWEB_LOG_DEBUG("Exit UpdateService.SimpleUpdate doPost"); 578 }); 579 } 580 581 inline void uploadImageFile(crow::Response& res, std::string_view body) 582 { 583 std::filesystem::path filepath("/tmp/images/" + bmcweb::getRandomUUID()); 584 585 BMCWEB_LOG_DEBUG("Writing file to {}", filepath.string()); 586 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 587 std::ofstream::trunc); 588 // set the permission of the file to 640 589 std::filesystem::perms permission = std::filesystem::perms::owner_read | 590 std::filesystem::perms::group_read; 591 std::filesystem::permissions(filepath, permission); 592 out << body; 593 594 if (out.bad()) 595 { 596 messages::internalError(res); 597 cleanUp(); 598 } 599 } 600 601 inline void setApplyTime(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 602 const std::string& applyTime) 603 { 604 std::string applyTimeNewVal; 605 if (applyTime == "Immediate") 606 { 607 applyTimeNewVal = 608 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; 609 } 610 else if (applyTime == "OnReset") 611 { 612 applyTimeNewVal = 613 "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset"; 614 } 615 else 616 { 617 BMCWEB_LOG_INFO( 618 "ApplyTime value is not in the list of acceptable values"); 619 messages::propertyValueNotInList(asyncResp->res, applyTime, 620 "ApplyTime"); 621 return; 622 } 623 624 setDbusProperty(asyncResp, "xyz.openbmc_project.Settings", 625 sdbusplus::message::object_path( 626 "/xyz/openbmc_project/software/apply_time"), 627 "xyz.openbmc_project.Software.ApplyTime", 628 "RequestedApplyTime", "ApplyTime", applyTimeNewVal); 629 } 630 631 inline void 632 updateMultipartContext(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 633 const MultipartParser& parser) 634 { 635 const std::string* uploadData = nullptr; 636 std::optional<std::string> applyTime = "OnReset"; 637 bool targetFound = false; 638 for (const FormPart& formpart : parser.mime_fields) 639 { 640 boost::beast::http::fields::const_iterator it = 641 formpart.fields.find("Content-Disposition"); 642 if (it == formpart.fields.end()) 643 { 644 BMCWEB_LOG_ERROR("Couldn't find Content-Disposition"); 645 return; 646 } 647 BMCWEB_LOG_INFO("Parsing value {}", it->value()); 648 649 // The construction parameters of param_list must start with `;` 650 size_t index = it->value().find(';'); 651 if (index == std::string::npos) 652 { 653 continue; 654 } 655 656 for (const auto& param : 657 boost::beast::http::param_list{it->value().substr(index)}) 658 { 659 if (param.first != "name" || param.second.empty()) 660 { 661 continue; 662 } 663 664 if (param.second == "UpdateParameters") 665 { 666 std::vector<std::string> targets; 667 nlohmann::json content = 668 nlohmann::json::parse(formpart.content); 669 nlohmann::json::object_t* obj = 670 content.get_ptr<nlohmann::json::object_t*>(); 671 if (obj == nullptr) 672 { 673 messages::propertyValueFormatError(asyncResp->res, targets, 674 "UpdateParameters"); 675 return; 676 } 677 678 if (!json_util::readJsonObject( 679 *obj, asyncResp->res, "Targets", targets, 680 "@Redfish.OperationApplyTime", applyTime)) 681 { 682 return; 683 } 684 if (targets.size() != 1) 685 { 686 messages::propertyValueFormatError(asyncResp->res, targets, 687 "Targets"); 688 return; 689 } 690 if (targets[0] != "/redfish/v1/Managers/bmc") 691 { 692 messages::propertyValueNotInList(asyncResp->res, targets[0], 693 "Targets/0"); 694 return; 695 } 696 targetFound = true; 697 } 698 else if (param.second == "UpdateFile") 699 { 700 uploadData = &(formpart.content); 701 } 702 } 703 } 704 705 if (uploadData == nullptr) 706 { 707 BMCWEB_LOG_ERROR("Upload data is NULL"); 708 messages::propertyMissing(asyncResp->res, "UpdateFile"); 709 return; 710 } 711 if (!targetFound) 712 { 713 messages::propertyMissing(asyncResp->res, "targets"); 714 return; 715 } 716 717 setApplyTime(asyncResp, *applyTime); 718 719 uploadImageFile(asyncResp->res, *uploadData); 720 } 721 722 inline void 723 handleUpdateServicePost(App& app, const crow::Request& req, 724 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 725 { 726 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 727 { 728 return; 729 } 730 std::string_view contentType = req.getHeaderValue("Content-Type"); 731 732 BMCWEB_LOG_DEBUG("doPost: contentType={}", contentType); 733 734 // Make sure that content type is application/octet-stream or 735 // multipart/form-data 736 if (bmcweb::asciiIEquals(contentType, "application/octet-stream")) 737 { 738 // Setup callback for when new software detected 739 monitorForSoftwareAvailable(asyncResp, req, 740 "/redfish/v1/UpdateService"); 741 742 uploadImageFile(asyncResp->res, req.body()); 743 } 744 else if (contentType.starts_with("multipart/form-data")) 745 { 746 MultipartParser parser; 747 748 // Setup callback for when new software detected 749 monitorForSoftwareAvailable(asyncResp, req, 750 "/redfish/v1/UpdateService"); 751 752 ParserError ec = parser.parse(req); 753 if (ec != ParserError::PARSER_SUCCESS) 754 { 755 // handle error 756 BMCWEB_LOG_ERROR("MIME parse failed, ec : {}", 757 static_cast<int>(ec)); 758 messages::internalError(asyncResp->res); 759 return; 760 } 761 updateMultipartContext(asyncResp, parser); 762 } 763 else 764 { 765 BMCWEB_LOG_DEBUG("Bad content type specified:{}", contentType); 766 asyncResp->res.result(boost::beast::http::status::bad_request); 767 } 768 } 769 770 inline void requestRoutesUpdateService(App& app) 771 { 772 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 773 .privileges(redfish::privileges::getUpdateService) 774 .methods(boost::beast::http::verb::get)( 775 [&app](const crow::Request& req, 776 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 777 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 778 { 779 return; 780 } 781 asyncResp->res.jsonValue["@odata.type"] = 782 "#UpdateService.v1_11_1.UpdateService"; 783 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 784 asyncResp->res.jsonValue["Id"] = "UpdateService"; 785 asyncResp->res.jsonValue["Description"] = "Service for Software Update"; 786 asyncResp->res.jsonValue["Name"] = "Update Service"; 787 788 asyncResp->res.jsonValue["HttpPushUri"] = 789 "/redfish/v1/UpdateService/update"; 790 asyncResp->res.jsonValue["MultipartHttpPushUri"] = 791 "/redfish/v1/UpdateService/update"; 792 793 // UpdateService cannot be disabled 794 asyncResp->res.jsonValue["ServiceEnabled"] = true; 795 asyncResp->res.jsonValue["FirmwareInventory"]["@odata.id"] = 796 "/redfish/v1/UpdateService/FirmwareInventory"; 797 // Get the MaxImageSizeBytes 798 asyncResp->res.jsonValue["MaxImageSizeBytes"] = 799 bmcwebHttpReqBodyLimitMb * 1024 * 1024; 800 801 #ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE 802 // Update Actions object. 803 nlohmann::json& updateSvcSimpleUpdate = 804 asyncResp->res.jsonValue["Actions"]["#UpdateService.SimpleUpdate"]; 805 updateSvcSimpleUpdate["target"] = 806 "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate"; 807 updateSvcSimpleUpdate["TransferProtocol@Redfish.AllowableValues"] = { 808 "TFTP"}; 809 #endif 810 // Get the current ApplyTime value 811 sdbusplus::asio::getProperty<std::string>( 812 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 813 "/xyz/openbmc_project/software/apply_time", 814 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime", 815 [asyncResp](const boost::system::error_code& ec, 816 const std::string& applyTime) { 817 if (ec) 818 { 819 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 820 messages::internalError(asyncResp->res); 821 return; 822 } 823 824 // Store the ApplyTime Value 825 if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 826 "RequestedApplyTimes.Immediate") 827 { 828 asyncResp->res.jsonValue["HttpPushUriOptions"] 829 ["HttpPushUriApplyTime"]["ApplyTime"] = 830 "Immediate"; 831 } 832 else if (applyTime == "xyz.openbmc_project.Software.ApplyTime." 833 "RequestedApplyTimes.OnReset") 834 { 835 asyncResp->res.jsonValue["HttpPushUriOptions"] 836 ["HttpPushUriApplyTime"]["ApplyTime"] = 837 "OnReset"; 838 } 839 }); 840 }); 841 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/") 842 .privileges(redfish::privileges::patchUpdateService) 843 .methods(boost::beast::http::verb::patch)( 844 [&app](const crow::Request& req, 845 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 846 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 847 { 848 return; 849 } 850 BMCWEB_LOG_DEBUG("doPatch..."); 851 852 std::optional<std::string> applyTime; 853 if (!json_util::readJsonPatch( 854 req, asyncResp->res, 855 "HttpPushUriOptions/HttpPushUriApplyTime/ApplyTime", applyTime)) 856 { 857 return; 858 } 859 860 if (applyTime) 861 { 862 setApplyTime(asyncResp, *applyTime); 863 } 864 }); 865 866 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/update/") 867 .privileges(redfish::privileges::postUpdateService) 868 .methods(boost::beast::http::verb::post)( 869 std::bind_front(handleUpdateServicePost, std::ref(app))); 870 } 871 872 inline void requestRoutesSoftwareInventoryCollection(App& app) 873 { 874 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/") 875 .privileges(redfish::privileges::getSoftwareInventoryCollection) 876 .methods(boost::beast::http::verb::get)( 877 [&app](const crow::Request& req, 878 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 879 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 880 { 881 return; 882 } 883 asyncResp->res.jsonValue["@odata.type"] = 884 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 885 asyncResp->res.jsonValue["@odata.id"] = 886 "/redfish/v1/UpdateService/FirmwareInventory"; 887 asyncResp->res.jsonValue["Name"] = "Software Inventory Collection"; 888 const std::array<const std::string_view, 1> iface = { 889 "xyz.openbmc_project.Software.Version"}; 890 891 redfish::collection_util::getCollectionMembers( 892 asyncResp, 893 boost::urls::url("/redfish/v1/UpdateService/FirmwareInventory"), 894 iface, "/xyz/openbmc_project/software"); 895 }); 896 } 897 /* Fill related item links (i.e. bmc, bios) in for inventory */ 898 inline static void 899 getRelatedItems(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 900 const std::string& purpose) 901 { 902 if (purpose == sw_util::bmcPurpose) 903 { 904 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 905 nlohmann::json::object_t item; 906 item["@odata.id"] = "/redfish/v1/Managers/bmc"; 907 relatedItem.emplace_back(std::move(item)); 908 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 909 relatedItem.size(); 910 } 911 else if (purpose == sw_util::biosPurpose) 912 { 913 nlohmann::json& relatedItem = asyncResp->res.jsonValue["RelatedItem"]; 914 nlohmann::json::object_t item; 915 item["@odata.id"] = "/redfish/v1/Systems/system/Bios"; 916 relatedItem.emplace_back(std::move(item)); 917 asyncResp->res.jsonValue["RelatedItem@odata.count"] = 918 relatedItem.size(); 919 } 920 else 921 { 922 BMCWEB_LOG_DEBUG("Unknown software purpose {}", purpose); 923 } 924 } 925 926 inline void 927 getSoftwareVersion(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 928 const std::string& service, const std::string& path, 929 const std::string& swId) 930 { 931 sdbusplus::asio::getAllProperties( 932 *crow::connections::systemBus, service, path, 933 "xyz.openbmc_project.Software.Version", 934 [asyncResp, 935 swId](const boost::system::error_code& ec, 936 const dbus::utility::DBusPropertiesMap& propertiesList) { 937 if (ec) 938 { 939 messages::internalError(asyncResp->res); 940 return; 941 } 942 943 const std::string* swInvPurpose = nullptr; 944 const std::string* version = nullptr; 945 946 const bool success = sdbusplus::unpackPropertiesNoThrow( 947 dbus_utils::UnpackErrorPrinter(), propertiesList, "Purpose", 948 swInvPurpose, "Version", version); 949 950 if (!success) 951 { 952 messages::internalError(asyncResp->res); 953 return; 954 } 955 956 if (swInvPurpose == nullptr) 957 { 958 BMCWEB_LOG_DEBUG("Can't find property \"Purpose\"!"); 959 messages::internalError(asyncResp->res); 960 return; 961 } 962 963 BMCWEB_LOG_DEBUG("swInvPurpose = {}", *swInvPurpose); 964 965 if (version == nullptr) 966 { 967 BMCWEB_LOG_DEBUG("Can't find property \"Version\"!"); 968 969 messages::internalError(asyncResp->res); 970 971 return; 972 } 973 asyncResp->res.jsonValue["Version"] = *version; 974 asyncResp->res.jsonValue["Id"] = swId; 975 976 // swInvPurpose is of format: 977 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 978 // Translate this to "ABC image" 979 size_t endDesc = swInvPurpose->rfind('.'); 980 if (endDesc == std::string::npos) 981 { 982 messages::internalError(asyncResp->res); 983 return; 984 } 985 endDesc++; 986 if (endDesc >= swInvPurpose->size()) 987 { 988 messages::internalError(asyncResp->res); 989 return; 990 } 991 992 std::string formatDesc = swInvPurpose->substr(endDesc); 993 asyncResp->res.jsonValue["Description"] = formatDesc + " image"; 994 getRelatedItems(asyncResp, *swInvPurpose); 995 }); 996 } 997 998 inline void requestRoutesSoftwareInventory(App& app) 999 { 1000 BMCWEB_ROUTE(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/") 1001 .privileges(redfish::privileges::getSoftwareInventory) 1002 .methods(boost::beast::http::verb::get)( 1003 [&app](const crow::Request& req, 1004 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1005 const std::string& param) { 1006 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1007 { 1008 return; 1009 } 1010 std::shared_ptr<std::string> swId = 1011 std::make_shared<std::string>(param); 1012 1013 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1014 "/redfish/v1/UpdateService/FirmwareInventory/{}", *swId); 1015 1016 constexpr std::array<std::string_view, 1> interfaces = { 1017 "xyz.openbmc_project.Software.Version"}; 1018 dbus::utility::getSubTree( 1019 "/", 0, interfaces, 1020 [asyncResp, 1021 swId](const boost::system::error_code& ec, 1022 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1023 BMCWEB_LOG_DEBUG("doGet callback..."); 1024 if (ec) 1025 { 1026 messages::internalError(asyncResp->res); 1027 return; 1028 } 1029 1030 // Ensure we find our input swId, otherwise return an error 1031 bool found = false; 1032 for (const std::pair<std::string, 1033 std::vector<std::pair< 1034 std::string, std::vector<std::string>>>>& 1035 obj : subtree) 1036 { 1037 if (!obj.first.ends_with(*swId)) 1038 { 1039 continue; 1040 } 1041 1042 if (obj.second.empty()) 1043 { 1044 continue; 1045 } 1046 1047 found = true; 1048 sw_util::getSwStatus(asyncResp, swId, obj.second[0].first); 1049 getSoftwareVersion(asyncResp, obj.second[0].first, obj.first, 1050 *swId); 1051 } 1052 if (!found) 1053 { 1054 BMCWEB_LOG_WARNING("Input swID {} not found!", *swId); 1055 messages::resourceMissingAtURI( 1056 asyncResp->res, 1057 boost::urls::format( 1058 "/redfish/v1/UpdateService/FirmwareInventory/{}", 1059 *swId)); 1060 return; 1061 } 1062 asyncResp->res.jsonValue["@odata.type"] = 1063 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 1064 asyncResp->res.jsonValue["Name"] = "Software Inventory"; 1065 asyncResp->res.jsonValue["Status"]["HealthRollup"] = "OK"; 1066 1067 asyncResp->res.jsonValue["Updateable"] = false; 1068 sw_util::getSwUpdatableStatus(asyncResp, swId); 1069 }); 1070 }); 1071 } 1072 1073 } // namespace redfish 1074