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