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