blob: 883154eab9e72bb3af315a938ea591f292f93dc3 [file] [log] [blame]
#include "log_services.hpp"
#include "bmcweb_config.h"
#include "app.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "external_storer.hpp"
#include "generated/enums/log_entry.hpp"
#include "gzfile.hpp"
#include "http_utility.hpp"
#include "human_sort.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "query.hpp"
#include "redfish_util.hpp"
#include "registries.hpp"
#include "registries/base_message_registry.hpp"
#include "registries/openbmc_message_registry.hpp"
#include "registries/privilege_registry.hpp"
#include "task.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/system_utils.hpp"
#include "utils/time_utils.hpp"
#include <systemd/sd-journal.h>
#include <tinyxml2.h>
#include <unistd.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/system/linux_error.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/message/types.hpp>
#include <sdbusplus/unpack_properties.hpp>
#include <array>
#include <charconv>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <optional>
#include <span>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <variant>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish
{
#ifdef BMCWEB_ENABLE_RASMANAGER_EVENT_LOG
constexpr char const* rasManagerObject = "com.intel.RAS";
constexpr char const* rasManagerPath = "/com/intel/ras/logs";
#endif
constexpr char const* crashdumpObject =
bmcwebEnableAmd ? "com.amd.crashdump" : "com.intel.crashdump";
constexpr char const* crashdumpPath =
bmcwebEnableAmd ? "/com/amd/crashdump" : "/com/intel/crashdump";
constexpr char const* crashdumpInterface =
bmcwebEnableAmd ? "com.amd.crashdump" : "com.intel.crashdump";
constexpr char const* deleteAllInterface =
"xyz.openbmc_project.Collection.DeleteAll";
constexpr char const* crashdumpOnDemandInterface =
bmcwebEnableAmd ? "com.amd.crashdump.OnDemand"
: "com.intel.crashdump.OnDemand";
constexpr char const* crashdumpTelemetryInterface =
bmcwebEnableAmd ? "com.amd.crashdump.Telemetry"
: "com.intel.crashdump.Telemetry";
constexpr char const* pprFileInterface =
"xyz.openbmc_project.PostPackageRepair.PprData";
constexpr char const* pprFileObject = "xyz.openbmc_project.PostPackageRepair";
constexpr char const* pprFilePath = "/xyz/openbmc_project/PostPackageRepair";
constexpr char const* defaultLogPrefix = "log";
constexpr uint64_t kMaxRuntimePPRCount = 8;
constexpr uint64_t kPPRTypeBoottimeMask = 0x8000;
constexpr uint16_t kBtSetToHardMask = 0x01;
constexpr uint16_t kRtToBtMask = 0x02;
enum class DumpCreationProgress : std::uint8_t
{
DUMP_CREATE_SUCCESS,
DUMP_CREATE_FAILED,
DUMP_CREATE_INPROGRESS
};
std::string dumpTypeToString(DumpType dumpType)
{
return std::string(dumpTypeStrings[static_cast<size_t>(dumpType)]);
}
inline std::string bootTimeDataTypeToString(const BootTimeDataType dataType)
{
return std::string(bootTimeDataTypeString[static_cast<size_t>(dataType)]);
}
inline static int getJournalMetadata(sd_journal* journal,
std::string_view field,
std::string_view& contents)
{
const char* data = nullptr;
size_t length = 0;
int ret = 0;
// Get the metadata from the requested field of the journal entry
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
const void** dataVoid = reinterpret_cast<const void**>(&data);
ret = sd_journal_get_data(journal, field.data(), dataVoid, &length);
if (ret < 0)
{
return ret;
}
contents = std::string_view(data, length);
// Only use the content after the "=" character.
contents.remove_prefix(std::min(contents.find('=') + 1, contents.size()));
return ret;
}
inline static int getJournalMetadata(sd_journal* journal,
std::string_view field, const int& base,
int64_t& contents)
{
int ret = 0;
std::string_view metadata;
// Get the metadata from the requested field of the journal entry
ret = getJournalMetadata(journal, field, metadata);
if (ret < 0)
{
return ret;
}
contents = strtol(metadata.data(), nullptr, base);
return ret;
}
inline static bool getEntryTimestamp(sd_journal* journal,
std::string& entryTimestamp)
{
int ret = 0;
uint64_t timestamp = 0;
ret = sd_journal_get_realtime_usec(journal, &timestamp);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
<< strerror(-ret);
return false;
}
entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp);
return true;
}
inline static bool getUniqueEntryID(sd_journal* journal, std::string& entryID,
const bool firstEntry = true)
{
int ret = 0;
static uint64_t prevTs = 0;
static int index = 0;
if (firstEntry)
{
prevTs = 0;
}
// Get the entry timestamp
uint64_t curTs = 0;
ret = sd_journal_get_realtime_usec(journal, &curTs);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
<< strerror(-ret);
return false;
}
// If the timestamp isn't unique, increment the index
if (curTs == prevTs)
{
index++;
}
else
{
// Otherwise, reset it
index = 0;
}
// Save the timestamp
prevTs = curTs;
entryID = std::to_string(curTs);
if (index > 0)
{
entryID += "_" + std::to_string(index);
}
return true;
}
inline static bool
getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID, uint64_t& timestamp,
uint64_t& index)
{
if (entryID.empty())
{
return false;
}
// Convert the unique ID back to a timestamp to find the entry
std::string_view tsStr(entryID);
auto underscorePos = tsStr.find('_');
if (underscorePos != std::string_view::npos)
{
// Timestamp has an index
tsStr.remove_suffix(tsStr.size() - underscorePos);
std::string_view indexStr(entryID);
indexStr.remove_prefix(underscorePos + 1);
auto [ptr, ec] = std::from_chars(
indexStr.data(), indexStr.data() + indexStr.size(), index);
if (ec != std::errc())
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
return false;
}
}
// Timestamp has no index
auto [ptr, ec] =
std::from_chars(tsStr.data(), tsStr.data() + tsStr.size(), timestamp);
if (ec != std::errc())
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
return false;
}
return true;
}
inline log_entry::OriginatorTypes
mapDbusOriginatorTypeToRedfish(const std::string& originatorType)
{
if (originatorType ==
"xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client")
{
return log_entry::OriginatorTypes::Client;
}
if (originatorType ==
"xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Internal")
{
return log_entry::OriginatorTypes::Internal;
}
if (originatorType ==
"xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.SupportingService")
{
return log_entry::OriginatorTypes::SupportingService;
}
return log_entry::OriginatorTypes::Invalid;
}
inline void parseDumpEntryFromDbusObject(
const dbus::utility::ManagedObjectType::value_type& object,
std::string& dumpStatus, uint64_t& size, uint64_t& timestampUs,
std::string& originatorId, log_entry::OriginatorTypes& originatorType,
std::string& entryType, std::string& primaryLogId,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
for (const auto& interfaceMap : object.second)
{
if (interfaceMap.first == "xyz.openbmc_project.Common.Progress")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Status")
{
const auto* status =
std::get_if<std::string>(&propertyMap.second);
if (status == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
dumpStatus = *status;
}
}
}
else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Size")
{
const auto* sizePtr =
std::get_if<uint64_t>(&propertyMap.second);
if (sizePtr == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
size = *sizePtr;
break;
}
}
}
else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Elapsed")
{
const uint64_t* usecsTimeStamp =
std::get_if<uint64_t>(&propertyMap.second);
if (usecsTimeStamp == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
timestampUs = *usecsTimeStamp;
break;
}
}
}
else if (interfaceMap.first ==
"xyz.openbmc_project.Common.OriginatedBy")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "OriginatorId")
{
const std::string* id =
std::get_if<std::string>(&propertyMap.second);
if (id == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
originatorId = *id;
}
if (propertyMap.first == "OriginatorType")
{
const std::string* type =
std::get_if<std::string>(&propertyMap.second);
if (type == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
originatorType = mapDbusOriginatorTypeToRedfish(*type);
if (originatorType == log_entry::OriginatorTypes::Invalid)
{
messages::internalError(asyncResp->res);
break;
}
}
}
}
else if (interfaceMap.first ==
"xyz.openbmc_project.Dump.Entry.FaultLog")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Type")
{
const std::string* entryTypePtr =
std::get_if<std::string>(&propertyMap.second);
if (entryTypePtr == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
if (*entryTypePtr ==
"xyz.openbmc_project.Common.FaultLogType.FaultLogTypes.Crashdump")
{
entryType = "Crashdump";
}
else if (
*entryTypePtr ==
"xyz.openbmc_project.Common.FaultLogType.FaultLogTypes.CPER")
{
entryType = "CPER";
}
}
else if (propertyMap.first == "PrimaryLogId")
{
const std::string* primaryLogIdPtr =
std::get_if<std::string>(&propertyMap.second);
if (primaryLogIdPtr == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
primaryLogId = *primaryLogIdPtr;
}
}
}
}
}
static std::string getDumpEntriesPath(const Dump& dump)
{
std::string entriesPath;
switch (dump.type)
{
case DumpType::BMC_DUMP:
entriesPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
break;
case DumpType::BMC_FAULT_LOG:
entriesPath =
"/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/";
break;
case DumpType::SYSTEM_DUMP:
entriesPath =
"/redfish/v1/Systems/system/LogServices/Dump/Entries/";
break;
case DumpType::SYSTEM_FAULT_LOG:
if (dump.systemName)
{
entriesPath = "/redfish/v1/Systems/" + *dump.systemName +
"/LogServices/FaultLog/Entries/";
}
break;
}
// Returns empty string on error
return entriesPath;
}
constexpr std::array<std::pair<std::string_view, std::string_view>, 2>
systemNameToDumpEntryPath{
{{"system1", "/xyz/openbmc_project/dump/faultlog/0"},
{"system2", "/xyz/openbmc_project/dump/faultlog/1"}}};
inline std::optional<std::string> getDumpEntryPathFromDump(const Dump& dump)
{
if (!dump.systemName || *dump.systemName == "system")
{
return "/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(
dumpTypeToString(dump.type))) +
"/entry/";
}
for (const auto& mapping : systemNameToDumpEntryPath)
{
if (mapping.first == *dump.systemName)
{
return std::string(mapping.second) + "/entry/";
}
}
return std::nullopt;
}
// This function queries the collectionUri, and add the fields from the
// collectionUri to asyncResp. When the collectionUri response has less
// faultLog entries, the additional fields in asyncResp will be removed.
//
// This can happen during log rotation. We want to avoid incomplete
// data in asyncResp in the middle of log rotation.
void queryFaultCollection(
App& app, const crow::Request& origReq,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& collectionUri,
std::unordered_map<std::string, nlohmann::json::json_pointer>&
faultLogEntries,
nlohmann::json& originalMembers)
{
std::error_code ec;
crow::Request newReq({boost::beast::http::verb::get, collectionUri, 11},
ec);
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
newReq.with_trust_bundle = origReq.with_trust_bundle;
newReq.peer_authenticated = origReq.peer_authenticated;
newReq.peer_privileges = origReq.peer_privileges;
newReq.fromGrpc = origReq.fromGrpc;
auto newAsyncResp = std::make_shared<bmcweb::AsyncResp>(asyncResp->strand_);
newAsyncResp->response_type = asyncResp->response_type;
newAsyncResp->res.setCompleteRequestHandler(
[faultLogEntries{std::move(faultLogEntries)},
originalMembers(std::move(originalMembers)),
asyncResp](const crow::Response& subResp) mutable {
if (!subResp.jsonValue.contains("Members"))
{
BMCWEB_LOG_WARNING << "Sub query response is empty.";
return;
}
for (const auto& resItem : subResp.jsonValue["Members"])
{
const std::string link = resItem["@odata.id"];
if (!faultLogEntries.contains(link))
{
// This could happen when there is inconsistency in cache.
// Ignoring this specific entry.
BMCWEB_LOG_WARNING << "Entry doesn't exist in response: "
<< link;
continue;
}
// This path is within "Members". Since it has been moved to a
// separate jsonArray, it looks like '/0/Links/RelatedLogEntries/0'
// here.
const std::string& jsonPath = faultLogEntries.at(link).to_string();
std::vector<std::string> split;
bmcweb::split(split, jsonPath, '/');
if (split.size() < 2)
{
BMCWEB_LOG_WARNING << "Invalid JSON path for entry " << link;
continue;
}
unsigned int memberIdx =
static_cast<unsigned int>(std::stoi(split[1]));
originalMembers[faultLogEntries.at(link)] = resItem;
asyncResp->res.jsonValue["Members"].push_back(
std::move(originalMembers[""_json_pointer / memberIdx]));
}
asyncResp->res.jsonValue["Members@odata.count"] =
asyncResp->res.jsonValue["Members"].size();
asyncResp->res.eventSourceIds.insert(
asyncResp->res.eventSourceIds.end(), subResp.eventSourceIds.begin(),
subResp.eventSourceIds.end());
});
app.handle(newReq, newAsyncResp);
redfish::query_param::propogateError(asyncResp->res, newAsyncResp->res);
}
inline bool shouldEfficientExpandFaultLog(
DumpType dumpType,
uint64_t delegatedExpandLevel)
{
// We only apply efficient expand when delegated expand level is 2+,
// as log services are auto-expanded.
// And only BMC_FAULT_LOG and SYSTEM_FAULT_LOG is supported.
return (dumpType == DumpType::BMC_FAULT_LOG ||
dumpType == DumpType::SYSTEM_FAULT_LOG) &&
delegatedExpandLevel > 1;
}
void getDumpEntryCollection(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const Dump& dump,
const query_param::Query& delegatedQuery)
{
std::string entriesPath = getDumpEntriesPath(dump);
if (entriesPath.empty())
{
messages::internalError(asyncResp->res);
return;
}
uint64_t filterTimestampUs = 0;
uint64_t delegatedExpandLevel = delegatedQuery.expandLevel;
if (!delegatedQuery.filter.empty())
{
std::vector<std::string> filterParams;
bmcweb::split(filterParams, delegatedQuery.filter, ' ');
// Only supported filter query is "Created ge '<timestamp>'"
if (filterParams.size() != 3 || filterParams[0] != "Created" ||
filterParams[1] != "ge")
{
redfish::messages::queryParameterValueFormatError(
asyncResp->res, "$filter", delegatedQuery.filter);
asyncResp->res.result(boost::beast::http::status::not_implemented);
return;
}
std::string& timestampStr = filterParams[2];
if (timestampStr.size() < 2 || timestampStr.at(0) != '\'' ||
timestampStr.at(timestampStr.size() - 1) != '\'')
{
BMCWEB_LOG_ERROR << "Filter Timestamp not quoted " << timestampStr;
redfish::messages::queryParameterValueFormatError(
asyncResp->res, "$filter", delegatedQuery.filter);
return;
}
std::optional<redfish::time_utils::usSinceEpoch>
filterTimestampUsChrono = redfish::time_utils::dateStringToEpoch(
timestampStr.substr(1, timestampStr.size() - 2));
if (filterTimestampUsChrono == std::nullopt)
{
BMCWEB_LOG_ERROR << "Filter Timestamp validation failure"
<< filterParams[2];
redfish::messages::queryParameterValueFormatError(
asyncResp->res, "$filter", delegatedQuery.filter);
return;
}
filterTimestampUs = filterTimestampUsChrono->count();
}
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getManagedObjectsWithContext(
"xyz.openbmc_project.Dump.Manager", {"/xyz/openbmc_project/dump"},
context,
// App's lifespan is tied with the program as it is created in the main
// function
[&app, asyncResp, entriesPath, dump, filterTimestampUs, req,
delegatedExpandLevel](
const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& managedObjects) {
if (ec)
{
BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
// Remove ending slash
std::string odataIdStr = entriesPath;
if (!odataIdStr.empty())
{
odataIdStr.pop_back();
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] = std::move(odataIdStr);
asyncResp->res.jsonValue["Name"] =
dumpTypeToString(dump.type) + " Dump Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of " + dumpTypeToString(dump.type) + " Dump Entries";
nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
entriesArray = nlohmann::json::array();
std::optional<std::string> dumpEntryPath =
getDumpEntryPathFromDump(dump);
if (!dumpEntryPath)
{
BMCWEB_LOG_ERROR << "Cant convert system "
<< dump.systemName.value_or("None")
<< "to dumpEntryPath";
messages::internalError(asyncResp->res);
return;
}
dbus::utility::ManagedObjectType resp = managedObjects;
std::sort(resp.begin(), resp.end(), [](const auto& l, const auto& r) {
return AlphanumLess<std::string>()(l.first.filename(),
r.first.filename());
});
// This maps the collection URI to all the entries and their json
// pointer. This is used to fill in the entries in the final response.
// Example:
// /redfish/v1/Systems/system1/LogServices/HostCper/Entries ->
// /redfish/v1/Systems/system1/LogServices/HostCper/Entries/1 ->
// /Members/Links/RelatedLogEntries/0
std::unordered_map<
std::string,
std::unordered_map<std::string, nlohmann::json::json_pointer>>
faultLogEntries;
// This maps the collection URI to all the original members.
// Example:
// /redfish/v1/Systems/system1/LogServices/HostCper/Entries ->
// [
// {
// "@odata.id": "../FaultLog/Entries/1",
// }
// ]
std::unordered_map<
std::string,
nlohmann::json> faultLogOriginalMembers;
for (auto& object : resp)
{
if (object.first.str.find(*dumpEntryPath) == std::string::npos)
{
continue;
}
uint64_t timestampUs = 0;
uint64_t size = 0;
std::string dumpStatus;
std::string originatorId;
std::string primaryLogId;
std::string entryType;
log_entry::OriginatorTypes originatorType =
log_entry::OriginatorTypes::Internal;
nlohmann::json::object_t thisEntry;
std::string entryID = object.first.filename();
if (entryID.empty())
{
continue;
}
parseDumpEntryFromDbusObject(object, dumpStatus, size, timestampUs,
originatorId, originatorType,
entryType, primaryLogId, asyncResp);
if (dumpStatus !=
"xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
!dumpStatus.empty())
{
// Dump status is not Complete, no need to enumerate
continue;
}
if (timestampUs < filterTimestampUs)
{
continue;
}
thisEntry["@odata.type"] = "#LogEntry.v1_11_0.LogEntry";
thisEntry["@odata.id"] = entriesPath + entryID;
thisEntry["Id"] = entryID;
thisEntry["EntryType"] = "Event";
thisEntry["Name"] = dumpTypeToString(dump.type) + " Dump Entry";
thisEntry["Created"] =
redfish::time_utils::getDateTimeUintUs(timestampUs);
if (!originatorId.empty())
{
thisEntry["Originator"] = originatorId;
thisEntry["OriginatorType"] = originatorType;
}
if (dump.type == DumpType::BMC_DUMP)
{
thisEntry["DiagnosticDataType"] = "Manager";
thisEntry["AdditionalDataURI"] =
entriesPath + entryID + "/attachment";
thisEntry["AdditionalDataSizeBytes"] = size;
}
else if (dump.type == DumpType::BMC_FAULT_LOG ||
dump.type == DumpType::SYSTEM_FAULT_LOG)
{
thisEntry["Created"] =
redfish::time_utils::getDateTimeUintUs(timestampUs);
thisEntry["DiagnosticDataType"] = "OEM";
thisEntry["OEMDiagnosticDataType"] = "OpenBMC Fault Log";
thisEntry["EntryType"] = "Oem";
std::string systemPathSegment =
dump.systemName ? *dump.systemName : "system";
if (entryType == "CPER")
{
if (absl::StrContains(primaryLogId, "/Entries/"))
{
// This is for backward compatibility; we can remove it
// later.
thisEntry["AdditionalDataURI"] =
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices", primaryLogId);
}
else
{
// Will eventually be changed from AdditionalDataURI to
// OriginOfCondition Link
thisEntry["AdditionalDataURI"] =
#ifdef BMCWEB_ENABLE_CPER_CRASHDUMP_USE_FAULTLOG_ID
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices",
std::string(hostCperLogServiceName), "Entries",
entryID);
#else
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices",
std::string(hostCperLogServiceName), "Entries",
primaryLogId);
#endif
}
thisEntry["OemRecordFormat"] = "CPER";
}
else if (entryType == "Crashdump")
{
thisEntry["AdditionalDataURI"] =
#ifdef BMCWEB_ENABLE_CPER_CRASHDUMP_USE_FAULTLOG_ID
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices", "Crashdump", "Entries", entryID);
#else
crow::utility::urlFromPieces("redfish", "v1", "Systems",
systemPathSegment,
"LogServices", "Crashdump",
"Entries", primaryLogId);
#endif
thisEntry["OemRecordFormat"] = "Crashdump";
}
// entryType must be of CPER or CrashDump or else accessing
// thisEntry["AdditionalDataURI"] becomes undefined behavior
else
{
BMCWEB_LOG_ERROR << "entryType of faultLog is invalid";
messages::internalError(asyncResp->res);
return;
}
// Also export a RelatedLogEntry link to the AdditionalDataURI.
thisEntry["Links"]["RelatedLogEntries"] =
nlohmann::json::array();
nlohmann::json::object_t relatedLogEntry;
relatedLogEntry["@odata.id"] = thisEntry["AdditionalDataURI"];
thisEntry["Links"]["RelatedLogEntries"].emplace_back(
relatedLogEntry);
}
else if (dump.type == DumpType::SYSTEM_DUMP)
{
thisEntry["DiagnosticDataType"] = "OEM";
thisEntry["OEMDiagnosticDataType"] = "System";
thisEntry["AdditionalDataURI"] =
entriesPath + entryID + "/attachment";
thisEntry["AdditionalDataSizeBytes"] = size;
}
if (shouldEfficientExpandFaultLog(dump.type, delegatedExpandLevel))
{
std::string dataUri = thisEntry["AdditionalDataURI"];
std::string collectionUri =
dataUri.substr(0, dataUri.rfind('/') + 1);
if (!faultLogEntries.contains(collectionUri))
{
faultLogEntries.emplace(
collectionUri,
std::unordered_map<std::string,
nlohmann::json::json_pointer>());
faultLogOriginalMembers.emplace(
collectionUri,
nlohmann::json::array());
}
auto jsonPath = ""_json_pointer /
faultLogOriginalMembers[collectionUri].size() / "Links" /
"RelatedLogEntries" /
(thisEntry["Links"]["RelatedLogEntries"].size() - 1);
faultLogEntries[collectionUri].emplace(dataUri, jsonPath);
faultLogOriginalMembers[collectionUri].push_back(std::move(thisEntry));
}
else
{
entriesArray.push_back(std::move(thisEntry));
}
}
asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
if (shouldEfficientExpandFaultLog(dump.type, delegatedExpandLevel))
{
// faultLogEntries and faultLogOriginamMemobers will be moved
// into the completion handler of sub queries in
// queryFaultCollection. It won't be availabe in this scope after
// this loop.
for (auto& [collectionUri, uriJsonPaths] : faultLogEntries)
{
nlohmann::json& originalMembers =
faultLogOriginalMembers[collectionUri];
queryFaultCollection(app, req, asyncResp, collectionUri,
uriJsonPaths, originalMembers);
}
}
});
}
inline void
getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID, const Dump& dump)
{
std::string entriesPath = getDumpEntriesPath(dump);
if (entriesPath.empty())
{
messages::internalError(asyncResp->res);
return;
}
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getManagedObjectsWithContext(
"xyz.openbmc_project.Dump.Manager", {"/xyz/openbmc_project/dump"},
context,
[asyncResp, entryID, dump,
entriesPath](const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& resp) {
if (ec)
{
BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
bool foundDumpEntry = false;
std::optional<std::string> dumpEntryPath =
getDumpEntryPathFromDump(dump);
if (!dumpEntryPath)
{
BMCWEB_LOG_ERROR << "Cant convert system "
<< dump.systemName.value_or("None")
<< "to dumpEntryPath";
messages::internalError(asyncResp->res);
return;
}
for (const auto& objectPath : resp)
{
if (objectPath.first.str != *dumpEntryPath + entryID)
{
continue;
}
foundDumpEntry = true;
uint64_t timestampUs = 0;
uint64_t size = 0;
std::string dumpStatus;
std::string originatorId;
std::string primaryLogId;
std::string entryType;
log_entry::OriginatorTypes originatorType =
log_entry::OriginatorTypes::Internal;
parseDumpEntryFromDbusObject(
objectPath, dumpStatus, size, timestampUs, originatorId,
originatorType, entryType, primaryLogId, asyncResp);
if (dumpStatus !=
"xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
!dumpStatus.empty())
{
// Dump status is not Complete
// return not found until status is changed to Completed
messages::resourceNotFound(
asyncResp->res, dumpTypeToString(dump.type) + " dump",
entryID);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntry.v1_11_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] = entriesPath + entryID;
asyncResp->res.jsonValue["Id"] = entryID;
asyncResp->res.jsonValue["EntryType"] = "Event";
asyncResp->res.jsonValue["Name"] =
dumpTypeToString(dump.type) + " Dump Entry";
asyncResp->res.jsonValue["Created"] =
redfish::time_utils::getDateTimeUintUs(timestampUs);
if (!originatorId.empty())
{
asyncResp->res.jsonValue["Originator"] = originatorId;
asyncResp->res.jsonValue["OriginatorType"] = originatorType;
}
if (dump.type == DumpType::BMC_DUMP)
{
asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager";
asyncResp->res.jsonValue["AdditionalDataURI"] =
entriesPath + entryID + "/attachment";
asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
}
else if (dump.type == DumpType::BMC_FAULT_LOG ||
dump.type == DumpType::SYSTEM_FAULT_LOG)
{
asyncResp->res.jsonValue["Created"] =
redfish::time_utils::getDateTimeUintUs(timestampUs);
asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
asyncResp->res.jsonValue["OEMDiagnosticDataType"] =
"OpenBMC Fault Log";
asyncResp->res.jsonValue["EntryType"] = "Oem";
std::string systemPathSegment =
dump.systemName ? *dump.systemName : "system";
if (entryType == "CPER")
{
if (absl::StrContains(primaryLogId, "/Entries/"))
{
// This is for backward compatibility; we can remove it
// later.
asyncResp->res.jsonValue["AdditionalDataURI"] =
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices", primaryLogId);
}
else
{
// Will eventually be changed from AdditionalDataURI to
// OriginOfCondition Link
asyncResp->res.jsonValue["AdditionalDataURI"] =
#ifdef BMCWEB_ENABLE_CPER_CRASHDUMP_USE_FAULTLOG_ID
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices",
std::string(hostCperLogServiceName), "Entries",
entryID);
#else
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices",
std::string(hostCperLogServiceName), "Entries",
primaryLogId);
#endif
}
asyncResp->res.jsonValue["OemRecordFormat"] = "CPER";
}
else if (entryType == "Crashdump")
{
asyncResp->res.jsonValue["AdditionalDataURI"] =
#ifdef BMCWEB_ENABLE_CPER_CRASHDUMP_USE_FAULTLOG_ID
crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemPathSegment,
"LogServices", "Crashdump", "Entries", entryID);
#else
crow::utility::urlFromPieces("redfish", "v1", "Systems",
systemPathSegment,
"LogServices", "Crashdump",
"Entries", primaryLogId);
#endif
asyncResp->res.jsonValue["OemRecordFormat"] = "Crashdump";
}
// entryType must be of CPER or CrashDump or else accessing
// thisEntry["AdditionalDataURI"] becomes undefined behavior
else
{
BMCWEB_LOG_ERROR << "entryType of faultLog is invalid";
messages::internalError(asyncResp->res);
return;
}
// Also export a RelatedLogEntry link to the AdditionalDataURI.
asyncResp->res.jsonValue["Links"]["RelatedLogEntries"] =
nlohmann::json::array();
nlohmann::json::object_t relatedLogEntry;
relatedLogEntry["@odata.id"] =
asyncResp->res.jsonValue["AdditionalDataURI"];
asyncResp->res.jsonValue["Links"]["RelatedLogEntries"]
.emplace_back(relatedLogEntry);
}
else if (dump.type == DumpType::SYSTEM_DUMP)
{
asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
asyncResp->res.jsonValue["OEMDiagnosticDataType"] = "System";
asyncResp->res.jsonValue["AdditionalDataURI"] =
entriesPath + entryID + "/attachment";
asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
}
}
if (!foundDumpEntry)
{
BMCWEB_LOG_ERROR << "Can't find Dump Entry";
messages::internalError(asyncResp->res);
return;
}
});
}
inline void deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID, const Dump& dump)
{
auto respHandler = [asyncResp,
entryID](const boost::system::error_code& ec) {
BMCWEB_LOG_DEBUG << "Dump Entry doDelete callback: Done";
if (ec)
{
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
return;
}
BMCWEB_LOG_ERROR << "Dump (DBus) doDelete respHandler got error "
<< ec << " entryID=" << entryID;
messages::internalError(asyncResp->res);
return;
}
};
std::optional<std::string> dumpEntryPath = getDumpEntryPathFromDump(dump);
if (!dumpEntryPath)
{
BMCWEB_LOG_ERROR << "Cant convert system "
<< dump.systemName.value_or("None")
<< "to dumpEntryPath";
messages::internalError(asyncResp->res);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
respHandler, "xyz.openbmc_project.Dump.Manager",
*dumpEntryPath + entryID, "xyz.openbmc_project.Object.Delete",
"Delete");
}
constexpr const char* dumpsFolderBasePath =
"/var/lib/phosphor-debug-collector/dumps";
inline bool getDumpFiles(crow::Response& resp, const std::string& dumpFilePath,
std::vector<std::filesystem::path>& dumpFiles)
{
std::error_code ec;
std::filesystem::directory_iterator logPath(dumpFilePath, ec);
if (ec)
{
BMCWEB_LOG_ERROR << ec.message();
if (ec.value() == ENOENT)
{
// The directory didn't exist
messages::resourceNotFound(resp, "", "");
}
else
{
messages::internalError(resp);
}
return false;
}
for (const std::filesystem::directory_entry& it : logPath)
{
std::string filename = it.path().filename();
dumpFiles.emplace_back(it.path());
}
return true;
}
inline void
downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID, const Dump& dump)
{
if (dump.type != DumpType::BMC_DUMP)
{
// We don't know for sure what resource was originally queried
messages::resourceNotFound(asyncResp->res, "", "");
return;
}
std::string dumpFolderPath = dumpsFolderBasePath;
dumpFolderPath += "/" + entryID;
std::vector<std::filesystem::path> dumpFiles;
if (!getDumpFiles(asyncResp->res, dumpFolderPath, dumpFiles))
{
// getDumpFiles() will have already written the error to the response
BMCWEB_LOG_ERROR << "failed to get host log file path";
return;
}
if (dumpFiles.size() != 1)
{
BMCWEB_LOG_ERROR << "Expected 1 dump file, found " << dumpFiles.size()
<< " dump files";
messages::internalError(asyncResp->res);
return;
}
const std::string& dumpFile = dumpFiles.front().string();
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
int fd = open(dumpFile.c_str(), O_RDONLY);
if (fd < 0)
{
BMCWEB_LOG_ERROR << "Failed to open dump file!";
messages::internalError(asyncResp->res);
return;
}
int64_t size = lseek(fd, 0, SEEK_END);
if (size == -1)
{
messages::internalError(asyncResp->res);
return;
}
// Arbitrary max size of 8MB
constexpr int maxFileSize = 8 * 1024 * 1024;
if (size > maxFileSize)
{
BMCWEB_LOG_ERROR << "File size " << size
<< " exceeds maximum allowed size of " << maxFileSize;
messages::internalError(asyncResp->res);
}
std::vector<char> data(static_cast<size_t>(size));
int64_t rc = lseek(fd, 0, SEEK_SET);
if (rc == -1)
{
messages::internalError(asyncResp->res);
return;
}
rc = read(fd, data.data(), data.size());
if ((rc == -1) || (rc != size))
{
messages::internalError(asyncResp->res);
return;
}
close(fd);
std::string_view strData(data.data(), data.size());
std::string output(strData);
asyncResp->res.addHeader(boost::beast::http::field::content_type,
"application/octet-stream");
asyncResp->res.body() = std::move(output);
}
inline DumpCreationProgress
mapDbusStatusToDumpProgress(const std::string& status)
{
if (status ==
"xyz.openbmc_project.Common.Progress.OperationStatus.Failed" ||
status == "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted")
{
return DumpCreationProgress::DUMP_CREATE_FAILED;
}
if (status ==
"xyz.openbmc_project.Common.Progress.OperationStatus.Completed")
{
return DumpCreationProgress::DUMP_CREATE_SUCCESS;
}
return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
}
inline DumpCreationProgress
getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap& values)
{
for (const auto& [key, val] : values)
{
if (key == "Status")
{
const std::string* value = std::get_if<std::string>(&val);
if (value == nullptr)
{
BMCWEB_LOG_ERROR << "Status property value is null";
return DumpCreationProgress::DUMP_CREATE_FAILED;
}
return mapDbusStatusToDumpProgress(*value);
}
}
return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
}
inline std::string getDumpEntryPath(const std::string& dumpPath)
{
if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry")
{
return "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
}
if (dumpPath == "/xyz/openbmc_project/dump/system/entry")
{
return "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
}
return "";
}
inline void createDumpTaskCallback(
task::Payload&& payload,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const sdbusplus::message::object_path& createdObjPath)
{
const std::string dumpPath = createdObjPath.parent_path().str;
const std::string dumpId = createdObjPath.filename();
std::string dumpEntryPath = getDumpEntryPath(dumpPath);
if (dumpEntryPath.empty())
{
BMCWEB_LOG_ERROR << "Invalid dump type received";
messages::internalError(asyncResp->res);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, payload, createdObjPath,
dumpEntryPath{std::move(dumpEntryPath)},
dumpId](const boost::system::error_code& ec,
const std::string& introspectXml) {
if (ec)
{
BMCWEB_LOG_ERROR << "Introspect call failed with error: "
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
// Check if the created dump object has implemented Progress
// interface to track dump completion. If yes, fetch the "Status"
// property of the interface, modify the task state accordingly.
// Else, return task completed.
tinyxml2::XMLDocument doc;
doc.Parse(introspectXml.data(), introspectXml.size());
tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
if (pRoot == nullptr)
{
BMCWEB_LOG_ERROR << "XML document failed to parse";
messages::internalError(asyncResp->res);
return;
}
tinyxml2::XMLElement* interfaceNode =
pRoot->FirstChildElement("interface");
bool isProgressIntfPresent = false;
while (interfaceNode != nullptr)
{
const char* thisInterfaceName = interfaceNode->Attribute("name");
if (thisInterfaceName != nullptr)
{
if (thisInterfaceName ==
std::string_view("xyz.openbmc_project.Common.Progress"))
{
interfaceNode =
interfaceNode->NextSiblingElement("interface");
continue;
}
isProgressIntfPresent = true;
break;
}
interfaceNode = interfaceNode->NextSiblingElement("interface");
}
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[createdObjPath, dumpEntryPath, dumpId, isProgressIntfPresent](
const boost::system::error_code& err, sdbusplus::message_t& msg,
const std::shared_ptr<task::TaskData>& taskData) {
if (err)
{
BMCWEB_LOG_ERROR << createdObjPath.str
<< ": Error in creating dump";
taskData->messages.emplace_back(messages::internalError());
taskData->state = "Cancelled";
return task::completed;
}
if (isProgressIntfPresent)
{
dbus::utility::DBusPropertiesMap values;
std::string prop;
msg.read(prop, values);
DumpCreationProgress dumpStatus =
getDumpCompletionStatus(values);
if (dumpStatus == DumpCreationProgress::DUMP_CREATE_FAILED)
{
BMCWEB_LOG_ERROR << createdObjPath.str
<< ": Error in creating dump";
taskData->state = "Cancelled";
return task::completed;
}
if (dumpStatus == DumpCreationProgress::DUMP_CREATE_INPROGRESS)
{
BMCWEB_LOG_DEBUG << createdObjPath.str
<< ": Dump creation task is in progress";
return !task::completed;
}
}
nlohmann::json retMessage = messages::success();
taskData->messages.emplace_back(retMessage);
std::string headerLoc =
"Location: " + dumpEntryPath + http_helpers::urlEncode(dumpId);
taskData->payload->httpHeaders.emplace_back(std::move(headerLoc));
BMCWEB_LOG_DEBUG << createdObjPath.str
<< ": Dump creation task completed";
taskData->state = "Completed";
return task::completed;
},
"type='signal',interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',path='" +
createdObjPath.str + "'");
// The task timer is set to max time limit within which the
// requested dump will be collected.
task->startTimer(std::chrono::minutes(6));
task->populateResp(asyncResp->res);
task->payload.emplace(payload);
},
"xyz.openbmc_project.Dump.Manager", createdObjPath,
"org.freedesktop.DBus.Introspectable", "Introspect");
}
inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const crow::Request& req, const Dump& dump)
{
std::string dumpPath = getDumpEntriesPath(dump);
if (dumpPath.empty())
{
messages::internalError(asyncResp->res);
return;
}
std::optional<std::string> diagnosticDataType;
std::optional<std::string> oemDiagnosticDataType;
if (!redfish::json_util::readJsonAction(
req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
"OEMDiagnosticDataType", oemDiagnosticDataType))
{
return;
}
if (dump.type == DumpType::SYSTEM_DUMP)
{
if (!oemDiagnosticDataType || !diagnosticDataType)
{
BMCWEB_LOG_ERROR
<< "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!";
messages::actionParameterMissing(
asyncResp->res, "CollectDiagnosticData",
"DiagnosticDataType & OEMDiagnosticDataType");
return;
}
if ((*oemDiagnosticDataType != "System") ||
(*diagnosticDataType != "OEM"))
{
BMCWEB_LOG_ERROR << "Wrong parameter values passed";
messages::internalError(asyncResp->res);
return;
}
dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/";
}
else if (dump.type == DumpType::BMC_DUMP)
{
if (!diagnosticDataType)
{
BMCWEB_LOG_ERROR
<< "CreateDump action parameter 'DiagnosticDataType' not found!";
messages::actionParameterMissing(
asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
return;
}
if (*diagnosticDataType != "Manager")
{
BMCWEB_LOG_ERROR
<< "Wrong parameter value passed for 'DiagnosticDataType'";
messages::internalError(asyncResp->res);
return;
}
dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/";
}
else
{
BMCWEB_LOG_ERROR << "CreateDump failed. Unknown dump type";
messages::internalError(asyncResp->res);
return;
}
std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>>
createDumpParamVec;
if (req.session != nullptr)
{
createDumpParamVec.emplace_back(
"xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorId",
req.session->clientIp);
createDumpParamVec.emplace_back(
"xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorType",
"xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client");
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, payload(task::Payload(req)),
dumpPath](const boost::system::error_code& ec,
const sdbusplus::message_t& msg,
const sdbusplus::message::object_path& objPath) mutable {
if (ec)
{
BMCWEB_LOG_ERROR << "CreateDump resp_handler got error " << ec;
const sd_bus_error* dbusError = msg.get_error();
if (dbusError == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_ERROR << "CreateDump DBus error: " << dbusError->name
<< " and error msg: " << dbusError->message;
if (std::string_view(
"xyz.openbmc_project.Common.Error.NotAllowed") ==
dbusError->name)
{
messages::resourceInStandby(asyncResp->res);
return;
}
if (std::string_view(
"xyz.openbmc_project.Dump.Create.Error.Disabled") ==
dbusError->name)
{
messages::serviceDisabled(asyncResp->res, dumpPath);
return;
}
if (std::string_view(
"xyz.openbmc_project.Common.Error.Unavailable") ==
dbusError->name)
{
messages::resourceInUse(asyncResp->res);
return;
}
// Other Dbus errors such as:
// xyz.openbmc_project.Common.Error.InvalidArgument &
// org.freedesktop.DBus.Error.InvalidArgs are all related to
// the dbus call that is made here in the bmcweb
// implementation and has nothing to do with the client's
// input in the request. Hence, returning internal error
// back to the client.
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "Dump Created. Path: " << objPath.str;
createDumpTaskCallback(std::move(payload), asyncResp, objPath);
},
"xyz.openbmc_project.Dump.Manager",
"/xyz/openbmc_project/dump/" +
std::string(
boost::algorithm::to_lower_copy(dumpTypeToString(dump.type))),
"xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec);
}
inline std::optional<std::string> getDumpManagerCollectionPath(const Dump& dump)
{
if (!dump.systemName || *dump.systemName == "system")
{
return "/xyz/openbmc_project/dump/" +
std::string(boost::algorithm::to_lower_copy(
dumpTypeToString(dump.type)));
}
for (const auto& mapping : systemNameToDumpEntryPath)
{
if (mapping.first == *dump.systemName)
{
return std::string(mapping.second);
}
}
return std::nullopt;
}
inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const Dump& dump)
{
std::optional<std::string> dumpManagerCollectionPath =
getDumpManagerCollectionPath(dump);
if (!dumpManagerCollectionPath)
{
BMCWEB_LOG_ERROR << "Cant get dumpManagerCollectionPath from system "
<< dump.systemName.value_or("none");
messages::internalError(asyncResp->res);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "clearDump resp_handler got error " << ec;
messages::internalError(asyncResp->res);
return;
}
}, "xyz.openbmc_project.Dump.Manager", *dumpManagerCollectionPath,
"xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
}
inline static void
parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap& params,
std::string& filename, std::string& timestamp,
std::string& logfile)
{
const std::string* filenamePtr = nullptr;
const std::string* timestampPtr = nullptr;
const std::string* logfilePtr = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), params, "Timestamp", timestampPtr,
"Filename", filenamePtr, "Log", logfilePtr);
if (!success)
{
return;
}
if (filenamePtr != nullptr)
{
filename = *filenamePtr;
}
if (timestampPtr != nullptr)
{
timestamp = *timestampPtr;
}
if (logfilePtr != nullptr)
{
logfile = *logfilePtr;
}
}
inline void
handleLogServices(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemPath)
{
// If the systemPath is empty, then the system is single host
bool multiHost = !systemPath.empty();
// Default systemName is system and change if system is multi-host
std::string systemName = "system";
if (multiHost)
{
systemName = sdbusplus::message::object_path(systemPath).filename();
}
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogServiceCollection.LogServiceCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices";
asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
asyncResp->res.jsonValue["Description"] =
"Collection of LogServices for this Computer System";
nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
logServiceArray = nlohmann::json::array();
#ifdef BMCWEB_ENABLE_REDFISH_HOST_LOGGER
nlohmann::json::object_t hostlogger;
hostlogger["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/HostLogger";
logServiceArray.push_back(std::move(hostlogger));
#endif
#ifdef BMCWEB_ENABLE_REDFISH_SYSTEM_FAULT_LOG
nlohmann::json::object_t faultLog;
faultLog["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/FaultLog";
logServiceArray.push_back(std::move(faultLog));
#endif
// Similar to the structure of the ComputerSystem Handlers, we will separate
// multi-host features and singlehost features.
if (!multiHost)
{
#ifdef BMCWEB_ENABLE_REDFISH_EVENT_LOG
nlohmann::json::object_t eventLog;
eventLog["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/EventLog";
logServiceArray.push_back(std::move(eventLog));
#endif
#ifdef BMCWEB_ENABLE_RASMANAGER_EVENT_LOG
nlohmann::json::object_t rasManagerLog;
rasManagerLog["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/HostCper";
logServiceArray.push_back(std::move(rasManagerLog));
#endif
#ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
nlohmann::json::object_t dumpLog;
dumpLog["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Dump";
logServiceArray.push_back(std::move(dumpLog));
#endif
#ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
nlohmann::json::object_t crashdump;
crashdump["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Crashdump";
logServiceArray.push_back(std::move(crashdump));
#endif
#ifdef BMCWEB_ENABLE_PPR
nlohmann::json::object_t ppr;
ppr["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostPackageRepair";
logServiceArray.push_back(std::move(ppr));
#endif
#ifdef BMCWEB_ENABLE_REDFISH_EXTERNAL_STORER
// This is the ExternalStorer integration point
for (const auto& instance :
external_storer::rememberLogServices()->listInstances())
{
nlohmann::json::object_t externalStorerInstance;
externalStorerInstance["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/" + instance;
logServiceArray.push_back(std::move(externalStorerInstance));
}
#endif
#ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME_LOG
nlohmann::json::object_t bootTime;
bootTime["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/BootTime";
logServiceArray.push_back(std::move(bootTime));
#endif
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.State.Boot.PostCode"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/", 0, interfaces, requestContext,
[asyncResp](const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse&
subtreePath) {
if (ec)
{
BMCWEB_LOG_ERROR << ec;
return;
}
for (const auto& pathStr : subtreePath)
{
if (absl::StrContains(pathStr, "PostCode"))
{
nlohmann::json& logServiceArrayLocal =
asyncResp->res.jsonValue["Members"];
nlohmann::json::object_t member;
member["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostCodes";
logServiceArrayLocal.push_back(std::move(member));
asyncResp->res.jsonValue["Members@odata.count"] =
logServiceArrayLocal.size();
return;
}
}
});
}
asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
}
void requestRoutesSystemLogServiceCollection(App& app)
{
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/")
.privileges(redfish::privileges::getLogServiceCollection)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
redfish::system_utils::getSystemInformation(asyncResp, systemName,
handleLogServices);
});
}
constexpr const char* hostLoggerFolderPath = "/var/log/console";
// Hardcoding mapping of system1 -> S2 and system2 -> S1
constexpr std::array<std::pair<std::string_view, std::string_view>, 2>
systemNameToLogPrefix{{{"system1", "S2"}, {"system2", "S1"}}};
inline bool
getHostLoggerFiles(const std::string& hostLoggerFilePath,
std::vector<std::filesystem::path>& hostLoggerFiles,
const std::string& systemName, bool multiHost)
{
std::error_code ec;
std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
if (ec)
{
BMCWEB_LOG_ERROR << ec.message();
return false;
}
// Setting default filePrefix is log
std::string filePrefix = defaultLogPrefix;
// For multi-host systems, the prefix will change based on which host
if (multiHost)
{
// Right now we dont have a guarateed and extendable way to determine
// which file is for which host. Will hardcode for now and fix later
bool systemFound = false;
for (const auto& mapping : systemNameToLogPrefix)
{
if (mapping.first == systemName)
{
filePrefix += mapping.second;
systemFound = true;
break;
}
}
if (!systemFound)
{
BMCWEB_LOG_ERROR << "Could not find HostLogPrefix for system "
<< systemName;
return false;
}
}
for (const std::filesystem::directory_entry& it : logPath)
{
std::string filename = it.path().filename();
// Prefix of each log files is "log". Find the file and save the
// path
if (filename.starts_with(filePrefix))
{
hostLoggerFiles.emplace_back(it.path());
}
}
// As the log files rotate, they are appended with a ".#" that is higher for
// the older logs. Since we start from oldest logs, sort the name in
// descending order.
std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
AlphanumLess<std::string>());
return true;
}
inline bool getHostLoggerEntries(
const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
{
GzFileReader logFile;
// Go though all log files and expose host logs.
for (const std::filesystem::path& it : hostLoggerFiles)
{
if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
{
BMCWEB_LOG_ERROR << "fail to expose host logs";
return false;
}
}
// Get lastMessage from constructor by getter
std::string lastMessage = logFile.getLastMessage();
if (!lastMessage.empty())
{
logCount++;
if (logCount > skip && logCount <= (skip + top))
{
logEntries.push_back(lastMessage);
}
}
return true;
}
inline void fillHostLoggerEntryJson(const std::string& logEntryID,
const std::string& msg,
nlohmann::json::object_t& logEntryJson,
const std::string& systemName)
{
// Fill in the log entry with the gathered data.
logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
logEntryJson["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", systemName, "LogServices", "HostLogger",
"Entries", logEntryID);
logEntryJson["Name"] = "Host Logger Entry";
logEntryJson["Id"] = logEntryID;
logEntryJson["Message"] = msg;
logEntryJson["EntryType"] = "Oem";
logEntryJson["Severity"] = "OK";
logEntryJson["OemRecordFormat"] = "Host Logger Entry";
}
inline void
handleSystemHostLogger(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemPath)
{
// Default systemName is system and change if system is multi-host
std::string systemName = "system";
if (!systemPath.empty())
{
systemName = sdbusplus::message::object_path(systemPath).filename();
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/HostLogger";
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["Name"] = "Host Logger Service";
asyncResp->res.jsonValue["Description"] = "Host Logger Service";
asyncResp->res.jsonValue["Id"] = "HostLogger";
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/HostLogger/Entries";
}
void requestRoutesSystemHostLogger(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
redfish::system_utils::getSystemInformation(asyncResp, systemName,
handleSystemHostLogger);
});
}
inline void handleSystemHostLoggerCollection(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemPath, const query_param::Query& delegatedQuery)
{
// If the systemPath is empty, then the system is single host
bool multiHost = !systemPath.empty();
// Default systemName is system and change if system is multi-host
std::string systemName = "system";
if (multiHost)
{
systemName = sdbusplus::message::object_path(systemPath).filename();
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/HostLogger/Entries";
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of HostLogger Entries";
nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
logEntryArray = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
std::vector<std::filesystem::path> hostLoggerFiles;
if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles, systemName,
multiHost))
{
BMCWEB_LOG_ERROR << "fail to get host log file path";
return;
}
// If we weren't provided top and skip limits, use the defaults.
size_t skip = delegatedQuery.skip.value_or(0);
size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
size_t logCount = 0;
// This vector only store the entries we want to expose that
// control by skip and top.
std::vector<std::string> logEntries;
if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, logCount))
{
messages::internalError(asyncResp->res);
return;
}
// If vector is empty, that means skip value larger than total
// log count
if (logEntries.empty())
{
asyncResp->res.jsonValue["Members@odata.count"] = logCount;
return;
}
if (!logEntries.empty())
{
for (size_t i = 0; i < logEntries.size(); i++)
{
nlohmann::json::object_t hostLogEntry;
fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
hostLogEntry, systemName);
logEntryArray.push_back(std::move(hostLogEntry));
}
asyncResp->res.jsonValue["Members@odata.count"] = logCount;
if (skip + top < logCount)
{
asyncResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Systems/" + systemName +
"/LogServices/HostLogger/Entries?$skip=" +
std::to_string(skip + top);
}
}
}
inline void handleGetSystemHostLoggerCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName)
{
query_param::QueryCapabilities capabilities = {
.canDelegateTop = true,
.canDelegateSkip = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
redfish::system_utils::getSystemInformation(
asyncResp, systemName,
[delegatedQuery](const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& systemPath) {
handleSystemHostLoggerCollection(aResp, systemPath, delegatedQuery);
});
}
void requestRoutesSystemHostLoggerCollection(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleGetSystemHostLoggerCollection, std::ref(app)));
}
inline void handleSystemHostLoggerLogEntry(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemPath, const std::string& param)
{
// If the systemPath is empty, then the system is single host
bool multiHost = !systemPath.empty();
// Default systemName is system and change if system is multi-host
std::string systemName = "system";
if (multiHost)
{
systemName = sdbusplus::message::object_path(systemPath).filename();
}
const std::string& targetID = param;
uint64_t idInt = 0;
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
const char* end = targetID.data() + targetID.size();
auto [ptr, ec] = std::from_chars(targetID.data(), end, idInt);
if (ec == std::errc::invalid_argument ||
ec == std::errc::result_out_of_range)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", param);
return;
}
std::vector<std::filesystem::path> hostLoggerFiles;
if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles, systemName,
multiHost))
{
BMCWEB_LOG_ERROR << "fail to get host log file path";
return;
}
size_t logCount = 0;
size_t top = 1;
std::vector<std::string> logEntries;
// We can get specific entry by skip and top. For example, if we
// want to get nth entry, we can set skip = n-1 and top = 1 to
// get that entry
if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
logCount))
{
messages::internalError(asyncResp->res);
return;
}
if (!logEntries.empty())
{
nlohmann::json::object_t hostLogEntry;
fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry,
systemName);
asyncResp->res.jsonValue.update(hostLogEntry);
return;
}
// Requested ID was not found
messages::resourceNotFound(asyncResp->res, "LogEntry", param);
}
inline void handleGetSystemHostLoggerLogEntry(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& param)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
redfish::system_utils::getSystemInformation(
asyncResp, systemName,
[param](const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& systemPath) {
handleSystemHostLoggerLogEntry(aResp, systemPath, param);
});
}
void requestRoutesSystemHostLoggerLogEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetSystemHostLoggerLogEntry, std::ref(app)));
}
inline void handleBMCLogServicesCollectionGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogServiceCollection.LogServiceCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices";
asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
asyncResp->res.jsonValue["Description"] =
"Collection of LogServices for this Manager";
nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
logServiceArray = nlohmann::json::array();
#ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
nlohmann::json::object_t journal;
journal["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal";
logServiceArray.push_back(std::move(journal));
#endif
#ifdef BMCWEB_ENABLE_REDFISH_BOOT_TIME_LOG
nlohmann::json::object_t bootTime;
bootTime["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BootTime";
logServiceArray.push_back(std::move(bootTime));
#endif
asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
#ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Collection.DeleteAll"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/xyz/openbmc_project/dump", 0, interfaces, requestContext,
[asyncResp](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "handleBMCLogServicesCollectionGet respHandler got error "
<< ec;
// Assume that getting an error simply means there are no dump
// LogServices. Return without adding any error response.
return;
}
nlohmann::json& logServiceArrayLocal =
asyncResp->res.jsonValue["Members"];
for (const std::string& path : subTreePaths)
{
if (path == "/xyz/openbmc_project/dump/bmc")
{
nlohmann::json::object_t member;
member["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Dump";
logServiceArrayLocal.push_back(std::move(member));
}
else if (path == "/xyz/openbmc_project/dump/faultlog")
{
nlohmann::json::object_t member;
member["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/FaultLog";
logServiceArrayLocal.push_back(std::move(member));
}
}
asyncResp->res.jsonValue["Members@odata.count"] =
logServiceArrayLocal.size();
});
#endif
}
void requestRoutesBMCLogServiceCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/")
.privileges(redfish::privileges::getLogServiceCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app)));
}
inline void handleGetBMCJournalLogService(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Journal";
asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
asyncResp->res.jsonValue["Id"] = "Journal";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
}
void requestRoutesBMCJournalLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetBMCJournalLogService, std::ref(app)));
}
static int
fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID,
sd_journal* journal,
nlohmann::json::object_t& bmcJournalLogEntryJson)
{
// Get the Log Entry contents
int ret = 0;
std::string message;
std::string_view syslogID;
ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: "
<< strerror(-ret);
}
if (!syslogID.empty())
{
message += std::string(syslogID) + ": ";
}
std::string_view msg;
ret = getJournalMetadata(journal, "MESSAGE", msg);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
return 1;
}
message += std::string(msg);
// Get the severity from the PRIORITY field
int64_t severity = 8; // Default to an invalid priority
ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
}
// Get the Created time from the timestamp
std::string entryTimeStr;
if (!getEntryTimestamp(journal, entryTimeStr))
{
return 1;
}
// Fill in the log entry with the gathered data
bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
bmcJournalLogEntryJson["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Managers", "bmc", "LogServices", "Journal", "Entries",
bmcJournalLogEntryID);
bmcJournalLogEntryJson["Name"] = "BMC Journal Entry";
bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID;
bmcJournalLogEntryJson["Message"] = std::move(message);
bmcJournalLogEntryJson["EntryType"] = "Oem";
bmcJournalLogEntryJson["Severity"] = severity <= 2 ? "Critical"
: severity <= 4 ? "Warning"
: "OK";
bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry";
bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr);
return 0;
}
inline void handleGetBMCJournalLogEntryCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
query_param::QueryCapabilities capabilities = {
.canDelegateTop = true,
.canDelegateSkip = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
size_t skip = delegatedQuery.skip.value_or(0);
size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
// Collections don't include the static data added by SubRoute
// because it has a duplicate entry for members
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of BMC Journal Entries";
nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
logEntryArray = nlohmann::json::array();
// Go through the journal and use the timestamp to create a
// unique ID for each entry
sd_journal* journalTmp = nullptr;
int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
messages::internalError(asyncResp->res);
return;
}
std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
journalTmp, sd_journal_close);
journalTmp = nullptr;
uint64_t entryCount = 0;
// Reset the unique ID on the first entry
bool firstEntry = true;
SD_JOURNAL_FOREACH(journal.get())
{
entryCount++;
// Handle paging using skip (number of entries to skip from
// the start) and top (number of entries to display)
if (entryCount <= skip || entryCount > skip + top)
{
continue;
}
std::string idStr;
if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
{
continue;
}
firstEntry = false;
nlohmann::json::object_t bmcJournalLogEntry;
if (fillBMCJournalLogEntryJson(idStr, journal.get(),
bmcJournalLogEntry) != 0)
{
messages::internalError(asyncResp->res);
return;
}
logEntryArray.push_back(std::move(bmcJournalLogEntry));
}
asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
if (skip + top < entryCount)
{
asyncResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
std::to_string(skip + top);
}
}
void requestRoutesBMCJournalLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleGetBMCJournalLogEntryCollection, std::ref(app)));
}
inline void handleGetBMCJournalLogEntry(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryID)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// Convert the unique ID back to a timestamp to find the entry
uint64_t ts = 0;
uint64_t index = 0;
if (!getTimestampFromID(asyncResp, entryID, ts, index))
{
return;
}
sd_journal* journalTmp = nullptr;
int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
messages::internalError(asyncResp->res);
return;
}
std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
journalTmp, sd_journal_close);
journalTmp = nullptr;
// Go to the timestamp in the log and move to the entry at the
// index tracking the unique ID
std::string idStr;
bool firstEntry = true;
ret = sd_journal_seek_realtime_usec(journal.get(), ts);
if (ret < 0)
{
BMCWEB_LOG_ERROR << "failed to seek to an entry in journal"
<< strerror(-ret);
messages::internalError(asyncResp->res);
return;
}
for (uint64_t i = 0; i <= index; i++)
{
sd_journal_next(journal.get());
if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
{
messages::internalError(asyncResp->res);
return;
}
firstEntry = false;
}
// Confirm that the entry ID matches what was requested
if (idStr != entryID)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
return;
}
nlohmann::json::object_t bmcJournalLogEntry;
if (fillBMCJournalLogEntryJson(entryID, journal.get(),
bmcJournalLogEntry) != 0)
{
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue.update(bmcJournalLogEntry);
}
void requestRoutesBMCJournalLogEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetBMCJournalLogEntry, std::ref(app)));
}
namespace internal
{
void getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const Dump& dump)
{
std::string dumpPath;
std::string overWritePolicy;
bool collectDiagnosticDataSupported = false;
switch (dump.type)
{
case DumpType::BMC_DUMP:
dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump";
overWritePolicy = "WrapsWhenFull";
collectDiagnosticDataSupported = true;
break;
case DumpType::BMC_FAULT_LOG:
dumpPath = "/redfish/v1/Managers/bmc/LogServices/FaultLog";
overWritePolicy = "Unknown";
collectDiagnosticDataSupported = false;
break;
case DumpType::SYSTEM_DUMP:
dumpPath = "/redfish/v1/Systems/system/LogServices/Dump";
overWritePolicy = "WrapsWhenFull";
collectDiagnosticDataSupported = true;
break;
case DumpType::SYSTEM_FAULT_LOG:
if (dump.systemName)
{
dumpPath = "/redfish/v1/Systems/" + *dump.systemName +
"/LogServices/FaultLog";
overWritePolicy = "Unknown";
collectDiagnosticDataSupported = false;
}
break;
}
asyncResp->res.jsonValue["@odata.id"] = dumpPath;
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] =
dumpTypeToString(dump.type) + " Dump LogService";
asyncResp->res.jsonValue["Description"] =
dumpTypeToString(dump.type) + " Dump LogService";
asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy);
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries";
if (collectDiagnosticDataSupported)
{
asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
["target"] =
dumpPath + "/Actions/LogService.CollectDiagnosticData";
}
constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/xyz/openbmc_project/dump", 0, interfaces, requestContext,
[asyncResp, dump, dumpPath](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
if (ec)
{
BMCWEB_LOG_ERROR << "getDumpServiceInfo respHandler got error "
<< ec;
// Assume that getting an error simply means there are no dump
// LogServices. Return without adding any error response.
return;
}
std::optional<std::string> dumpManagerCollectionPath =
getDumpManagerCollectionPath(dump);
if (!dumpManagerCollectionPath)
{
BMCWEB_LOG_ERROR
<< "Cant get dumpManagerCollectionPath from system "
<< dump.systemName.value_or("none");
messages::internalError(asyncResp->res);
return;
}
for (const std::string& path : subTreePaths)
{
if (path == *dumpManagerCollectionPath)
{
asyncResp->res
.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
dumpPath + "/Actions/LogService.ClearLog";
break;
}
}
});
}
} // namespace internal
inline void handleLogServicesDumpServiceGet(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
internal::getDumpServiceInfo(asyncResp, Dump{.type = dumpType});
}
inline void handleLogServicesDumpServiceComputerSystemGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (id != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem", id);
return;
}
internal::getDumpServiceInfo(asyncResp,
Dump{.type = DumpType::SYSTEM_DUMP});
}
inline void handleLogServicesDumpEntriesCollectionGet(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
query_param::QueryCapabilities capabilities = {
.canDelegateExpandLevel = 2,
.canDelegateFilter = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
getDumpEntryCollection(app, req, asyncResp, Dump{.type = dumpType},
delegatedQuery);
}
inline void handleLogServicesDumpEntriesCollectionComputerSystemGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId)
{
query_param::QueryCapabilities capabilities = {
.canDelegateExpandLevel = 2,
.canDelegateFilter = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
if (chassisId != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
return;
}
getDumpEntryCollection(app, req, asyncResp,
Dump{.type = DumpType::SYSTEM_DUMP}, delegatedQuery);
}
inline void handleLogServicesDumpEntryGet(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& dumpId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
getDumpEntryById(asyncResp, dumpId, Dump{.type = dumpType});
}
inline void handleLogServicesDumpEntryComputerSystemGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId, const std::string& dumpId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (chassisId != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
return;
}
getDumpEntryById(asyncResp, dumpId, Dump{.type = DumpType::SYSTEM_DUMP});
}
inline void handleLogServicesDumpEntryDelete(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& dumpId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
deleteDumpEntry(asyncResp, dumpId, Dump{.type = dumpType});
}
inline void handleLogServicesDumpEntryComputerSystemDelete(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId, const std::string& dumpId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (chassisId != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
return;
}
deleteDumpEntry(asyncResp, dumpId, Dump{.type = DumpType::SYSTEM_DUMP});
}
inline void handleLogServicesDumpEntryDownloadGet(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& dumpId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
downloadDumpEntry(asyncResp, dumpId, Dump{.type = dumpType});
}
inline void handleLogServicesDumpCollectDiagnosticDataPost(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
createDump(asyncResp, req, Dump{.type = dumpType});
}
inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (chassisId != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
return;
}
createDump(asyncResp, req, Dump{.type = DumpType::SYSTEM_DUMP});
}
inline void handleLogServicesDumpClearLogPost(
crow::App& app, DumpType dumpType, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
clearDump(asyncResp, Dump{.type = dumpType});
}
inline void handleLogServicesDumpClearLogComputerSystemPost(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& chassisId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (chassisId != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
return;
}
clearDump(asyncResp, Dump{.type = DumpType::SYSTEM_DUMP});
}
inline void handleLogServiceFaultLogEntriesGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName)
{
query_param::QueryCapabilities capabilities = {
.canDelegateExpandLevel = 2,
.canDelegateFilter = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
getDumpEntryCollection(
app, req, asyncResp,
Dump{.type = DumpType::SYSTEM_FAULT_LOG, .systemName = systemName},
delegatedQuery);
}
void requestRoutesBMCDumpService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesDumpServiceGet, std::ref(app),
DumpType::BMC_DUMP));
}
void requestRoutesBMCDumpEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesDumpEntriesCollectionGet,
std::ref(app), DumpType::BMC_DUMP));
}
void requestRoutesBMCDumpEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleLogServicesDumpEntryGet, std::ref(app), DumpType::BMC_DUMP));
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
.privileges(redfish::privileges::deleteLogEntry)
.methods(boost::beast::http::verb::delete_)(
std::bind_front(handleLogServicesDumpEntryDelete, std::ref(app),
DumpType::BMC_DUMP));
}
void requestRoutesBMCDumpEntryDownload(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/attachment")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesDumpEntryDownloadGet,
std::ref(app), DumpType::BMC_DUMP));
}
void requestRoutesBMCDumpCreate(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(
std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost,
std::ref(app), DumpType::BMC_DUMP));
}
void requestRoutesBMCDumpClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(
std::bind_front(handleLogServicesDumpClearLogPost, std::ref(app),
DumpType::BMC_DUMP));
}
void requestRoutesFaultLogDumpService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesDumpServiceGet, std::ref(app),
DumpType::BMC_FAULT_LOG));
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/FaultLog/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
internal::getDumpServiceInfo(
asyncResp,
Dump{.type = DumpType::SYSTEM_FAULT_LOG, .systemName = systemName});
});
}
void requestRoutesFaultLogDumpEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesDumpEntriesCollectionGet,
std::ref(app), DumpType::BMC_FAULT_LOG));
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/FaultLog/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServiceFaultLogEntriesGet, std::ref(app)));
}
void requestRoutesFaultLogDumpEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesDumpEntryGet, std::ref(app),
DumpType::BMC_FAULT_LOG));
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/FaultLog/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& dumpId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
getDumpEntryById(
asyncResp, dumpId,
Dump{.type = DumpType::SYSTEM_FAULT_LOG, .systemName = systemName});
});
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/")
.privileges(redfish::privileges::deleteLogEntry)
.methods(boost::beast::http::verb::delete_)(
std::bind_front(handleLogServicesDumpEntryDelete, std::ref(app),
DumpType::BMC_FAULT_LOG));
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/FaultLog/Entries/<str>/")
.privileges(redfish::privileges::deleteLogEntry)
.methods(boost::beast::http::verb::delete_)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& dumpId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
deleteDumpEntry(
asyncResp, dumpId,
Dump{.type = DumpType::SYSTEM_FAULT_LOG, .systemName = systemName});
});
}
void requestRoutesFaultLogDumpClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Managers/bmc/LogServices/FaultLog/Actions/LogService.ClearLog/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(
std::bind_front(handleLogServicesDumpClearLogPost, std::ref(app),
DumpType::BMC_FAULT_LOG));
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/FaultLog/Actions/LogService.ClearLog/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(
[&app](const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
clearDump(asyncResp, Dump{.type = DumpType::SYSTEM_FAULT_LOG,
.systemName = systemName});
});
}
void requestRoutesSystemDumpService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleLogServicesDumpServiceComputerSystemGet, std::ref(app)));
}
void requestRoutesSystemDumpEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleLogServicesDumpEntriesCollectionComputerSystemGet,
std::ref(app)));
}
void requestRoutesSystemDumpEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleLogServicesDumpEntryComputerSystemGet, std::ref(app)));
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
.privileges(redfish::privileges::deleteLogEntry)
.methods(boost::beast::http::verb::delete_)(std::bind_front(
handleLogServicesDumpEntryComputerSystemDelete, std::ref(app)));
}
void requestRoutesSystemDumpCreate(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(std::bind_front(
handleLogServicesDumpCollectDiagnosticDataComputerSystemPost,
std::ref(app)));
}
void requestRoutesSystemDumpClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(std::bind_front(
handleLogServicesDumpClearLogComputerSystemPost, std::ref(app)));
}
#ifdef BMCWEB_ENABLE_RASMANAGER_EVENT_LOG
inline void handleLogServicesRasManagerGet(
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;
}
// Copy over the static data to include the entries added by
// SubRoute
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/HostCper";
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] = "Open BMC Oem RasManager Service";
asyncResp->res.jsonValue["Description"] = "Oem RasManager Service";
asyncResp->res.jsonValue["Id"] = "RasManager";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems", "system",
"LogServices", "HostCper", "Entries");
asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
"/redfish/v1/Systems/system/LogServices/HostCper/Actions/LogService.ClearLog";
}
void requestRoutesRasManagerLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostCper/")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handleLogServicesRasManagerGet, std::ref(app)));
}
inline void handleLogServicesRasManagerClearLogPost(
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,
const std::string&) {
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
}, rasManagerObject, rasManagerPath, deleteAllInterface, "DeleteAll");
}
void requestRoutesRasManagerLogClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/HostCper/Actions/LogService.ClearLog/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(std::bind_front(
handleLogServicesRasManagerClearLogPost, std::ref(app)));
}
inline static void
logRasManagerEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& logID, nlohmann::json& logEntryJson)
{
auto getStoredLogCallback =
[asyncResp, logID,
&logEntryJson](const boost::system::error_code& ec,
const dbus::utility::DBusPropertiesMap& params) {
if (ec)
{
BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
if (ec.value() ==
boost::system::linux_error::bad_request_descriptor)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
}
else
{
messages::internalError(asyncResp->res);
}
return;
}
uint64_t timestampMs = 0;
const uint64_t* timestampPtr = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), params, "TimestampMs",
timestampPtr);
if (!success)
{
return;
}
if (timestampPtr != nullptr)
{
timestampMs = *timestampPtr;
}
nlohmann::json::object_t logEntry;
logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
logEntry["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", "system", "LogServices", "HostCper",
"Entries", logID);
logEntry["Name"] = "RasManager";
logEntry["Id"] = logID;
logEntry["EntryType"] = "Oem";
logEntry["DiagnosticDataType"] = "CPER";
logEntry["OemRecordFormat"] = "CPER";
logEntry["Created"] =
redfish::time_utils::getDateTimeUintMs(timestampMs);
// read bin data from file and then encode it to boost base64
std::string cperFile = rasManagerLogPath + logID + ".cpr";
std::ifstream ifs(cperFile, std::ios::in | std::ios::binary);
if (!ifs.is_open())
{
BMCWEB_LOG_ERROR << "Failed to open file: " << cperFile;
return;
}
std::string cperData((std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()));
std::string encodedData;
encodedData.resize(
boost::beast::detail::base64::encoded_size(cperData.size()));
encodedData.resize(boost::beast::detail::base64::encode(
&encodedData[0], cperData.data(), cperData.size()));
logEntry["DiagnosticData"] = std::move(encodedData);
ifs.close();
// If logEntryJson references an array of LogEntry resources
// ('Members' list), then push this as a new entry, otherwise set it
// directly
if (logEntryJson.is_array())
{
logEntryJson.push_back(logEntry);
asyncResp->res.jsonValue["Members@odata.count"] =
logEntryJson.size();
}
else
{
logEntryJson.update(logEntry);
}
};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
rasManagerObject, rasManagerPath + std::string("/") + logID,
"xyz.openbmc_project.Time.EpochTime", context,
std::move(getStoredLogCallback));
}
inline void handleLogServicesRasManagerEntriesCollectionGet(
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;
}
constexpr std::array<std::string_view, 1> interfaces = {
"xyz.openbmc_project.Time.EpochTime"};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
rasManagerPath, 0, interfaces, requestContext,
[asyncResp](const boost::system::error_code& ec,
const std::vector<std::string>& resp) {
if (ec)
{
if (ec.value() != boost::system::errc::no_such_file_or_directory)
{
BMCWEB_LOG_DEBUG << "failed to get entries ec: "
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/HostCper/Entries";
asyncResp->res.jsonValue["Name"] = "Open BMC RasManager Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of RasManager Entries";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
for (const std::string& path : resp)
{
const sdbusplus::message::object_path objPath(path);
// Get the log ID
std::string logID = objPath.filename();
if (logID.empty())
{
continue;
}
// Add the log entry to the array
logRasManagerEntry(asyncResp, logID,
asyncResp->res.jsonValue["Members"]);
}
});
}
void requestRoutesRasManagerLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostCper/Entries/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(std::bind_front(
handleLogServicesRasManagerEntriesCollectionGet, std::ref(app)));
}
inline void handleLogServicesRasManagerEntryGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& param)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
const std::string& logID = param;
logRasManagerEntry(asyncResp, logID, asyncResp->res.jsonValue);
}
void requestRoutesRasManagerLogEntry(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/HostCper/Entries/<str>/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(std::bind_front(
handleLogServicesRasManagerEntryGet, std::ref(app)));
}
#endif // BMCWEB_ENABLE_RASMANAGER_EVENT_LOG
inline void handleGetCrashdumpService(
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;
}
// Copy over the static data to include the entries added by
// SubRoute
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Crashdump";
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
asyncResp->res.jsonValue["Id"] = "Crashdump";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
if constexpr (bmcwebEnableAmd)
{
asyncResp->res.jsonValue["MaxNumberOfRecords"] = 10;
}
else
{
asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
}
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Systems", "system",
"LogServices", "Crashdump", "Entries");
asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
"/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog";
asyncResp->res
.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]["target"] =
"/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData";
}
void requestRoutesCrashdumpService(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/")
// This is incorrect, should be:
//.privileges(redfish::privileges::getLogService)
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetCrashdumpService, std::ref(app)));
}
inline void handlePostCrashdumpClear(
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,
const std::string&) {
if (ec)
{
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
},
crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
}
void requestRoutesCrashdumpClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/")
// This is incorrect, should be:
//.privileges(redfish::privileges::postLogService)
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
std::bind_front(handlePostCrashdumpClear, std::ref(app)));
}
inline static void
logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& logID, nlohmann::json& logEntryJson)
{
auto getStoredLogCallback =
[asyncResp, logID,
&logEntryJson](const boost::system::error_code& ec,
const dbus::utility::DBusPropertiesMap& params) {
if (ec)
{
BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
if (ec.value() ==
boost::system::linux_error::bad_request_descriptor)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
}
else
{
messages::internalError(asyncResp->res);
}
return;
}
std::string timestamp{};
std::string filename{};
std::string logfile{};
parseCrashdumpParameters(params, filename, timestamp, logfile);
if (filename.empty() || timestamp.empty())
{
messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
return;
}
std::string crashdumpURI =
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
logID + "/" + filename;
nlohmann::json::object_t logEntry;
logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
logEntry["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", "system", "LogServices", "Crashdump",
"Entries", logID);
logEntry["Name"] = "CPU Crashdump";
logEntry["Id"] = logID;
logEntry["EntryType"] = "Oem";
logEntry["AdditionalDataURI"] = std::move(crashdumpURI);
logEntry["DiagnosticDataType"] = "OEM";
if constexpr (bmcwebEnableAmd)
{
logEntry["OEMDiagnosticDataType"] = "CPER";
}
else
{
logEntry["OEMDiagnosticDataType"] = "PECICrashdump";
}
logEntry["Created"] = std::move(timestamp);
// If logEntryJson references an array of LogEntry resources
// ('Members' list), then push this as a new entry, otherwise set it
// directly
if (logEntryJson.is_array())
{
logEntryJson.push_back(logEntry);
asyncResp->res.jsonValue["Members@odata.count"] =
logEntryJson.size();
}
else
{
logEntryJson.update(logEntry);
}
};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
crashdumpObject, crashdumpPath + std::string("/") + logID,
crashdumpInterface, context, std::move(getStoredLogCallback));
}
inline void parseElapsedTimestampFromDbusObject(
const dbus::utility::ManagedObjectType::value_type& object,
uint64_t& timestampUs, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
for (const auto& interfaceMap : object.second)
{
if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Elapsed")
{
const uint64_t* usecsTimeStamp =
std::get_if<uint64_t>(&propertyMap.second);
if (usecsTimeStamp == nullptr)
{
messages::internalError(asyncResp->res);
break;
}
timestampUs = *usecsTimeStamp;
break;
}
}
break;
}
}
}
inline void handleGetCrashdumpEntryCollection(
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;
}
constexpr std::array<std::string_view, 1> interfaces = {crashdumpInterface};
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->getSubTreePaths(
"/", 0, interfaces, requestContext,
[asyncResp](const boost::system::error_code& ec,
const std::vector<std::string>& resp) {
if (ec)
{
if (ec.value() != boost::system::errc::no_such_file_or_directory)
{
BMCWEB_LOG_DEBUG << "failed to get entries ec: "
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of Crashdump Entries";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
for (const std::string& path : resp)
{
const sdbusplus::message::object_path objPath(path);
// Get the log ID
std::string logID = objPath.filename();
if (logID.empty())
{
continue;
}
// Add the log entry to the array
logCrashdumpEntry(asyncResp, logID,
asyncResp->res.jsonValue["Members"]);
}
});
}
void requestRoutesCrashdumpEntryCollection(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
/**
* Functions triggers appropriate requests on DBus
*/
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/")
// This is incorrect, should be.
//.privileges(redfish::privileges::postLogEntryCollection)
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetCrashdumpEntryCollection, std::ref(app)));
}
inline void
handleGetCrashdumpEntry(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& param)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
const std::string& logID = param;
logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
}
void requestRoutesCrashdumpEntry(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/")
// this is incorrect, should be
// .privileges(redfish::privileges::getLogEntry)
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetCrashdumpEntry, std::ref(app)));
}
inline void
handleGetCrashdumpFile([[maybe_unused]] App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& logID,
const std::string& fileName)
{
// Do not call getRedfishRoute here since the crashdump file is not a
// Redfish resource.
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
auto getStoredLogCallback =
[asyncResp, logID, fileName, url(boost::urls::url(req.url()))](
const boost::system::error_code& ec,
const std::vector<
std::pair<std::string, dbus::utility::DbusVariantType>>& resp) {
if (ec)
{
BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
messages::internalError(asyncResp->res);
return;
}
std::string dbusFilename{};
std::string dbusTimestamp{};
std::string dbusFilepath{};
parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp,
dbusFilepath);
if (dbusFilename.empty() || dbusTimestamp.empty() ||
dbusFilepath.empty())
{
messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
return;
}
// Verify the file name parameter is correct
if (fileName != dbusFilename)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
return;
}
if (!std::filesystem::exists(dbusFilepath))
{
messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
return;
}
std::ifstream ifs(dbusFilepath, std::ios::in | std::ios::binary);
asyncResp->res.body() =
std::string(std::istreambuf_iterator<char>{ifs}, {});
// Configure this to be a file download when accessed
// from a browser
asyncResp->res.addHeader(boost::beast::http::field::content_disposition,
"attachment");
};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
crashdumpObject, crashdumpPath + std::string("/") + logID,
crashdumpInterface, context, std::move(getStoredLogCallback));
}
void requestRoutesCrashdumpFile(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetCrashdumpFile, std::ref(app)));
}
enum class OEMDiagnosticType : std::uint8_t
{
onDemand,
telemetry,
invalid,
};
inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr)
{
if (oemDiagStr == "OnDemand")
{
return OEMDiagnosticType::onDemand;
}
if (oemDiagStr == "Telemetry")
{
return OEMDiagnosticType::telemetry;
}
return OEMDiagnosticType::invalid;
}
inline void handlePostCrashdumpCollect(
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;
}
std::string diagnosticDataType;
std::string oemDiagnosticDataType;
if (!redfish::json_util::readJsonAction(
req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
"OEMDiagnosticDataType", oemDiagnosticDataType))
{
return;
}
if (diagnosticDataType != "OEM")
{
BMCWEB_LOG_ERROR
<< "Only OEM DiagnosticDataType supported for Crashdump";
messages::actionParameterValueFormatError(
asyncResp->res, diagnosticDataType, "DiagnosticDataType",
"CollectDiagnosticData");
return;
}
OEMDiagnosticType oemDiagType = getOEMDiagnosticType(oemDiagnosticDataType);
std::string iface;
std::string method;
std::string taskMatchStr;
if (oemDiagType == OEMDiagnosticType::onDemand)
{
iface = crashdumpOnDemandInterface;
method = "GenerateOnDemandLog";
if constexpr (bmcwebEnableAmd)
{
taskMatchStr = "type='signal',"
"interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',"
"arg0namespace='com.amd.crashdump'";
}
else
{
taskMatchStr = "type='signal',"
"interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',"
"arg0namespace='com.intel.crashdump'";
}
}
else if (oemDiagType == OEMDiagnosticType::telemetry)
{
iface = crashdumpTelemetryInterface;
method = "GenerateTelemetryLog";
if constexpr (bmcwebEnableAmd)
{
taskMatchStr = "type='signal',"
"interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',"
"arg0namespace='com.amd.crashdump'";
}
else
{
taskMatchStr = "type='signal',"
"interface='org.freedesktop.DBus.Properties',"
"member='PropertiesChanged',"
"arg0namespace='com.intel.crashdump'";
}
}
else
{
BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: "
<< oemDiagnosticDataType;
messages::actionParameterValueFormatError(
asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType",
"CollectDiagnosticData");
return;
}
auto collectCrashdumpCallback =
[asyncResp, payload(task::Payload(req)), taskMatchStr](
const boost::system::error_code& ec, const std::string&) mutable {
if (ec)
{
if (ec.value() == boost::system::errc::operation_not_supported)
{
messages::resourceInStandby(asyncResp->res);
}
else if (ec.value() == boost::system::errc::device_or_resource_busy)
{
messages::serviceTemporarilyUnavailable(asyncResp->res, "60");
}
else
{
messages::internalError(asyncResp->res);
}
return;
}
std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
[](const boost::system::error_code& err, sdbusplus::message_t&,
const std::shared_ptr<task::TaskData>& taskData) {
if (!err)
{
taskData->messages.emplace_back(
messages::taskCompletedOK(std::to_string(taskData->index)));
taskData->state = "Completed";
}
return task::completed;
},
taskMatchStr);
task->startTimer(std::chrono::minutes(5));
task->populateResp(asyncResp->res);
task->payload.emplace(std::move(payload));
};
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath,
iface, method);
}
void requestRoutesCrashdumpCollect(App& app)
{
// Note: Deviated from redfish privilege registry for GET & HEAD
// method for security reasons.
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/")
// The below is incorrect; Should be ConfigureManager
//.privileges(redfish::privileges::postLogService)
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
std::bind_front(handlePostCrashdumpCollect, std::ref(app)));
}
inline void handlePostDBusLogServiceActionsClear(
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;
}
BMCWEB_LOG_DEBUG << "Do delete all entries.";
// Process response from Logging service.
auto respHandler = [asyncResp](const boost::system::error_code& ec) {
BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
if (ec)
{
// TODO Handle for specific error code
BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
return;
}
asyncResp->res.result(boost::beast::http::status::no_content);
};
// Make call to Logging service to request Clear Log
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
respHandler, "xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging",
"xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
}
/**
* DBusLogServiceActionsClear class supports POST method for ClearLog action.
*/
void requestRoutesDBusLogServiceActionsClear(App& app)
{
/**
* Function handles POST method request.
* The Clear Log actions does not require any parameter.The action deletes
* all entries found in the Entries collection for this Log Service.
*/
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
.privileges(redfish::privileges::postLogService)
.methods(boost::beast::http::verb::post)(std::bind_front(
handlePostDBusLogServiceActionsClear, std::ref(app)));
}
inline void handleGetPostCodesLogService(
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;
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostCodes";
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["Name"] = "POST Code Log Service";
asyncResp->res.jsonValue["Description"] = "POST Code Log Service";
asyncResp->res.jsonValue["Id"] = "PostCodes";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
{"target",
"/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}};
}
/****************************************************
* Redfish PostCode interfaces
* using DBUS interface: getPostCodesTS
******************************************************/
void requestRoutesPostCodesLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetPostCodesLogService, std::ref(app)));
}
inline void handlePostPostCodesClear(
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;
}
BMCWEB_LOG_DEBUG << "Do delete all postcodes entries.";
// Make call to post-code service to request clear all
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& ec) {
if (ec)
{
// TODO Handle for specific error code
BMCWEB_LOG_ERROR << "doClearPostCodes resp_handler got error "
<< ec;
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
}, "xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
}
void requestRoutesPostCodesClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/")
// The following privilege is incorrect; It should be ConfigureManager
//.privileges(redfish::privileges::postLogService)
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
std::bind_front(handlePostPostCodesClear, std::ref(app)));
}
/**
* @brief Parse post code ID and get the current value and index value
* eg: postCodeID=B1-2, currentValue=1, index=2
*
* @param[in] postCodeID Post Code ID
* @param[out] currentValue Current value
* @param[out] index Index value
*
* @return bool true if the parsing is successful, false the parsing fails
*/
inline static bool parsePostCode(const std::string& postCodeID,
uint64_t& currentValue, uint16_t& index)
{
std::vector<std::string> split;
bmcweb::split(split, postCodeID, '-');
if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B')
{
return false;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
const char* start = split[0].data() + 1;
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
const char* end = split[0].data() + split[0].size();
auto [ptrIndex, ecIndex] = std::from_chars(start, end, index);
if (ptrIndex != end || ecIndex != std::errc())
{
return false;
}
start = split[1].data();
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
end = split[1].data() + split[1].size();
auto [ptrValue, ecValue] = std::from_chars(start, end, currentValue);
return ptrValue == end && ecValue == std::errc();
}
static bool fillPostCodeEntry(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const boost::container::flat_map<
uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode,
const uint16_t bootIndex, const uint64_t codeIndex = 0,
const uint64_t skip = 0, const uint64_t top = 0)
{
// Get the Message from the MessageRegistry
const registries::Message* message =
registries::getMessage("OpenBMC.0.2.BIOSPOSTCode");
uint64_t currentCodeIndex = 0;
uint64_t firstCodeTimeUs = 0;
for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
code : postcode)
{
currentCodeIndex++;
std::string postcodeEntryID =
"B" + std::to_string(bootIndex) + "-" +
std::to_string(currentCodeIndex); // 1 based index in EntryID string
uint64_t usecSinceEpoch = code.first;
uint64_t usTimeOffset = 0;
if (1 == currentCodeIndex)
{ // already incremented
firstCodeTimeUs = code.first;
}
else
{
usTimeOffset = code.first - firstCodeTimeUs;
}
// skip if no specific codeIndex is specified and currentCodeIndex does
// not fall between top and skip
if ((codeIndex == 0) &&
(currentCodeIndex <= skip || currentCodeIndex > top))
{
continue;
}
// skip if a specific codeIndex is specified and does not match the
// currentIndex
if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
{
// This is done for simplicity. 1st entry is needed to calculate
// time offset. To improve efficiency, one can get to the entry
// directly (possibly with flatmap's nth method)
continue;
}
// currentCodeIndex is within top and skip or equal to specified code
// index
// Get the Created time from the timestamp
std::string entryTimeStr;
entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch);
// assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
std::ostringstream hexCode;
hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
<< std::get<0>(code.second);
std::ostringstream timeOffsetStr;
// Set Fixed -Point Notation
timeOffsetStr << std::fixed;
// Set precision to 4 digits
timeOffsetStr << std::setprecision(4);
// Add double to stream
timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
std::vector<std::string> messageArgs = {
std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()};
// Get MessageArgs template from message registry
std::string msg;
if (message != nullptr)
{
msg = message->message;
// fill in this post code value
int i = 0;
for (const std::string& messageArg : messageArgs)
{
std::string argStr = "%" + std::to_string(++i);
size_t argPos = msg.find(argStr);
if (argPos != std::string::npos)
{
msg.replace(argPos, argStr.length(), messageArg);
}
}
}
// Get Severity template from message registry
std::string severity;
if (message != nullptr)
{
severity = message->messageSeverity;
}
// Format entry
nlohmann::json::object_t bmcLogEntry;
bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
bmcLogEntry["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", "system", "LogServices", "PostCodes",
"Entries", postcodeEntryID);
bmcLogEntry["Name"] = "POST Code Log Entry";
bmcLogEntry["Id"] = postcodeEntryID;
bmcLogEntry["Message"] = std::move(msg);
bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode";
bmcLogEntry["MessageArgs"] = std::move(messageArgs);
bmcLogEntry["EntryType"] = "Event";
bmcLogEntry["Severity"] = std::move(severity);
bmcLogEntry["Created"] = entryTimeStr;
if (!std::get<std::vector<uint8_t>>(code.second).empty())
{
bmcLogEntry["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" +
postcodeEntryID + "/attachment";
}
// codeIndex is only specified when querying single entry, return only
// that entry in this case
if (codeIndex != 0)
{
aResp->res.jsonValue.update(bmcLogEntry);
return true;
}
nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"];
logEntryArray.push_back(std::move(bmcLogEntry));
}
// Return value is always false when querying multiple entries
return false;
}
static void getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& entryId)
{
uint16_t bootIndex = 0;
uint64_t codeIndex = 0;
if (!parsePostCode(entryId, codeIndex, bootIndex))
{
// Requested ID was not found
messages::resourceNotFound(aResp->res, "LogEntry", entryId);
return;
}
if (bootIndex == 0 || codeIndex == 0)
{
// 0 is an invalid index
messages::resourceNotFound(aResp->res, "LogEntry", entryId);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
aResp->strand_,
[aResp, entryId, bootIndex,
codeIndex](const boost::system::error_code& ec,
const boost::container::flat_map<
uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
postcode) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
messages::internalError(aResp->res);
return;
}
if (postcode.empty())
{
messages::resourceNotFound(aResp->res, "LogEntry", entryId);
return;
}
if (!fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex))
{
messages::resourceNotFound(aResp->res, "LogEntry", entryId);
return;
}
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
bootIndex);
}
static void getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const uint16_t bootIndex,
const uint16_t bootCount,
const uint64_t entryCount, size_t skip,
size_t top)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
aResp->strand_,
[aResp, bootIndex, bootCount, entryCount, skip,
top](const boost::system::error_code& ec,
const boost::container::flat_map<
uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
postcode) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
messages::internalError(aResp->res);
return;
}
uint64_t endCount = entryCount;
if (!postcode.empty())
{
endCount = entryCount + postcode.size();
if (skip < endCount && (top + skip) > entryCount)
{
uint64_t thisBootSkip =
std::max(static_cast<uint64_t>(skip), entryCount) -
entryCount;
uint64_t thisBootTop =
std::min(static_cast<uint64_t>(top + skip), endCount) -
entryCount;
fillPostCodeEntry(aResp, postcode, bootIndex, 0, thisBootSkip,
thisBootTop);
}
aResp->res.jsonValue["Members@odata.count"] = endCount;
}
// continue to previous bootIndex
if (bootIndex < bootCount)
{
getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1),
bootCount, endCount, skip, top);
}
else if (skip + top < endCount)
{
aResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" +
std::to_string(skip + top);
}
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
bootIndex);
}
static void
getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
size_t skip, size_t top)
{
uint64_t entryCount = 0;
managedStore::ManagedObjectStoreContext requestContext(aResp);
dbus_utils::getProperty<uint16_t>(
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount",
requestContext,
[aResp, entryCount, skip, top](const boost::system::error_code& ec,
const uint16_t bootCount) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(aResp->res);
return;
}
getPostCodeForBoot(aResp, 1, bootCount, entryCount, skip, top);
});
}
inline void handleGetPostCodesEntryCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName)
{
query_param::QueryCapabilities capabilities = {
.canDelegateTop = true,
.canDelegateSkip = true,
};
query_param::Query delegatedQuery;
if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
delegatedQuery, capabilities))
{
return;
}
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of POST Code Log Entries";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
size_t skip = delegatedQuery.skip.value_or(0);
size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
getCurrentBootNumber(asyncResp, skip, top);
}
void requestRoutesPostCodesEntryCollection(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetPostCodesEntryCollection, std::ref(app)));
}
inline void handleGetPostCodesEntryAdditionalData(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& postCodeID)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (http_helpers::isContentTypeAllowed(
req.getHeaderValue("Accept"),
http_helpers::ContentType::OctetStream, true))
{
asyncResp->res.result(boost::beast::http::status::bad_request);
return;
}
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
uint64_t currentValue = 0;
uint16_t index = 0;
if (!parsePostCode(postCodeID, currentValue, index))
{
messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
return;
}
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, postCodeID, currentValue](
const boost::system::error_code& ec,
const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>&
postcodes) {
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
size_t value = static_cast<size_t>(currentValue) - 1;
if (value == std::string::npos || postcodes.size() < currentValue)
{
BMCWEB_LOG_WARNING << "Wrong currentValue value";
messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
return;
}
const auto& [tID, c] = postcodes[value];
if (c.empty())
{
BMCWEB_LOG_WARNING << "No found post code data";
messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
return;
}
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
const char* d = reinterpret_cast<const char*>(c.data());
std::string_view strData(d, c.size());
asyncResp->res.addHeader(boost::beast::http::field::content_type,
"application/octet-stream");
asyncResp->res.addHeader(
boost::beast::http::field::content_transfer_encoding, "Base64");
asyncResp->res.body() = crow::utility::base64encode(strData);
},
"xyz.openbmc_project.State.Boot.PostCode0",
"/xyz/openbmc_project/State/Boot/PostCode0",
"xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index);
}
void requestRoutesPostCodesEntryAdditionalData(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleGetPostCodesEntryAdditionalData, std::ref(app)));
}
inline void
handleGetPostCodesEntry(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName,
const std::string& targetID)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
getPostCodeForEntry(asyncResp, targetID);
}
void requestRoutesPostCodesEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetPostCodesEntry, std::ref(app)));
}
static void handleBootTimeLogServiceGet(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["Name"] = "Boot Time Log Service";
asyncResp->res.jsonValue["Description"] = "Boot Time Log Service";
asyncResp->res.jsonValue["Id"] = "BootTime";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
asyncResp->res.jsonValue["LogEntryType"] = "Multiple";
std::pair<std::string, std::string> redfishDateTimeOffset =
redfish::time_utils::getDateTimeOffsetNow();
asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
asyncResp->res.jsonValue["DateTimeLocalOffset"] =
redfishDateTimeOffset.second;
}
void handleSystemBootTimeLogServiceGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// Only for single host
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/BootTime";
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/BootTime/Entries";
handleBootTimeLogServiceGet(asyncResp);
}
void handleManagerBootTimeLogServiceGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/BootTime";
asyncResp->res.jsonValue["Entries"]["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/BootTime/Entries";
handleBootTimeLogServiceGet(asyncResp);
}
void requestRoutesBootTimeLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/BootTime/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleSystemBootTimeLogServiceGet, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/BootTime/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleManagerBootTimeLogServiceGet, std::ref(app)));
}
inline static std::string
generateBootTimeLogId(BootTimeDataType diagnosticsDataType,
const size_t index)
{
return bootTimeDataTypeToString(diagnosticsDataType) +
std::to_string(index);
}
static std::shared_ptr<nlohmann::json::object_t>
makeBootTimeLogEntry(BootTimeDataType diagnosticsDataType,
const nlohmann::json::object_t& diagnosticDataJson,
const size_t index, const std::string& parentODataId)
{
nlohmann::json::object_t logEntry;
const std::string id = generateBootTimeLogId(diagnosticsDataType, index);
// required properties start
logEntry["@odata.id"] = parentODataId + "/" + id;
logEntry["@odata.type"] = "#LogEntry.v1_15_0.LogEntry";
logEntry["Name"] = "Boot Time Log Entry";
logEntry["Id"] = id;
logEntry["EntryType"] = "Oem";
// required properties end
logEntry["OEMDiagnosticDataType"] =
bootTimeDataTypeToString(diagnosticsDataType);
std::string jsonStr = nlohmann::json(diagnosticDataJson).dump();
logEntry["Message"] = jsonStr;
const std::string base64DiagnosticData =
crow::utility::base64encode(jsonStr);
logEntry["DiagnosticData"] = base64DiagnosticData;
return std::make_shared<nlohmann::json::object_t>(logEntry);
}
static void getBootTimeCheckpointLogEntries(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& host,
const std::shared_ptr<const std::string>& parentODataId,
const std::shared_ptr<size_t>& specificIndex,
const std::function<void(const nlohmann::json& logEntryArray)>&
successCallback)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
aResp->strand_,
[aResp, parentODataId, specificIndex,
successCb{successCallback}](
const boost::system::error_code& ec,
const std::vector<BootTimeCheckpoint>& checkpoints) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS Boot Time checkpoints response error";
messages::internalError(aResp->res);
return;
}
std::span<const BootTimeCheckpoint> filteredCheckpoints;
if (specificIndex != nullptr)
{
const size_t index = *specificIndex;
if (index >= checkpoints.size())
{
messages::resourceNotFound(
aResp->res, "LogEntry",
generateBootTimeLogId(BootTimeDataType::CHECKPOINT, index));
return;
}
filteredCheckpoints = std::span<const BootTimeCheckpoint>(
std::next(checkpoints.begin(), static_cast<int>(index)),
std::next(checkpoints.begin(), static_cast<int>(index + 1)));
}
else
{
filteredCheckpoints =
std::span<const BootTimeCheckpoint>(checkpoints);
}
nlohmann::json logEntryArray = nlohmann::json::array();
size_t currentIndex = specificIndex == nullptr ? 0 : *specificIndex;
for (const BootTimeCheckpoint& checkpoint : filteredCheckpoints)
{
const auto [name, wallTime, monoTime] = checkpoint;
nlohmann::json::object_t diagnosticDataJson;
diagnosticDataJson["Name"] = name;
diagnosticDataJson["WallTimeMs"] = wallTime;
diagnosticDataJson["MonoTimeMs"] = monoTime;
try
{
std::shared_ptr<nlohmann::json::object_t> logEntry =
makeBootTimeLogEntry(BootTimeDataType::CHECKPOINT,
diagnosticDataJson, currentIndex,
*parentODataId);
logEntryArray.push_back(std::move(*logEntry));
}
catch (...)
{
// May face error while parsing data to json or to base64
BMCWEB_LOG_DEBUG
<< "Error while making Boot time log entry for Checkpoint"
<< std::to_string(currentIndex);
messages::internalError(aResp->res);
return;
}
currentIndex++;
}
successCb(logEntryArray);
},
"com.google.gbmc.boot_time_monitor",
"/xyz/openbmc_project/time/boot/" + host,
"xyz.openbmc_project.Time.Boot.Checkpoint", "GetCheckpointList");
}
static void getBootTimeDurationLogEntries(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& host,
const std::shared_ptr<const std::string>& parentODataId,
const std::shared_ptr<size_t>& specificIndex,
const std::function<void(const nlohmann::json& logEntryArray)>&
successCallback)
{
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
aResp->strand_,
[aResp, parentODataId, specificIndex,
successCb{successCallback}](
const boost::system::error_code& ec,
const std::vector<BootTimeDuration>& durations) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS Boot Time durations response error";
messages::internalError(aResp->res);
return;
}
std::span<const BootTimeDuration> filteredDurations;
if (specificIndex != nullptr)
{
const size_t index = *specificIndex;
if (index >= durations.size())
{
messages::resourceNotFound(
aResp->res, "LogEntry",
generateBootTimeLogId(BootTimeDataType::DURATION, index));
return;
}
filteredDurations = std::span<const BootTimeDuration>(
std::next(durations.begin(), static_cast<int>(index)),
std::next(durations.begin(), static_cast<int>(index + 1)));
}
else
{
filteredDurations = std::span<const BootTimeDuration>(durations);
}
nlohmann::json logEntryArray = nlohmann::json::array();
size_t currentIndex = specificIndex == nullptr ? 0 : *specificIndex;
for (const BootTimeDuration& duration : filteredDurations)
{
nlohmann::json::object_t diagnosticDataJson;
const auto [name, durationMs] = duration;
diagnosticDataJson["Name"] = name;
diagnosticDataJson["DurationMs"] = durationMs;
try
{
std::shared_ptr<nlohmann::json::object_t> logEntry =
makeBootTimeLogEntry(BootTimeDataType::DURATION,
diagnosticDataJson, currentIndex,
*parentODataId);
logEntryArray.push_back(std::move(*logEntry));
}
catch (...)
{
// May face error while parsing data to json or to base64
BMCWEB_LOG_DEBUG
<< "Error while making Boot time log entry for Duration"
<< std::to_string(currentIndex);
messages::internalError(aResp->res);
return;
}
currentIndex++;
}
successCb(logEntryArray);
},
"com.google.gbmc.boot_time_monitor",
"/xyz/openbmc_project/time/boot/" + host,
"xyz.openbmc_project.Time.Boot.Duration", "GetAdditionalDurations");
}
static void getBootTimeStatisticLogEntry(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& host,
const std::shared_ptr<const std::string>& parentODataId,
const std::function<void(const nlohmann::json& logEntryArray)>&
successCallback)
{
managedStore::ManagedObjectStoreContext requestContext(aResp);
dbus_utils::getProperty<bool>(
"com.google.gbmc.boot_time_monitor",
"/xyz/openbmc_project/time/boot/" + host,
"xyz.openbmc_project.Time.Boot.Statistic", "IsRebooting",
requestContext,
[aResp, parentODataId, successCb{successCallback}](
const boost::system::error_code& ec, const bool rebooting) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS Boot Time rebooting response error" << ec;
messages::internalError(aResp->res);
return;
}
nlohmann::json logEntryArray = nlohmann::json::array();
nlohmann::json::object_t diagnosticDataJson;
diagnosticDataJson["IsRebooting"] = rebooting;
try
{
std::shared_ptr<nlohmann::json::object_t> logEntry =
makeBootTimeLogEntry(BootTimeDataType::STATISTIC,
diagnosticDataJson, 0, *parentODataId);
logEntryArray.push_back(std::move(*logEntry));
}
catch (...)
{
// May face error while parsing data to json or to base64
BMCWEB_LOG_DEBUG
<< "Error while making Boot time log entry for Statistic0";
messages::internalError(aResp->res);
return;
}
successCb(logEntryArray);
});
}
inline void addBootLogEntriesToRespAndCount(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const nlohmann::json& logEntryArray)
{
for (auto entry : logEntryArray)
{
asyncResp->res.jsonValue["Members"].push_back(std::move(entry));
}
asyncResp->res.jsonValue["Members@odata.count"] =
asyncResp->res.jsonValue["Members"].size();
}
inline void handleBootTimeLogsEntryCollectionGet(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& host)
{
asyncResp->res.jsonValue["@odata.type"] =
"#LogEntryCollection.LogEntryCollection";
asyncResp->res.jsonValue["Name"] = "Boot Time Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of Boot time Log Entries with checkpoints and duration";
std::shared_ptr<const std::string> parentId =
std::make_shared<const std::string>(
asyncResp->res.jsonValue["@odata.id"]);
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
getBootTimeCheckpointLogEntries(
asyncResp, host, parentId, nullptr,
std::bind_front(addBootLogEntriesToRespAndCount, asyncResp));
getBootTimeDurationLogEntries(
asyncResp, host, parentId, nullptr,
std::bind_front(addBootLogEntriesToRespAndCount, asyncResp));
getBootTimeStatisticLogEntry(
asyncResp, host, parentId,
std::bind_front(addBootLogEntriesToRespAndCount, asyncResp));
}
void handleSystemBootTimeLogsEntryCollectionGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// only for single host
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/" + systemName + "/LogServices/BootTime/Entries";
handleBootTimeLogsEntryCollectionGet(asyncResp, "host0");
}
void handleManagerBootTimeLogsEntryCollectionGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Managers/bmc/LogServices/BootTime/Entries";
handleBootTimeLogsEntryCollectionGet(asyncResp, "bmc");
}
void requestRoutesBootTimeLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/BootTime/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleSystemBootTimeLogsEntryCollectionGet, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/BootTime/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleManagerBootTimeLogsEntryCollectionGet, std::ref(app)));
}
static bool parseBootTimeLogEntryId(const std::string& entryId,
BootTimeDataType& dataType, size_t& index)
{
size_t typeIndex = 0;
for (const std::string_view type : bootTimeDataTypeString)
{
if (entryId.starts_with(type.data()))
{
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
auto [_, ec] =
std::from_chars(entryId.c_str() + type.length(),
entryId.c_str() + entryId.length(), index);
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
// calling std::errc without any argument means successful parse
if (ec != std::errc())
{
return false;
}
dataType = static_cast<BootTimeDataType>(typeIndex);
return (dataType != BootTimeDataType::STATISTIC || index == 0);
}
typeIndex++;
}
return false;
}
inline void
mergeBootLogEntryToResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::shared_ptr<const std::string>& entryId,
const nlohmann::json& logEntryArray)
{
if (asyncResp->res.jsonValue.contains("error"))
{
return;
}
if (logEntryArray.empty())
{
messages::resourceNotFound(asyncResp->res, "LogEntry", *entryId);
return;
}
asyncResp->res.jsonValue.merge_patch(logEntryArray[0]);
}
inline void handleBootTimeLogsEntryGet(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& host, const std::string& entryId,
const std::string& parentODataId)
{
BootTimeDataType dataType = BootTimeDataType::CHECKPOINT;
size_t index = 0;
if (!parseBootTimeLogEntryId(entryId, dataType, index))
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
return;
}
std::shared_ptr<const std::string> parentId =
std::make_shared<const std::string>(parentODataId);
std::shared_ptr<const std::string> entryIdPtr =
std::make_shared<const std::string>(entryId);
std::shared_ptr<size_t> indexPt = std::make_shared<size_t>(index);
if (dataType == BootTimeDataType::CHECKPOINT)
{
getBootTimeCheckpointLogEntries(
asyncResp, host, parentId, indexPt,
std::bind_front(mergeBootLogEntryToResp, asyncResp, entryIdPtr));
}
else if (dataType == BootTimeDataType::DURATION)
{
getBootTimeDurationLogEntries(
asyncResp, host, parentId, indexPt,
std::bind_front(mergeBootLogEntryToResp, asyncResp, entryIdPtr));
}
else
{
getBootTimeStatisticLogEntry(
asyncResp, host, parentId,
std::bind_front(mergeBootLogEntryToResp, asyncResp, entryIdPtr));
}
}
void handleSystemBootTimeLogsEntryGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& systemName, const std::string& entryId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// only for single host
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
handleBootTimeLogsEntryGet(asyncResp, "host0", entryId,
"/redfish/v1/Systems/" + systemName +
"/LogServices/BootTime/Entries");
}
void handleManagerBootTimeLogsEntryGet(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& entryId)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
handleBootTimeLogsEntryGet(
asyncResp, "bmc", entryId,
"/redfish/v1/Managers/bmc/LogServices/BootTime/Entries");
}
void requestRoutesBootTimeLogEntry(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/<str>/LogServices/BootTime/Entries/<str>")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleSystemBootTimeLogsEntryGet, std::ref(app)));
BMCWEB_ROUTE(app,
"/redfish/v1/Managers/bmc/LogServices/BootTime/Entries/<str>")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleManagerBootTimeLogsEntryGet, std::ref(app)));
}
void handlePPRServiceGet(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
// Copy over the static data to include the entries added by SubRoute
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostPackageRepair";
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
asyncResp->res.jsonValue["Name"] = "Open BMC Oem PPR Service";
asyncResp->res.jsonValue["Description"] = "Oem Post Package Repair Service";
asyncResp->res.jsonValue["Id"] = "Oem ppr";
asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
asyncResp->res.jsonValue["Members"] = {
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/PostPackageRepair/"
"Config"},
{"@odata.id",
"/redfish/v1/Systems/system/LogServices/PostPackageRepair/"
"Status"},
};
asyncResp->res.jsonValue["Members@odata.count"] =
asyncResp->res.jsonValue["Members"].size();
asyncResp->res.jsonValue["Actions"] = {
{"#LogService.pprStatus",
{{"target", "/redfish/v1/Systems/system/LogServices/PostPackageRepair/"
"Status"}}},
{"#LogService.pprFile",
{{"target", "/redfish/v1/Systems/system/LogServices/PostPackageRepair/"
"RepairData"}}}};
}
void getPostPackageRepairStatus(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
requestContext.GetStrand(),
[asyncResp](
const boost::system::error_code& ec,
const std::vector<std::tuple<uint16_t, uint16_t, uint16_t, uint16_t,
std::vector<uint16_t>>>&
postpackagerepairstatus) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBUS response error in getPostPackageRepairStatus"
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "D-Bus getPostPackageRepairStatus success size : "
<< postpackagerepairstatus.size();
nlohmann::json pprDataOut = nlohmann::json::array();
int count = 0;
for (const auto& resolveList : postpackagerepairstatus)
{
BMCWEB_LOG_INFO << "Testing : " << std::get<0>(resolveList);
uint16_t repairEntryNum = std::get<0>(resolveList);
uint16_t repairType = std::get<1>(resolveList);
uint16_t socNum = std::get<2>(resolveList);
uint16_t repairResult = std::get<3>(resolveList);
std::vector<uint16_t> payload = std::get<4>(resolveList);
nlohmann::json jsonPpr = {{"repairEntryNum", repairEntryNum},
{"repairType", repairType},
{"socNum", socNum},
{"repairResult", repairResult},
{"payload", payload}};
pprDataOut.push_back(jsonPpr);
count++;
}
asyncResp->res.jsonValue["Members"] = pprDataOut;
asyncResp->res.jsonValue["Members@odata.count"] = count;
},
pprFileObject, pprFilePath, pprFileInterface,
"getPostPackageRepairStatus");
messages::success(asyncResp->res);
}
void handlePPRStatusGet(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostPackageRepair/"
"Status";
asyncResp->res.jsonValue["Name"] = "Post Package Repair Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of Post Package Repair Entries";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
getPostPackageRepairStatus(asyncResp);
}
void setPostPackageRepairData(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const uint16_t Index,
const uint16_t repairEntryNum, const uint16_t repairType,
const uint16_t socNum, std::vector<uint16_t>& payload)
{
std::optional<bool> RecordAdd = true;
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
requestContext.GetStrand(),
[asyncResp, RecordAdd, Index, requestContext](
const boost::system::error_code& ec, const bool& recordAdd) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBUS POST Package Repair Record Add error";
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "DBUS POST Package Repair Record Added : "
<< int(recordAdd);
managedStore::GetManagedObjectStore()->setProperty(
pprFileObject, pprFilePath, pprFileInterface, "RecordAdd",
*RecordAdd,
[asyncResp, Index,
requestContext](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_INFO << "D-Bus Record Add success";
managedStore::GetManagedObjectStore()
->PostDbusCallToIoContextThreadSafe(
requestContext.GetStrand(),
[asyncResp](const boost::system::error_code& ec,
const uint32_t& startRuntimeRepair) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBUS start Runtime Repair error";
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_INFO << "DBUS success start Runtime Repair : "
<< startRuntimeRepair;
},
pprFileObject, pprFilePath, pprFileInterface,
"startRuntimeRepair", Index);
});
},
pprFileObject, pprFilePath, pprFileInterface,
"setPostPackageRepairData", repairEntryNum, repairType, socNum,
payload);
}
static bool& getOobPprEnable()
{
static bool oobPprEnable = false;
return oobPprEnable;
}
void handlePPRFilePatch(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
uint16_t RepairType = 0;
uint16_t RepairEntryNum = 0;
uint16_t SocNum = 0;
uint16_t Index = 0;
uint16_t RuntimeIndex = 0;
nlohmann::json jsonRequest;
bool& oobPprEnable = getOobPprEnable();
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
dbus_utils::getProperty<bool>(
pprFileObject, pprFilePath, pprFileInterface, "oobPprEnable",
requestContext,
[asyncResp, &oobPprEnable](const boost::system::error_code& ec,
const bool oobPpr) {
if (ec)
{
BMCWEB_LOG_ERROR << "D-Bus responses error for OOB PPR Enable: "
<< ec;
messages::internalError(asyncResp->res);
return;
}
oobPprEnable = oobPpr;
});
if (!oobPprEnable)
{
BMCWEB_LOG_ERROR << "Error: OOB PPR is Not Enabled";
messages::internalError(asyncResp->res);
return;
}
if (!json_util::processJsonFromRequest(asyncResp->res, req, jsonRequest))
{
BMCWEB_LOG_ERROR << "Error: Json value not readable";
messages::malformedJSON(asyncResp->res);
return;
}
for (const auto& el : jsonRequest["pprDataIn"].items())
{
std::vector<uint16_t> Payload;
BMCWEB_LOG_DEBUG << "Key " << el.key();
BMCWEB_LOG_DEBUG << "value " << el.value();
if (!json_util::readJson(el.value(), asyncResp->res, "RepairType",
RepairType, "RepairEntryNum", RepairEntryNum,
"SocNum", SocNum, "Payload", Payload))
{
BMCWEB_LOG_ERROR << "Error: Issue with Json value read";
messages::malformedJSON(asyncResp->res);
return;
}
if ((RepairType & kPPRTypeBoottimeMask) == 0)
{
RuntimeIndex++;
if (RuntimeIndex > kMaxRuntimePPRCount)
{
BMCWEB_LOG_ERROR << "Error: Exceed Runtime PPR Max Entry of "
<< kMaxRuntimePPRCount;
messages::invalidObject(
asyncResp->res,
"Runtime PPR entries exceed PPR Max Entry of " +
std::to_string(kMaxRuntimePPRCount));
return;
}
}
setPostPackageRepairData(asyncResp, Index, RepairEntryNum, RepairType,
SocNum, Payload);
Index++;
}
messages::success(asyncResp->res);
}
void getPostPackageRepairConfig(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
{
managedStore::ManagedObjectStoreContext requestContext(aResp);
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
requestContext.GetStrand(),
[aResp](const boost::system::error_code& ec,
const std::vector<uint16_t>& postpackagerepairconfig) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBUS response error in getPostPackageRepairConfig"
<< ec.message();
messages::internalError(aResp->res);
return;
}
nlohmann::json pprConfig = nlohmann::json::array();
bool RtToBt = false;
bool BtSetToHard = false;
bool& oobPprEnable = getOobPprEnable();
oobPprEnable = (postpackagerepairconfig[0] != 0);
RtToBt = (postpackagerepairconfig[1] != 0);
BtSetToHard = (postpackagerepairconfig[2] != 0);
nlohmann::json jsonPpr = {{"OobPprEnable", oobPprEnable},
{"autoScheduleRtAsBtPpr", RtToBt},
{"autoScheduleBtAsHard", BtSetToHard}};
pprConfig.push_back(jsonPpr);
aResp->res.jsonValue["Members"] = pprConfig;
aResp->res.jsonValue["Members@odata.count"] = 1;
},
pprFileObject, pprFilePath, pprFileInterface,
"getPostPackageRepairConfig");
messages::success(aResp->res);
}
void setPostPackageRepairConfig(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const uint16_t& flag,
const bool& data)
{
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
requestContext.GetStrand(),
[asyncResp](const boost::system::error_code& ec, const bool& result) {
if (ec)
{
BMCWEB_LOG_ERROR
<< "DBUS response error in setPostPackageRepairConfig"
<< ec.message();
messages::internalError(asyncResp->res);
return;
}
BMCWEB_LOG_DEBUG << "DBUS POST Package Repair Config Changed : "
<< int(result);
}, pprFileObject, pprFilePath, pprFileInterface,
"setPostPackageRepairConfig", flag, data);
}
void handlePPRConfigGet(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/LogServices/PostPackageRepair/"
"Config";
asyncResp->res.jsonValue["Name"] = "Post Package Repair Config";
asyncResp->res.jsonValue["Description"] =
"Post Package Repair Configuration";
asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
asyncResp->res.jsonValue["Members@odata.count"] = 0;
getPostPackageRepairConfig(asyncResp);
}
void handlePPRConfigPatch(App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
{
if (!redfish::setUpRedfishRoute(app, req, asyncResp))
{
return;
}
std::optional<bool> BtSetToHard;
std::optional<bool> RtToBt;
uint16_t flag = 0;
bool data = false;
nlohmann::json jsonRequest;
if (!json_util::processJsonFromRequest(asyncResp->res, req, jsonRequest))
{
BMCWEB_LOG_ERROR << "Error: Json value not readable";
messages::malformedJSON(asyncResp->res);
return;
}
if (!json_util::readJson(jsonRequest, asyncResp->res,
"autoScheduleBtAsHard", BtSetToHard,
"autoScheduleRtAsBtPpr", RtToBt))
{
return;
}
if (BtSetToHard)
{
flag = kBtSetToHardMask;
data = BtSetToHard.value();
}
if (RtToBt)
{
flag = kRtToBtMask;
data = RtToBt.value();
}
if (flag == 0)
{
std::cout << "PPR Config Error: patch command, Param not found\n";
return;
}
setPostPackageRepairConfig(asyncResp, flag, data);
messages::success(asyncResp->res);
}
void requestRoutesPPRService(App& app)
{
BMCWEB_ROUTE(app,
"/redfish/v1/Systems/system/LogServices/PostPackageRepair/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handlePPRServiceGet, std::ref(app)));
}
void requestRoutesPPRStatus(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/PostPackageRepair/Status")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handlePPRStatusGet, std::ref(app)));
}
void requestRoutesPPRFile(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/system/LogServices/PostPackageRepair/RepairData")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::patch)(
std::bind_front(handlePPRFilePatch, std::ref(app)));
}
void requestRoutesPPRConfig(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/PostPackageRepair/Config")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::get)(
std::bind_front(handlePPRConfigGet, std::ref(app)));
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/system/LogServices/PostPackageRepair/Config")
.privileges({{"ConfigureManager"}})
.methods(boost::beast::http::verb::patch)(
std::bind_front(handlePPRConfigPatch, std::ref(app)));
}
} // namespace redfish