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