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