blob: fc3325088915fef1828134b39f96c0c1e45f6639 [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "hoth_skmhss.hpp"
#include "config.hpp"
#include <fmt/format.h>
#include <google3/host_commands.h>
#include <blobs-ipmid/blobs.hpp>
#include <nlohmann/json.hpp>
#include <phosphor-logging/log.hpp>
#include <stdplus/cancel.hpp>
#include <xyz/openbmc_project/Control/Hoth/error.hpp>
#include <algorithm>
#include <charconv>
#include <cstring>
#include <fstream>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <vector>
using phosphor::logging::entry;
using phosphor::logging::log;
using level = phosphor::logging::level;
using SdBusError = sdbusplus::exception::SdBusError;
using CommandFailure =
sdbusplus::xyz::openbmc_project::Control::Hoth::Error::CommandFailure;
using ResponseFailure =
sdbusplus::xyz::openbmc_project::Control::Hoth::Error::ResponseFailure;
using json = nlohmann::json;
namespace ipmi_hoth
{
namespace internal
{
/**
* @brief: Get baseId from a blob id string
* @param blobId: Input blob id which is expected to only contain alphanumerical
* characters and '/'.
* @returns: The baseId containing the blobId, stripping all contents from the
* last '/'. If no '/' is present, an empty string is returned.
*/
static std::string_view getBaseFromID(std::string_view blobId)
{
return blobId.substr(0, blobId.find_last_of('/') + 1);
}
/**
* @brief: Get the blob identifer under the baseId
* @param blobId: Input blob id which is expected to only contain alphanumerical
* characters and '/'.
* @returns: The truncated blobId, by deleting the baseId prefix
* in the input blobId.
*/
static std::string_view stripBaseFromID(std::string_view blobId)
{
return blobId.substr(blobId.find_last_of('/') + 1);
}
/**
* @brief: Get the slot number from a blob id string
* @param blobId: Input blob id which is expected to only contain alphanumerical
* characters and '/'.
* @returns: The slot number if the blob id has a tailing integer; otherwise
* returns a null pointer;
*/
static std::optional<uint32_t> extractSlotFromID(std::string_view blobId)
{
std::string_view pathId = internal::stripBaseFromID(blobId);
uint32_t slot;
const auto res =
std::from_chars(pathId.data(), pathId.data() + pathId.size(), slot);
if (res.ec != std::errc() || res.ptr != pathId.data() + pathId.size())
{
return std::nullopt;
}
return slot;
}
} // namespace internal
void HothSKMHSSBlobHandler::loadHSS()
{
readBasePathConfig();
for (auto& [basePath, hothId] : hothIdBasePaths)
{
slotPopulated[basePath] = {};
inits[basePath] = {};
dels[basePath] = {};
/* Iterate all slots and find valid HSS copies */
for (uint32_t slot = 0; slot < maxNumHSSSlot; slot++)
{
auto& init = inits[basePath][slot];
init = readSKMHSSSlot(
basePath, slot, hothId,
[&](std::span<const uint8_t>) noexcept { init.reset(); });
}
}
}
/* read the hothid_basepaths.config and store it in hothIdBasePaths*/
/**
* @brief: Read the hothid_basepaths.config and store it in a bimap.
* It populates basePath <--> hothId mapping
* @param :
* @returns:
*/
void HothSKMHSSBlobHandler::readBasePathConfig()
{
std::ifstream configFile(HOTHID_CONFIG);
// Check if the config file exists
if (configFile.fail())
{
log<level::ERR>(
"config file does not exist, populate map with defaults");
// There is no config file,
// So, populate with default values
hothIdBasePaths.insert({"/skm/hss-backup/", ""});
return;
}
try
{
json data = json::parse(configFile);
for (const auto& [basePath, hothId] : data.items())
{
hothIdBasePaths.insert({basePath, hothId});
}
}
catch (json::parse_error& ex)
{
log<level::ERR>("config parse error at ", entry("ERR_MSG=%d", ex.byte));
}
}
/**
* @brief: Get the hothId from a blob id string
* @param blobId: Input blob id which is expected to only contain alphanumerical
* characters and '/'.
* @returns: The hothId from the map hothIdBasePaths if it exists; otherwise
* empty string
*/
std::string_view
HothSKMHSSBlobHandler::getHothIdFromPath(std::string_view blobId)
{
auto base = internal::getBaseFromID(blobId);
auto it = hothIdBasePaths.left.find(static_cast<std::string>(base));
if (it != hothIdBasePaths.left.end())
{
return it->second;
}
return "";
}
/**
* @brief: Get the base path from a hothId string
* @param hothId: Input hothId string
* @returns: The hothId from the bimap hothIdBasePaths if it exists; otherwise
* empty string
*/
std::string_view
HothSKMHSSBlobHandler::getBaseFromHothId(std::string_view hothId)
{
auto it = hothIdBasePaths.right.find(static_cast<std::string>(hothId));
if (it != hothIdBasePaths.right.end())
{
return it->second;
}
return "";
}
stdplus::Cancel HothSKMHSSBlobHandler::readSKMHSSSlot(
std::string_view base, uint32_t slot, std::string_view hothId,
fu2::unique_function<void(std::span<const uint8_t>)>&& cb)
{
auto req = hothUtil_->readSKMHSS(slot);
return dbus_->SendHostCommand(
hothId, req,
[this, base, slot, cb = std::move(cb)](
std::optional<std::vector<uint8_t>> rsp) mutable noexcept {
try
{
auto data = hothUtil_->payloadECResponse(rsp.value());
this->slotPopulated[base][slot] =
data.size() == SKM_HSS_STRUCT_SIZE;
cb(data);
return;
}
catch (const ResponseFailure& e)
{
log<level::ERR>("Invalid Hoth response",
entry("ERR_MSG=%s", e.what()));
}
catch (...)
{}
cb({});
});
}
bool HothSKMHSSBlobHandler::canHandleBlob(const std::string& path)
{
auto base = internal::getBaseFromID(path);
if (hothIdBasePaths.left.find(static_cast<std::string>(base)) ==
hothIdBasePaths.left.end())
{
return false;
}
auto hothId = getHothIdFromPath(path);
if (!dbus_->pingHothd(hothId))
{
return false;
}
/* The HSS path is expected to be "/skm/hss-backup/{0,1,2,...}" */
std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path);
if (!slotPtr)
{
return false;
}
return *slotPtr < maxNumHSSSlot;
}
std::vector<std::string> HothSKMHSSBlobHandler::getBlobIds()
{
std::vector<std::string> result;
for (auto& [basePath, hothId] : hothIdBasePaths)
{
if (!dbus_->pingHothd(hothId))
{
continue;
}
result.emplace_back(basePath);
for (size_t i = 0; i < slotPopulated[basePath].size(); ++i)
{
if (slotPopulated[basePath][i])
{
result.emplace_back(fmt::format("{}{}", basePath, i));
}
}
}
return result;
}
bool HothSKMHSSBlobHandler::stat(const std::string& path, blobs::BlobMeta* meta)
{
std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path);
auto base = internal::getBaseFromID(path);
if (!slotPtr)
{
return false;
}
*meta = {};
return slotPopulated[base][*slotPtr];
}
bool HothSKMHSSBlobHandler::open(uint16_t session, uint16_t flags,
const std::string& path)
{
std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path);
if (!slotPtr)
{
return false;
}
std::string_view hothId = getHothIdFromPath(path);
if (!HothBlobHandler::open(session, flags, hothIdToPath(hothId)))
{
return false;
}
std::string_view base = internal::getBaseFromID(path);
sessionSlot[base].emplace(session, *slotPtr);
HothBlob* blobIt = getSession(session);
if (!(blobIt->state & blobs::StateFlags::open_read))
{
return true;
}
auto finalState = blobIt->state;
blobIt->state = blobs::StateFlags::committing;
blobIt->outstanding = readSKMHSSSlot(
base, *slotPtr, hothId,
[blobIt, finalState](std::span<const uint8_t> data) noexcept {
auto outstanding = std::move(blobIt->outstanding);
if (data.size() == SKM_HSS_STRUCT_SIZE)
{
/* Set committed to denote that our buffer is consistent with
* the remote copy, making commits a no-op until a write changes
* it.
*/
blobIt->state = finalState | blobs::StateFlags::committed;
blobIt->buffer = std::vector<uint8_t>(data.begin(), data.end());
return;
}
blobIt->state = blobs::StateFlags::commit_error;
if (data.data() != nullptr &&
finalState & blobs::StateFlags::open_write)
{
/* If the call succeeded, but we were uninitialized we want to
* allow a write to continue accessing the blob. This requires
* us to populate the open_* flags that were originally set on
* the blob. We don't do this for RO blobs since they can't
* fill in valid data to read back.
*/
blobIt->state |= finalState;
}
});
return true;
}
bool HothSKMHSSBlobHandler::commit(uint16_t session,
const std::vector<uint8_t>& data)
{
if (!data.empty())
{
log<level::ERR>("Unexpected data provided to commit call");
return false;
}
HothBlob* blobIt = getSession(session);
if (!blobIt)
{
return false;
}
if (blobIt->state &
(blobs::StateFlags::committed | blobs::StateFlags::committing))
{
return true;
}
std::string_view base = getBaseFromHothId(blobIt->hothId);
/* Clear remaining commit bits */
blobIt->state &= ~blobs::StateFlags::commit_error;
uint32_t slot = sessionSlot[base][session];
std::vector<uint8_t> req;
try
{
req = hothUtil_->writeSKMHSS(slot, blobIt->buffer);
}
catch (const CommandFailure& e)
{
blobIt->state |= blobs::StateFlags::commit_error;
log<level::ERR>("Hoth command failed", entry("ERR_MSG=%s", e.what()));
return false;
}
blobIt->state |= blobs::StateFlags::committing;
blobIt->outstanding = dbus_->SendHostCommand(
blobIt->hothId, req,
[this, base, slot,
blobIt](std::optional<std::vector<uint8_t>> rsp) noexcept {
auto outstanding = std::move(blobIt->outstanding);
blobIt->state &= ~blobs::StateFlags::committing;
try
{
hothUtil_->payloadECResponse(rsp.value());
blobIt->state |= blobs::StateFlags::committed;
slotPopulated[base][slot] = true;
return;
}
catch (const ResponseFailure& e)
{
log<level::ERR>("Invalid Hoth response",
entry("ERR_MSG=%s", e.what()));
}
catch (...)
{}
blobIt->state |= blobs::StateFlags::commit_error;
});
return true;
}
bool HothSKMHSSBlobHandler::stat(uint16_t session, blobs::BlobMeta* meta)
{
HothBlob* blobIt = getSession(session);
if (!blobIt)
{
return false;
}
meta->size = blobIt->buffer.size();
meta->blobState = blobIt->state;
return true;
}
bool HothSKMHSSBlobHandler::deleteBlob(const std::string& path)
{
std::optional<uint32_t> slotPtr = internal::extractSlotFromID(path);
std::string_view hothId = getHothIdFromPath(path);
auto base = internal::getBaseFromID(path);
if (!slotPtr)
{
return false;
}
auto& del = dels[base][*slotPtr];
if (del)
{
return true;
}
auto& pop = slotPopulated[base][*slotPtr];
del = dbus_->SendHostCommand(
hothId, hothUtil_->deleteSKMHSS(*slotPtr),
[&pop, &del, this](std::optional<std::vector<uint8_t>> rsp) noexcept {
auto adel = std::move(del);
try
{
hothUtil_->payloadECResponse(rsp.value());
pop = false;
return;
}
catch (const ResponseFailure& e)
{
log<level::ERR>("Invalid Hoth response",
entry("ERR_MSG=%s", e.what()));
}
catch (...)
{}
});
return true;
}
bool HothSKMHSSBlobHandler::close(uint16_t session)
{
HothBlob* blobIt = getSession(session);
if (!blobIt)
{
return false;
}
std::string_view base = getBaseFromHothId(blobIt->hothId);
if (HothBlobHandler::close(session))
{
sessionSlot[base].erase(session);
return true;
}
return false;
}
} // namespace ipmi_hoth