|  | #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 |