| #pragma once |
| |
| #include "absl/strings/substitute.h" |
| #include "bm_config.h" |
| |
| #include "app.hpp" |
| #include "managed_store.hpp" |
| #include "query.hpp" |
| #include "registries/privilege_registry.hpp" |
| #include "utils/sw_utils.hpp" |
| |
| #include <utils/json_utils.hpp> |
| |
| #include <filesystem> |
| #include <fstream> |
| |
| #ifdef UNIT_TEST_BUILD |
| #include "test/g3/mock_managed_store.hpp" // NOLINT |
| #endif |
| |
| namespace redfish |
| { |
| |
| constexpr absl::string_view kPldmServiceName = "xyz.openbmc_project.pldm"; |
| constexpr absl::string_view kPldmObjectPath = "/xyz/openbmc_project/pldm"; |
| constexpr absl::string_view kPldmControlInterface = |
| "xyz.openbmc_project.PLDM.control"; |
| constexpr absl::string_view kPldmLoadSingleFileMethod = |
| "loadSingleFileAndSyncCache"; |
| |
| std::string biosSettingFilepathFromConfig = BIOS_SETTING_FILE_PATH; |
| |
| std::string readBiosSettingsIntoBase64(std::string biosSettingBlobPath) |
| { |
| // If the path doesn't exist, set the properties to an empty string |
| if (!std::filesystem::exists(biosSettingBlobPath)) |
| { |
| BMCWEB_LOG_ERROR << "the BIOS setting file doesn't exist, path:" |
| << biosSettingBlobPath; |
| return {}; |
| } |
| const auto fileSize = std::filesystem::file_size(biosSettingBlobPath); |
| if (fileSize == 0) |
| { |
| BMCWEB_LOG_DEBUG << "the BIOS setting file is empty, path:" |
| << biosSettingBlobPath; |
| return {}; |
| } |
| // ManagedFd would be nice but stdplus is not included as DEPENDS in gbmcweb |
| std::ifstream ifs(biosSettingBlobPath, std::ios::binary); |
| ifs.exceptions(std::ifstream::failbit); |
| std::vector<char> fileData(fileSize); |
| try |
| { |
| std::copy(std::istreambuf_iterator<char>(ifs), |
| std::istreambuf_iterator<char>(), fileData.begin()); |
| } |
| catch (std::ios_base::failure& fail) |
| { |
| BMCWEB_LOG_ERROR << "Failed to read the BIOS setting file"; |
| return {}; |
| } |
| return crow::utility::base64encode( |
| std::string_view(fileData.data(), fileData.size())); |
| } |
| |
| inline std::string resourceIdFromNameString(const std::string& name) |
| { |
| // Find out the index of last non digit character(0,1,2,3,4,5,6,7,8,9) |
| // assuming the inout has at least one digit so that we can get resource |
| // index. For example, system1->1. |
| return name.substr(name.find_last_not_of("0123456789") + 1); |
| } |
| |
| inline void handleBios(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName, bool multiHost) |
| { |
| // systemId should be empty for single host system. |
| std::string systemId = |
| multiHost ? resourceIdFromNameString(systemName) : ""; |
| |
| asyncResp->res.jsonValue["@odata.id"] = |
| absl::Substitute("/redfish/v1/Systems/$0/Bios", systemName); |
| asyncResp->res.jsonValue["@odata.type"] = "#Bios.v1_1_0.Bios"; |
| asyncResp->res.jsonValue["Name"] = "BIOS Configuration"; |
| asyncResp->res.jsonValue["Description"] = "BIOS Configuration Service"; |
| asyncResp->res.jsonValue["Id"] = "BIOS"; |
| asyncResp->res.jsonValue["Actions"]["#Bios.ResetBios"] = { |
| {"target", |
| absl::Substitute("/redfish/v1/Systems/$0/Bios/Actions/Bios.ResetBios", |
| systemName)}}; |
| |
| // Get the ActiveSoftwareImage and SoftwareImages |
| sw_util::populateSoftwareInformation(asyncResp, sw_util::biosPurpose, "", |
| true); |
| |
| // Populated BIOS setting blob |
| std::string biosSettingFilepath = biosSettingFilepathFromConfig; |
| if (multiHost) |
| { |
| size_t posHost = biosSettingFilepath.find("host"); |
| if (posHost == std::string::npos) |
| { |
| BMCWEB_LOG_DEBUG |
| << "The BIOS setting file path template is ill-formated" |
| << biosSettingFilepath; |
| return; |
| } |
| // Get index |
| int systemIdInt = 0; |
| try |
| { |
| systemIdInt = std::stoi(systemId); |
| // system1 -> /run/soc_firmware/host0/vmtppr.cfg |
| // system2 -> /run/soc_firmware/host1/vmtppr.cfg |
| systemIdInt -= 1; |
| } |
| catch (const std::exception& ex) |
| { |
| BMCWEB_LOG_DEBUG << "file to parse systemId " << systemId |
| << ":from string to int, exception:" << ex.what(); |
| return; |
| } |
| |
| // insert the index right after "host" i.e. pos + 4 |
| biosSettingFilepath.insert(posHost + 4, std::to_string(systemIdInt)); |
| } |
| |
| asyncResp->res.jsonValue["Oem"]["Google"]["MemTestBlob"] = |
| readBiosSettingsIntoBase64(biosSettingFilepath); |
| } |
| |
| /** |
| * BiosService class supports handle get method for bios. |
| */ |
| inline void |
| handleBiosServiceGet(crow::App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| |
| constexpr std::array<std::string_view, 1> systemInterfaces{ |
| {"xyz.openbmc_project.Inventory.Item.System"}}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTreePaths( |
| "/", 0, systemInterfaces, requestContext, |
| [asyncResp, systemName](const boost::system::error_code& ec, |
| const std::vector<std::string>& objects) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // If there are less than 2 Item.Systems assume singlehost |
| if (objects.size() < 2) |
| { |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| handleBios(asyncResp, systemName, false); |
| return; |
| } |
| |
| // Otherwise find system, e.g. system1, system2 |
| for (const auto& object : objects) |
| { |
| std::string objectName = object.substr(object.rfind('/') + 1); |
| // Found system object |
| if (systemName == objectName) |
| { |
| handleBios(asyncResp, systemName, true); |
| return; |
| } |
| } |
| |
| // Could not find system object |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| }); |
| } |
| |
| inline void handleBiosSettingPatch( |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& biosSettingFilepath, bool multiHost, |
| std::optional<std::string> memTestBlob, int systemId = 0) |
| { |
| asyncResp->res.result(boost::beast::http::status::no_content); |
| |
| std::filesystem::path filePath(biosSettingFilepath); |
| if (!std::filesystem::exists(filePath.parent_path())) |
| { |
| BMCWEB_LOG_ERROR |
| << "The directory of the BIOS setting file doesn't exist"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| if (!memTestBlob) |
| { |
| BMCWEB_LOG_ERROR << "Payload was null"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| std::string memTestBlobBase64; |
| if (crow::utility::base64Decode(memTestBlob.value_or(""), |
| memTestBlobBase64) == false) |
| { |
| BMCWEB_LOG_ERROR << "Base64 Decoding unsuccessful"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| try |
| { |
| std::ofstream ofs; |
| ofs.open(biosSettingFilepath, std::ios::trunc | std::ios::binary); |
| ofs.write(reinterpret_cast<char*>(memTestBlobBase64.data()), |
| // Get warning if no cast |
| // " error: conversion to |
| // 'std::streamsize' {aka 'int'} from |
| // 'std::__cxx11::basic_string<char>::size_type' {aka |
| // 'unsigned int'} may change the sign of the result |
| // [-Werror=sign-conversion]" |
| static_cast<int>(memTestBlobBase64.size())); |
| ofs.close(); |
| } |
| catch (std::ios_base::failure& fail) |
| { |
| BMCWEB_LOG_ERROR << "Failed to write the BIOS setting file"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // We are using PLDM to transfer the BIOS file out in multi-host machine. |
| // Reload the file content is necessary to guarantee the file content in |
| // PLDMd cache is up-to-date. |
| if (multiHost) |
| { |
| managedStore::GetManagedObjectStore() |
| ->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp](const boost::system::error_code& ec) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "DBUS response error: " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| }, std::string(kPldmServiceName) + std::to_string(systemId), |
| std::string(kPldmObjectPath), |
| std::string(kPldmControlInterface), |
| std::string(kPldmLoadSingleFileMethod), |
| static_cast<uint16_t>(BIOS_SETTING_FILE_HANDLE)); |
| } |
| |
| redfish::messages::success(asyncResp->res); |
| return; |
| } |
| |
| inline void |
| patchBiosSettings(crow::App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| |
| std::optional<std::string> memTestBlob; |
| if (!ENABLE_BIOS_PEER_AUTH_BYPASS && !req.peer_authenticated) |
| { |
| redfish::messages::accessDenied(asyncResp->res, "Bios"); |
| return; |
| } |
| |
| if (!redfish::json_util::readJsonPatch( |
| req, asyncResp->res, "Oem/Google/MemTestBlob", memTestBlob)) |
| { |
| BMCWEB_LOG_ERROR << "There was no valid patch payload"; |
| redfish::messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| constexpr std::array<std::string_view, 1> systemInterfaces{ |
| {"xyz.openbmc_project.Inventory.Item.System"}}; |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); |
| managedStore::GetManagedObjectStore()->getSubTreePaths( |
| "/", 0, systemInterfaces, requestContext, |
| [asyncResp, systemName, |
| memTestBlob](const boost::system::error_code& ec, |
| const std::vector<std::string>& objects) { |
| if (ec) |
| { |
| BMCWEB_LOG_DEBUG << "DBUS response error"; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| std::string biosSettingFilepath = biosSettingFilepathFromConfig; |
| // If there are less than 2 Item.Systems assume singlehost |
| if (objects.size() < 2) |
| { |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| handleBiosSettingPatch(asyncResp, biosSettingFilepath, false, |
| memTestBlob); |
| return; |
| } |
| |
| // Otherwise find system, e.g. system1, system2 |
| for (const auto& object : objects) |
| { |
| std::string objectName = object.substr(object.rfind('/') + 1); |
| // Found system object |
| if (systemName == objectName) |
| { |
| // Format the actual file path based on systemName |
| std::string systemId = resourceIdFromNameString(systemName); |
| size_t posHost = biosSettingFilepath.find("host"); |
| if (posHost == std::string::npos) |
| { |
| BMCWEB_LOG_DEBUG |
| << "The BIOS setting file path template is invalid" |
| << biosSettingFilepath; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| // Get index |
| int systemIdInt = 0; |
| try |
| { |
| systemIdInt = std::stoi(systemId); |
| // system1 -> /run/soc_firmware/host0/vmtppr.cfg |
| // system2 -> /run/soc_firmware/host1/vmtppr.cfg |
| systemIdInt -= 1; |
| } |
| catch (const std::exception& ex) |
| { |
| BMCWEB_LOG_DEBUG |
| << "file to parse systemId " << systemId |
| << ":from string to int, exception:" << ex.what(); |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| |
| // insert the index right after "host" i.e. pos + 4 |
| biosSettingFilepath.insert(posHost + 4, |
| std::to_string(systemIdInt)); |
| |
| handleBiosSettingPatch(asyncResp, biosSettingFilepath, true, |
| memTestBlob, systemIdInt); |
| return; |
| } |
| } |
| |
| // Could not find system object |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| }); |
| } |
| |
| inline void requestRoutesBiosService(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Bios/") |
| .privileges(redfish::privileges::getBios) |
| .methods(boost::beast::http::verb::get)( |
| std::bind_front(handleBiosServiceGet, std::ref(app))); |
| |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Bios/") |
| .privileges(redfish::privileges::patchBios) |
| .methods(boost::beast::http::verb::patch)( |
| std::bind_front(patchBiosSettings, std::ref(app))); |
| } |
| |
| /** |
| * BiosReset class supports handle POST method for Reset bios. |
| * The class retrieves and sends data directly to D-Bus. |
| * |
| * Function handles POST method request. |
| * Analyzes POST body message before sends Reset request data to D-Bus. |
| */ |
| inline void |
| handleBiosResetPost(crow::App& app, const crow::Request& req, |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, |
| const std::string& systemName) |
| { |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) |
| { |
| return; |
| } |
| |
| if (systemName != "system") |
| { |
| messages::resourceNotFound(asyncResp->res, "ComputerSystem", |
| systemName); |
| return; |
| } |
| |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( |
| asyncResp->strand_, |
| [asyncResp](const boost::system::error_code& ec) { |
| if (ec) |
| { |
| BMCWEB_LOG_ERROR << "Failed to reset bios: " << ec; |
| messages::internalError(asyncResp->res); |
| return; |
| } |
| }, "org.open_power.Software.Host.Updater", "/xyz/openbmc_project/software", |
| "xyz.openbmc_project.Common.FactoryReset", "Reset"); |
| } |
| |
| inline void requestRoutesBiosReset(App& app) |
| { |
| BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Bios/Actions/Bios.ResetBios/") |
| .privileges(redfish::privileges::postBios) |
| .methods(boost::beast::http::verb::post)( |
| std::bind_front(handleBiosResetPost, std::ref(app))); |
| } |
| |
| } // namespace redfish |