blob: c4a8a29eef944478f6ec10ec7a7af72e1305a84c [file] [log] [blame]
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <filesystem> // NOLINT
#include <fstream>
#include <functional>
#include <iomanip>
#include <memory>
#include <optional>
#include <span> // NOLINT
#include <sstream>
#include <string>
#include <string_view>
#include <system_error> // NOLINT
#include <utility>
#include <variant>
#include <vector>
#include "app.hpp"
#include "http_request.hpp"
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "http_utility.hpp"
#include "str_utility.hpp"
#include "error_messages.hpp"
#include "query.hpp"
#include "registries.hpp"
#include "registries/privilege_registry.hpp"
#include "dbus_utils.hpp"
#include "json_utils.hpp"
#include "time_utils.hpp"
#include "log_services.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/message/native_types.hpp"
#include "sdbusplus/unpack_properties.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