blob: 65dd312ffd45da5373bda48611a74e75b0d74038 [file] [log] [blame]
#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 "log_services.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"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish
{
inline void handleGetEventLogService(
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/EventLog";
asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService";
asyncResp->res.jsonValue["Name"] = "Event Log Service";
asyncResp->res.jsonValue["Description"] = "System Event Log Service";
asyncResp->res.jsonValue["Id"] = "EventLog";
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/Systems/system/LogServices/EventLog/Entries";
asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
{"target",
"/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog"}};
}
void requestRoutesEventLogService(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/")
.privileges(redfish::privileges::getLogService)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetEventLogService, std::ref(app)));
}
static bool
getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles)
{
static const std::filesystem::path redfishLogDir = "/var/log";
static const std::string redfishLogFilename = "redfish";
// Loop through the directory looking for redfish log files
for (const std::filesystem::directory_entry& dirEnt :
std::filesystem::directory_iterator(redfishLogDir))
{
// If we find a redfish log file, save the path
std::string filename = dirEnt.path().filename();
if (filename.starts_with(redfishLogFilename))
{
redfishLogFiles.emplace_back(redfishLogDir / filename);
}
}
// As the log files rotate, they are appended with a ".#" that is higher for
// the older logs. Since we don't expect more than 10 log files, we
// can just sort the list to get them in order from newest to oldest
std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
return !redfishLogFiles.empty();
}
inline void handlePostJournalEventLogClear(
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;
}
// Clear the EventLog by deleting the log files
std::vector<std::filesystem::path> redfishLogFiles;
if (getRedfishLogFiles(redfishLogFiles))
{
for (const std::filesystem::path& file : redfishLogFiles)
{
std::error_code ec;
std::filesystem::remove(file, ec);
}
}
// Reload rsyslog so it knows to start new log files
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
messages::internalError(asyncResp->res);
return;
}
messages::success(asyncResp->res);
}, "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
"replace");
}
void requestRoutesJournalEventLogClear(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
.privileges({{"ConfigureComponents"}})
.methods(boost::beast::http::verb::post)(
std::bind_front(handlePostJournalEventLogClear, std::ref(app)));
}
static LogParseError
fillEventLogEntryJson(const std::string& logEntryID,
const std::string& logEntry,
nlohmann::json::object_t& logEntryJson)
{
// The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
// First get the Timestamp
size_t space = logEntry.find_first_of(' ');
if (space == std::string::npos)
{
return LogParseError::parseFailed;
}
std::string timestamp = logEntry.substr(0, space);
// Then get the log contents
size_t entryStart = logEntry.find_first_not_of(' ', space);
if (entryStart == std::string::npos)
{
return LogParseError::parseFailed;
}
std::string_view entry(logEntry);
entry.remove_prefix(entryStart);
// Use split to separate the entry into its fields
std::vector<std::string> logEntryFields;
bmcweb::split(logEntryFields, entry, ',');
// We need at least a MessageId to be valid
if (logEntryFields.empty())
{
return LogParseError::parseFailed;
}
std::string& messageID = logEntryFields[0];
// Get the Message from the MessageRegistry
const registries::Message* message = registries::getMessage(messageID);
if (message == nullptr)
{
BMCWEB_LOG_WARNING << "Log entry not found in registry: " << logEntry;
return LogParseError::messageIdNotInRegistry;
}
std::string msg = message->message;
// Get the MessageArgs from the log if there are any
std::span<std::string> messageArgs;
if (logEntryFields.size() > 1)
{
std::string& messageArgsStart = logEntryFields[1];
// If the first string is empty, assume there are no MessageArgs
std::size_t messageArgsSize = 0;
if (!messageArgsStart.empty())
{
messageArgsSize = logEntryFields.size() - 1;
}
messageArgs = {&messageArgsStart, messageArgsSize};
// Fill the MessageArgs into the Message
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 the Created time from the timestamp. The log timestamp is in RFC3339
// format which matches the Redfish format except for the fractional seconds
// between the '.' and the '+', so just remove them.
std::size_t dot = timestamp.find_first_of('.');
std::size_t plus = timestamp.find_first_of('+');
if (dot != std::string::npos && plus != std::string::npos)
{
timestamp.erase(dot, plus - dot);
}
// 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", "system", "LogServices", "EventLog",
"Entries", logEntryID);
logEntryJson["Name"] = "System Event Log Entry";
logEntryJson["Id"] = logEntryID;
logEntryJson["Message"] = std::move(msg);
logEntryJson["MessageId"] = std::move(messageID);
logEntryJson["MessageArgs"] = messageArgs;
logEntryJson["EntryType"] = "Event";
logEntryJson["Severity"] = message->messageSeverity;
logEntryJson["Created"] = std::move(timestamp);
return LogParseError::success;
}
static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
const bool firstEntry = true)
{
static time_t prevTs = 0;
static int index = 0;
if (firstEntry)
{
prevTs = 0;
}
// Get the entry timestamp
std::time_t curTs = 0;
std::tm timeStruct = {};
std::istringstream entryStream(logEntry);
if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
{
curTs = std::mktime(&timeStruct);
}
// 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 void handleGetJournalEventLogEntryCollection(
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;
}
size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
size_t skip = delegatedQuery.skip.value_or(0);
// 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/Systems/system/LogServices/EventLog/Entries";
asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of System Event Log Entries";
nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
logEntryArray = nlohmann::json::array();
// Go through the log files and create a unique ID for each
// entry
std::vector<std::filesystem::path> redfishLogFiles;
getRedfishLogFiles(redfishLogFiles);
uint64_t entryCount = 0;
std::string logEntry;
// Oldest logs are in the last file, so start there and loop
// backwards
for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
{
std::ifstream logStream(*it);
if (!logStream.is_open())
{
continue;
}
// Reset the unique ID on the first entry
bool firstEntry = true;
while (std::getline(logStream, logEntry))
{
std::string idStr;
if (!getUniqueEntryID(logEntry, idStr, firstEntry))
{
continue;
}
firstEntry = false;
nlohmann::json::object_t bmcLogEntry;
LogParseError status =
fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
if (status == LogParseError::messageIdNotInRegistry)
{
continue;
}
if (status != LogParseError::success)
{
messages::internalError(asyncResp->res);
return;
}
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;
}
logEntryArray.push_back(std::move(bmcLogEntry));
}
}
asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
if (skip + top < entryCount)
{
asyncResp->res.jsonValue["Members@odata.nextLink"] =
"/redfish/v1/Systems/system/LogServices/EventLog/Entries?$skip=" +
std::to_string(skip + top);
}
}
void requestRoutesJournalEventLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleGetJournalEventLogEntryCollection, std::ref(app)));
}
inline void handleGetJournalEventLogEntry(
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& targetID = param;
// Go through the log files and check the unique ID for each
// entry to find the target entry
std::vector<std::filesystem::path> redfishLogFiles;
getRedfishLogFiles(redfishLogFiles);
std::string logEntry;
// Oldest logs are in the last file, so start there and loop
// backwards
for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
{
std::ifstream logStream(*it);
if (!logStream.is_open())
{
continue;
}
// Reset the unique ID on the first entry
bool firstEntry = true;
while (std::getline(logStream, logEntry))
{
std::string idStr;
if (!getUniqueEntryID(logEntry, idStr, firstEntry))
{
continue;
}
firstEntry = false;
if (idStr == targetID)
{
nlohmann::json::object_t bmcLogEntry;
LogParseError status =
fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
if (status != LogParseError::success)
{
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue.update(bmcLogEntry);
return;
}
}
}
// Requested ID was not found
messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
}
void requestRoutesJournalEventLogEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetJournalEventLogEntry, std::ref(app)));
}
inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
{
std::optional<bool> notifyAction;
if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
{
notifyAction = true;
}
else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
{
notifyAction = false;
}
return notifyAction;
}
inline std::string translateSeverityDbusToRedfish(const std::string& s)
{
if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
{
return "Critical";
}
if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
(s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
{
return "OK";
}
if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
{
return "Warning";
}
return "";
}
inline void handleGetDBusEventLogEntryCollection(
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;
}
// 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/Systems/system/LogServices/EventLog/Entries";
asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
asyncResp->res.jsonValue["Description"] =
"Collection of System Event Log Entries";
managedStore::ManagedObjectStoreContext context(asyncResp);
// DBus implementation of EventLog/Entries
// Make call to Logging Service to find all log entry objects
managedStore::GetManagedObjectStore()->getManagedObjectsWithContext(
"xyz.openbmc_project.Logging", {"/xyz/openbmc_project/logging"},
context,
[asyncResp](const boost::system::error_code& ec,
const dbus::utility::ManagedObjectType& resp) {
if (ec)
{
// TODO Handle for specific error code
BMCWEB_LOG_ERROR << "getLogEntriesIfaceData resp_handler got error "
<< ec;
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
entriesArray = nlohmann::json::array();
for (const auto& objectPath : resp)
{
const uint32_t* id = nullptr;
const uint64_t* timestamp = nullptr;
const uint64_t* updateTimestamp = nullptr;
const std::string* severity = nullptr;
const std::string* message = nullptr;
const std::string* filePath = nullptr;
const std::string* resolution = nullptr;
bool resolved = false;
const std::string* notify = nullptr;
for (const auto& interfaceMap : objectPath.second)
{
if (interfaceMap.first == "xyz.openbmc_project.Logging.Entry")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Id")
{
id = std::get_if<uint32_t>(&propertyMap.second);
}
else if (propertyMap.first == "Timestamp")
{
timestamp =
std::get_if<uint64_t>(&propertyMap.second);
}
else if (propertyMap.first == "UpdateTimestamp")
{
updateTimestamp =
std::get_if<uint64_t>(&propertyMap.second);
}
else if (propertyMap.first == "Severity")
{
severity =
std::get_if<std::string>(&propertyMap.second);
}
else if (propertyMap.first == "Resolution")
{
resolution =
std::get_if<std::string>(&propertyMap.second);
}
else if (propertyMap.first == "Message")
{
message =
std::get_if<std::string>(&propertyMap.second);
}
else if (propertyMap.first == "Resolved")
{
const bool* resolveptr =
std::get_if<bool>(&propertyMap.second);
if (resolveptr == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
resolved = *resolveptr;
}
else if (propertyMap.first == "ServiceProviderNotify")
{
notify =
std::get_if<std::string>(&propertyMap.second);
if (notify == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
}
}
if (id == nullptr || message == nullptr ||
severity == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
}
else if (interfaceMap.first ==
"xyz.openbmc_project.Common.FilePath")
{
for (const auto& propertyMap : interfaceMap.second)
{
if (propertyMap.first == "Path")
{
filePath =
std::get_if<std::string>(&propertyMap.second);
}
}
}
}
// Object path without the
// xyz.openbmc_project.Logging.Entry interface, ignore
// and continue.
if (id == nullptr || message == nullptr || severity == nullptr ||
timestamp == nullptr || updateTimestamp == nullptr)
{
continue;
}
entriesArray.push_back({});
nlohmann::json& thisEntry = entriesArray.back();
thisEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
thisEntry["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", "system", "LogServices", "EventLog",
"Entries", std::to_string(*id));
thisEntry["Name"] = "System Event Log Entry";
thisEntry["Id"] = std::to_string(*id);
thisEntry["Message"] = *message;
thisEntry["Resolved"] = resolved;
if ((resolution != nullptr) && (!(*resolution).empty()))
{
thisEntry["Resolution"] = *resolution;
}
std::optional<bool> notifyAction = getProviderNotifyAction(*notify);
if (notifyAction)
{
thisEntry["ServiceProviderNotified"] = *notifyAction;
}
thisEntry["EntryType"] = "Event";
thisEntry["Severity"] = translateSeverityDbusToRedfish(*severity);
thisEntry["Created"] =
redfish::time_utils::getDateTimeUintMs(*timestamp);
thisEntry["Modified"] =
redfish::time_utils::getDateTimeUintMs(*updateTimestamp);
if (filePath != nullptr)
{
thisEntry["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
std::to_string(*id) + "/attachment";
}
}
std::sort(entriesArray.begin(), entriesArray.end(),
[](const nlohmann::json& left, const nlohmann::json& right) {
return (left["Id"] <= right["Id"]);
});
asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
});
}
void requestRoutesDBusEventLogEntryCollection(App& app)
{
BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
.privileges(redfish::privileges::getLogEntryCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleGetDBusEventLogEntryCollection, std::ref(app)));
}
void handleDeleteEventLogEntry(
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;
}
BMCWEB_LOG_DEBUG << "Do delete single event entries.";
std::string entryID = param;
dbus::utility::escapePathForDbus(entryID);
// Process response from Logging service.
auto respHandler = [asyncResp,
entryID](const boost::system::error_code& ec) {
BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
if (ec)
{
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
return;
}
// TODO Handle for specific error code
BMCWEB_LOG_ERROR
<< "EventLogEntry (DBus) doDelete respHandler got error " << ec;
asyncResp->res.result(
boost::beast::http::status::internal_server_error);
return;
}
asyncResp->res.result(boost::beast::http::status::ok);
};
// Make call to Logging service to request Delete Log
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
respHandler, "xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryID,
"xyz.openbmc_project.Object.Delete", "Delete");
}
void handlePatchEventLogEntry(
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;
}
if (systemName != "system")
{
messages::resourceNotFound(asyncResp->res, "ComputerSystem",
systemName);
return;
}
std::optional<bool> resolved;
if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
{
return;
}
BMCWEB_LOG_DEBUG << "Set Resolved";
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, entryId](const boost::system::error_code& ec) {
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
}, "xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryId,
"org.freedesktop.DBus.Properties", "Set",
"xyz.openbmc_project.Logging.Entry", "Resolved",
dbus::utility::DbusVariantType(*resolved));
}
inline void handleGetDBusEventLogEntry(
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;
}
std::string entryID = param;
dbus::utility::escapePathForDbus(entryID);
// DBus implementation of EventLog/Entries
// Make call to Logging Service to find all log entry objects
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
"xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryID, "", context,
[asyncResp, entryID](const boost::system::error_code& ec,
const dbus::utility::DBusPropertiesMap& resp) {
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res, "EventLogEntry",
entryID);
return;
}
if (ec)
{
BMCWEB_LOG_ERROR << "EventLogEntry (DBus) resp_handler got error "
<< ec;
messages::internalError(asyncResp->res);
return;
}
const uint32_t* id = nullptr;
const uint64_t* timestamp = nullptr;
const uint64_t* updateTimestamp = nullptr;
const std::string* severity = nullptr;
const std::string* message = nullptr;
const std::string* filePath = nullptr;
const std::string* resolution = nullptr;
bool resolved = false;
const std::string* notify = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), resp, "Id", id, "Timestamp",
timestamp, "UpdateTimestamp", updateTimestamp, "Severity", severity,
"Message", message, "Resolved", resolved, "Resolution", resolution,
"Path", filePath, "ServiceProviderNotify", notify);
if (!success)
{
messages::internalError(asyncResp->res);
return;
}
if (id == nullptr || message == nullptr || severity == nullptr ||
timestamp == nullptr || updateTimestamp == nullptr ||
notify == nullptr)
{
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Systems", "system", "LogServices", "EventLog",
"Entries", std::to_string(*id));
asyncResp->res.jsonValue["Name"] = "System Event Log Entry";
asyncResp->res.jsonValue["Id"] = std::to_string(*id);
asyncResp->res.jsonValue["Message"] = *message;
asyncResp->res.jsonValue["Resolved"] = resolved;
std::optional<bool> notifyAction = getProviderNotifyAction(*notify);
if (notifyAction)
{
asyncResp->res.jsonValue["ServiceProviderNotified"] = *notifyAction;
}
if ((resolution != nullptr) && (!(*resolution).empty()))
{
asyncResp->res.jsonValue["Resolution"] = *resolution;
}
asyncResp->res.jsonValue["EntryType"] = "Event";
asyncResp->res.jsonValue["Severity"] =
translateSeverityDbusToRedfish(*severity);
asyncResp->res.jsonValue["Created"] =
redfish::time_utils::getDateTimeUintMs(*timestamp);
asyncResp->res.jsonValue["Modified"] =
redfish::time_utils::getDateTimeUintMs(*updateTimestamp);
if (filePath != nullptr)
{
asyncResp->res.jsonValue["AdditionalDataURI"] =
"/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
std::to_string(*id) + "/attachment";
}
});
}
void requestRoutesDBusEventLogEntry(App& app)
{
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetDBusEventLogEntry, std::ref(app)));
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
.privileges(redfish::privileges::patchLogEntry)
.methods(boost::beast::http::verb::patch)(
std::bind_front(handlePatchEventLogEntry, std::ref(app)));
BMCWEB_ROUTE(
app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
.privileges(redfish::privileges::deleteLogEntry)
.methods(boost::beast::http::verb::delete_)(
std::bind_front(handleDeleteEventLogEntry, std::ref(app)));
}
inline void handleGetDBusEventLogEntryDownload(
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 (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;
}
std::string entryID = param;
dbus::utility::escapePathForDbus(entryID);
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, entryID](const boost::system::error_code& ec,
const sdbusplus::message::unix_fd& unixfd) {
if (ec.value() == EBADR)
{
messages::resourceNotFound(asyncResp->res, "EventLogAttachment",
entryID);
return;
}
if (ec)
{
BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
return;
}
int fd = -1;
fd = dup(unixfd);
if (fd == -1)
{
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 64kb
constexpr int maxFileSize = 65536;
if (size > maxFileSize)
{
BMCWEB_LOG_ERROR << "File size exceeds maximum allowed size of "
<< maxFileSize;
messages::internalError(asyncResp->res);
return;
}
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 = crow::utility::base64encode(strData);
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() = std::move(output);
},
"xyz.openbmc_project.Logging",
"/xyz/openbmc_project/logging/entry/" + entryID,
"xyz.openbmc_project.Logging.Entry", "GetEntry");
}
void requestRoutesDBusEventLogEntryDownload(App& app)
{
BMCWEB_ROUTE(
app,
"/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment")
.privileges(redfish::privileges::getLogEntry)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetDBusEventLogEntryDownload, std::ref(app)));
}
} // namespace redfish