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