blob: a17f73799bc3d1b757a421a093a3fd6ffc1c055b [file] [log] [blame]
#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