blob: ae475925b3a001530e577befc3b1406e8d5b2627 [file] [edit]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_BIOS_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_BIOS_H_
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <filesystem> // NOLINT
#include <fstream>
#include <functional>
#include <ios>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "bm_config.h"
#include "bmcweb_config.h" // NOLINT(misc-include-cleaner)
#include "app.hpp"
#include "http_request.hpp"
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "error_messages.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "json_utils.hpp"
#include "sw_utils.hpp"
#include "system_utils.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#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";
inline std::string biosSettingFilepathFromConfig = BIOS_SETTING_FILE_PATH;
inline 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, sw_util::SwAssociation::System,
systemName);
// 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 " << systemName
<< ":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);
#ifdef ENABLE_CONFIGURE_CORE_COUNT
// Get SystemRegistry from ManagedObjectStore
std::shared_ptr<milotic_tlbmc::SystemRegistry> registry =
managedStore::GetManagedObjectStore()->GetSystemRegistry();
if (registry == nullptr) {
BMCWEB_LOG_ERROR << "System Registry is Null";
messages::internalError(asyncResp->res);
return;
}
// Call GetCoreCount with the systemName
absl::StatusOr<uint32_t> coreCount = registry->GetCoreCount(systemName);
if (!coreCount.ok()) {
if (absl::IsUnimplemented(coreCount.status())) {
BMCWEB_LOG_DEBUG << "CoreCount is not implemented.";
return;
}
BMCWEB_LOG_ERROR << "GetCoreCount failed: " << coreCount.status();
messages::internalError(asyncResp->res);
return;
}
// Store the result in TotalEnabledCores
asyncResp->res.jsonValue["Oem"]["Google"]["TotalEnabledCores"] = *coreCount;
#endif
}
/**
* 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;
}
system_utils::findSystemPathAndTriggerCallback(
asyncResp, systemName, objects,
[&systemName](const std::shared_ptr<bmcweb::AsyncResp>& resp,
const std::string& objectPath) {
const bool multiHost = !objectPath.empty();
handleBios(resp, systemName, multiHost);
});
});
}
inline void handleBiosSettingPatch(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& biosSettingFilepath, bool multiHost,
std::optional<std::string> memTestBlob,
std::optional<uint32_t> totalEnabledCores,
[[maybe_unused]] const std::string& systemName, int systemId = 0) {
asyncResp->res.result(boost::beast::http::status::no_content);
if (!memTestBlob && !totalEnabledCores) {
BMCWEB_LOG_ERROR << "No valid patch parameter provided";
redfish::messages::actionParameterMissing(
asyncResp->res, "handleBiosSettingPatch",
"MemTestBlob or TotalEnabledCores");
return;
}
#ifdef ENABLE_CONFIGURE_CORE_COUNT
if (totalEnabledCores) {
std::shared_ptr<milotic_tlbmc::SystemRegistry> registry =
managedStore::GetManagedObjectStore()->GetSystemRegistry();
if (registry == nullptr) {
BMCWEB_LOG_ERROR << "SystemRegistry is null";
messages::internalError(asyncResp->res);
return;
}
absl::Status status =
registry->SetCoreCount(systemName, *totalEnabledCores);
if (!status.ok()) {
BMCWEB_LOG_ERROR << "SetCoreCount failed: " << status;
messages::internalError(asyncResp->res);
return;
}
}
#else
if (totalEnabledCores) {
BMCWEB_LOG_ERROR << "TotalEnabledCores is not supported";
messages::actionParameterNotSupported(asyncResp->res, "TotalEnabledCores",
"Configuring Core Count");
return;
}
#endif
if (memTestBlob) {
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;
}
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_,
absl::AnyInvocable<void(const boost::system::error_code&)>(
[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);
}
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;
std::optional<uint32_t> totalEnabledCores;
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,
"Oem/Google/TotalEnabledCores", totalEnabledCores)) {
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, totalEnabledCores](
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;
}
system_utils::findSystemPathAndTriggerCallback(
asyncResp, systemName, objects,
[&memTestBlob, &totalEnabledCores, &systemName](
const std::shared_ptr<bmcweb::AsyncResp>& resp,
const std::string& objectPath) {
std::string biosSettingFilepath = biosSettingFilepathFromConfig;
bool multiHost = !objectPath.empty();
int systemIdInt = 0;
if (multiHost) {
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(resp->res);
return;
}
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(resp->res);
return;
}
biosSettingFilepath.insert(posHost + 4,
std::to_string(systemIdInt));
}
handleBiosSettingPatch(resp, biosSettingFilepath, multiHost,
memTestBlob, totalEnabledCores, systemName,
systemIdInt);
});
});
}
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_,
absl::AnyInvocable<void(const boost::system::error_code&)>(
[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
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_BIOS_H_