/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #pragma once #include "gzfile.hpp" #include "http_utility.hpp" #include "human_sort.hpp" #include "registries.hpp" #include "registries/base_message_registry.hpp" #include "registries/openbmc_message_registry.hpp" #include "task.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace redfish { constexpr char const* crashdumpObject = "com.intel.crashdump"; constexpr char const* crashdumpPath = "/com/intel/crashdump"; constexpr char const* crashdumpInterface = "com.intel.crashdump"; constexpr char const* deleteAllInterface = "xyz.openbmc_project.Collection.DeleteAll"; constexpr char const* crashdumpOnDemandInterface = "com.intel.crashdump.OnDemand"; constexpr char const* crashdumpTelemetryInterface = "com.intel.crashdump.Telemetry"; namespace registries { static const Message* getMessageFromRegistry(const std::string& messageKey, const std::span registry) { std::span::iterator messageIt = std::find_if(registry.begin(), registry.end(), [&messageKey](const MessageEntry& messageEntry) { return std::strcmp(messageEntry.first, messageKey.c_str()) == 0; }); if (messageIt != registry.end()) { return &messageIt->second; } return nullptr; } static const Message* getMessage(const std::string_view& messageID) { // Redfish MessageIds are in the form // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find // the right Message std::vector fields; fields.reserve(4); boost::split(fields, messageID, boost::is_any_of(".")); std::string& registryName = fields[0]; std::string& messageKey = fields[3]; // Find the right registry and check it for the MessageKey if (std::string(base::header.registryPrefix) == registryName) { return getMessageFromRegistry( messageKey, std::span(base::registry)); } if (std::string(openbmc::header.registryPrefix) == registryName) { return getMessageFromRegistry( messageKey, std::span(openbmc::registry)); } return nullptr; } } // namespace registries namespace fs = std::filesystem; 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 static int getJournalMetadata(sd_journal* journal, const std::string_view& field, std::string_view& contents) { const char* data = nullptr; size_t length = 0; int ret = 0; // Get the metadata from the requested field of the journal entry // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) const void** dataVoid = reinterpret_cast(&data); ret = sd_journal_get_data(journal, field.data(), dataVoid, &length); if (ret < 0) { return ret; } contents = std::string_view(data, length); // Only use the content after the "=" character. contents.remove_prefix(std::min(contents.find('=') + 1, contents.size())); return ret; } inline static int getJournalMetadata(sd_journal* journal, const std::string_view& field, const int& base, long int& contents) { int ret = 0; std::string_view metadata; // Get the metadata from the requested field of the journal entry ret = getJournalMetadata(journal, field, metadata); if (ret < 0) { return ret; } contents = strtol(metadata.data(), nullptr, base); return ret; } inline static bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) { int ret = 0; uint64_t timestamp = 0; ret = sd_journal_get_realtime_usec(journal, ×tamp); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " << strerror(-ret); return false; } entryTimestamp = crow::utility::getDateTimeUint(timestamp / 1000 / 1000); return true; } inline static bool getUniqueEntryID(sd_journal* journal, std::string& entryID, const bool firstEntry = true) { int ret = 0; static uint64_t prevTs = 0; static int index = 0; if (firstEntry) { prevTs = 0; } // Get the entry timestamp uint64_t curTs = 0; ret = sd_journal_get_realtime_usec(journal, &curTs); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " << strerror(-ret); return false; } // If the timestamp isn't unique, increment the index if (curTs == prevTs) { index++; } else { // Otherwise, reset it index = 0; } // Save the timestamp prevTs = curTs; entryID = std::to_string(curTs); if (index > 0) { entryID += "_" + std::to_string(index); } return true; } 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 static bool getTimestampFromID(const std::shared_ptr& asyncResp, const std::string& entryID, uint64_t& timestamp, uint64_t& index) { if (entryID.empty()) { return false; } // Convert the unique ID back to a timestamp to find the entry std::string_view tsStr(entryID); auto underscorePos = tsStr.find('_'); if (underscorePos != std::string_view::npos) { // Timestamp has an index tsStr.remove_suffix(tsStr.size() - underscorePos); std::string_view indexStr(entryID); indexStr.remove_prefix(underscorePos + 1); auto [ptr, ec] = std::from_chars( indexStr.data(), indexStr.data() + indexStr.size(), index); if (ec != std::errc()) { messages::resourceMissingAtURI( asyncResp->res, crow::utility::urlFromPieces(entryID)); return false; } } // Timestamp has no index auto [ptr, ec] = std::from_chars(tsStr.data(), tsStr.data() + tsStr.size(), timestamp); if (ec != std::errc()) { messages::resourceMissingAtURI(asyncResp->res, crow::utility::urlFromPieces(entryID)); return false; } return true; } static bool getRedfishLogFiles(std::vector& 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 (boost::starts_with(filename, 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 getDumpEntryCollection(const std::shared_ptr& asyncResp, const std::string& dumpType) { std::string dumpPath; if (dumpType == "BMC") { dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/"; } else if (dumpType == "System") { dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/"; } else { BMCWEB_LOG_ERROR << "Invalid dump type" << dumpType; messages::internalError(asyncResp->res); return; } crow::connections::systemBus->async_method_call( [asyncResp, dumpPath, dumpType](const boost::system::error_code ec, dbus::utility::ManagedObjectType& resp) { if (ec) { BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec; messages::internalError(asyncResp->res); return; } nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"]; entriesArray = nlohmann::json::array(); std::string dumpEntryPath = "/xyz/openbmc_project/dump/" + std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/"; std::sort(resp.begin(), resp.end(), [](const auto& l, const auto& r) { return AlphanumLess()(l.first.filename(), r.first.filename()); }); for (auto& object : resp) { if (object.first.str.find(dumpEntryPath) == std::string::npos) { continue; } uint64_t timestamp = 0; uint64_t size = 0; std::string dumpStatus; nlohmann::json thisEntry; std::string entryID = object.first.filename(); if (entryID.empty()) { continue; } for (auto& interfaceMap : object.second) { if (interfaceMap.first == "xyz.openbmc_project.Common.Progress") { for (const auto& propertyMap : interfaceMap.second) { if (propertyMap.first == "Status") { const auto* status = std::get_if(&propertyMap.second); if (status == nullptr) { messages::internalError(asyncResp->res); break; } dumpStatus = *status; } } } else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry") { for (auto& propertyMap : interfaceMap.second) { if (propertyMap.first == "Size") { const auto* sizePtr = std::get_if(&propertyMap.second); if (sizePtr == nullptr) { messages::internalError(asyncResp->res); break; } size = *sizePtr; break; } } } else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime") { for (const auto& propertyMap : interfaceMap.second) { if (propertyMap.first == "Elapsed") { const uint64_t* usecsTimeStamp = std::get_if(&propertyMap.second); if (usecsTimeStamp == nullptr) { messages::internalError(asyncResp->res); break; } timestamp = (*usecsTimeStamp / 1000 / 1000); break; } } } } if (dumpStatus != "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" && !dumpStatus.empty()) { // Dump status is not Complete, no need to enumerate continue; } thisEntry["@odata.type"] = "#LogEntry.v1_8_0.LogEntry"; thisEntry["@odata.id"] = dumpPath + entryID; thisEntry["Id"] = entryID; thisEntry["EntryType"] = "Event"; thisEntry["Created"] = crow::utility::getDateTimeUint(timestamp); thisEntry["Name"] = dumpType + " Dump Entry"; thisEntry["AdditionalDataSizeBytes"] = size; if (dumpType == "BMC") { thisEntry["DiagnosticDataType"] = "Manager"; thisEntry["AdditionalDataURI"] = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/" + entryID + "/attachment"; } else if (dumpType == "System") { thisEntry["DiagnosticDataType"] = "OEM"; thisEntry["OEMDiagnosticDataType"] = "System"; thisEntry["AdditionalDataURI"] = "/redfish/v1/Systems/system/LogServices/Dump/Entries/" + entryID + "/attachment"; } entriesArray.push_back(std::move(thisEntry)); } asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); }, "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); } inline void getDumpEntryById(const std::shared_ptr& asyncResp, const std::string& entryID, const std::string& dumpType) { std::string dumpPath; if (dumpType == "BMC") { dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/"; } else if (dumpType == "System") { dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/"; } else { BMCWEB_LOG_ERROR << "Invalid dump type" << dumpType; messages::internalError(asyncResp->res); return; } crow::connections::systemBus->async_method_call( [asyncResp, entryID, dumpPath, dumpType](const boost::system::error_code ec, dbus::utility::ManagedObjectType& resp) { if (ec) { BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec; messages::internalError(asyncResp->res); return; } bool foundDumpEntry = false; std::string dumpEntryPath = "/xyz/openbmc_project/dump/" + std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/"; for (const auto& objectPath : resp) { if (objectPath.first.str != dumpEntryPath + entryID) { continue; } foundDumpEntry = true; uint64_t timestamp = 0; uint64_t size = 0; std::string dumpStatus; for (const auto& interfaceMap : objectPath.second) { if (interfaceMap.first == "xyz.openbmc_project.Common.Progress") { for (const auto& propertyMap : interfaceMap.second) { if (propertyMap.first == "Status") { const std::string* status = std::get_if(&propertyMap.second); if (status == nullptr) { messages::internalError(asyncResp->res); break; } dumpStatus = *status; } } } else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry") { for (const auto& propertyMap : interfaceMap.second) { if (propertyMap.first == "Size") { const uint64_t* sizePtr = std::get_if(&propertyMap.second); if (sizePtr == nullptr) { messages::internalError(asyncResp->res); break; } size = *sizePtr; break; } } } else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime") { for (const auto& propertyMap : interfaceMap.second) { if (propertyMap.first == "Elapsed") { const uint64_t* usecsTimeStamp = std::get_if(&propertyMap.second); if (usecsTimeStamp == nullptr) { messages::internalError(asyncResp->res); break; } timestamp = *usecsTimeStamp / 1000 / 1000; break; } } } } if (dumpStatus != "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" && !dumpStatus.empty()) { // Dump status is not Complete // return not found until status is changed to Completed messages::resourceNotFound(asyncResp->res, dumpType + " dump", entryID); return; } asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_8_0.LogEntry"; asyncResp->res.jsonValue["@odata.id"] = dumpPath + entryID; asyncResp->res.jsonValue["Id"] = entryID; asyncResp->res.jsonValue["EntryType"] = "Event"; asyncResp->res.jsonValue["Created"] = crow::utility::getDateTimeUint(timestamp); asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry"; asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size; if (dumpType == "BMC") { asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager"; asyncResp->res.jsonValue["AdditionalDataURI"] = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/" + entryID + "/attachment"; } else if (dumpType == "System") { asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM"; asyncResp->res.jsonValue["OEMDiagnosticDataType"] = "System"; asyncResp->res.jsonValue["AdditionalDataURI"] = "/redfish/v1/Systems/system/LogServices/Dump/Entries/" + entryID + "/attachment"; } } if (!foundDumpEntry) { BMCWEB_LOG_ERROR << "Can't find Dump Entry"; messages::internalError(asyncResp->res); return; } }, "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); } inline void deleteDumpEntry(const std::shared_ptr& asyncResp, const std::string& entryID, const std::string& dumpType) { auto respHandler = [asyncResp, entryID](const boost::system::error_code ec) { BMCWEB_LOG_DEBUG << "Dump Entry doDelete callback: Done"; if (ec) { if (ec.value() == EBADR) { messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); return; } BMCWEB_LOG_ERROR << "Dump (DBus) doDelete respHandler got error " << ec; messages::internalError(asyncResp->res); return; } }; crow::connections::systemBus->async_method_call( respHandler, "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump/" + std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/" + entryID, "xyz.openbmc_project.Object.Delete", "Delete"); } inline void createDumpTaskCallback(task::Payload&& payload, const std::shared_ptr& asyncResp, const uint32_t& dumpId, const std::string& dumpPath, const std::string& dumpType) { std::shared_ptr task = task::TaskData::createTask( [dumpId, dumpPath, dumpType]( boost::system::error_code err, sdbusplus::message::message& m, const std::shared_ptr& taskData) { if (err) { BMCWEB_LOG_ERROR << "Error in creating a dump"; taskData->state = "Cancelled"; return task::completed; } dbus::utility::DBusInteracesMap interfacesList; sdbusplus::message::object_path objPath; m.read(objPath, interfacesList); if (objPath.str == "/xyz/openbmc_project/dump/" + std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/" + std::to_string(dumpId)) { nlohmann::json retMessage = messages::success(); taskData->messages.emplace_back(retMessage); std::string headerLoc = "Location: " + dumpPath + std::to_string(dumpId); taskData->payload->httpHeaders.emplace_back(std::move(headerLoc)); taskData->state = "Completed"; return task::completed; } return task::completed; }, "type='signal',interface='org.freedesktop.DBus.ObjectManager'," "member='InterfacesAdded', " "path='/xyz/openbmc_project/dump'"); task->startTimer(std::chrono::minutes(3)); task->populateResp(asyncResp->res); task->payload.emplace(std::move(payload)); } inline void createDump(const std::shared_ptr& asyncResp, const crow::Request& req, const std::string& dumpType) { std::string dumpPath; if (dumpType == "BMC") { dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/"; } else if (dumpType == "System") { dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/"; } else { BMCWEB_LOG_ERROR << "Invalid dump type: " << dumpType; messages::internalError(asyncResp->res); return; } std::optional diagnosticDataType; std::optional oemDiagnosticDataType; if (!redfish::json_util::readJsonAction( req, asyncResp->res, "DiagnosticDataType", diagnosticDataType, "OEMDiagnosticDataType", oemDiagnosticDataType)) { return; } if (dumpType == "System") { if (!oemDiagnosticDataType || !diagnosticDataType) { BMCWEB_LOG_ERROR << "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!"; messages::actionParameterMissing( asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType & OEMDiagnosticDataType"); return; } if ((*oemDiagnosticDataType != "System") || (*diagnosticDataType != "OEM")) { BMCWEB_LOG_ERROR << "Wrong parameter values passed"; messages::internalError(asyncResp->res); return; } } else if (dumpType == "BMC") { if (!diagnosticDataType) { BMCWEB_LOG_ERROR << "CreateDump action parameter 'DiagnosticDataType' not found!"; messages::actionParameterMissing( asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType"); return; } if (*diagnosticDataType != "Manager") { BMCWEB_LOG_ERROR << "Wrong parameter value passed for 'DiagnosticDataType'"; messages::internalError(asyncResp->res); return; } } crow::connections::systemBus->async_method_call( [asyncResp, payload(task::Payload(req)), dumpPath, dumpType](const boost::system::error_code ec, const uint32_t& dumpId) mutable { if (ec) { BMCWEB_LOG_ERROR << "CreateDump resp_handler got error " << ec; messages::internalError(asyncResp->res); return; } BMCWEB_LOG_DEBUG << "Dump Created. Id: " << dumpId; createDumpTaskCallback(std::move(payload), asyncResp, dumpId, dumpPath, dumpType); }, "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump/" + std::string(boost::algorithm::to_lower_copy(dumpType)), "xyz.openbmc_project.Dump.Create", "CreateDump"); } inline void clearDump(const std::shared_ptr& asyncResp, const std::string& dumpType) { std::string dumpTypeLowerCopy = std::string(boost::algorithm::to_lower_copy(dumpType)); crow::connections::systemBus->async_method_call( [asyncResp, dumpType]( const boost::system::error_code ec, const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) { if (ec) { BMCWEB_LOG_ERROR << "resp_handler got error " << ec; messages::internalError(asyncResp->res); return; } for (const std::string& path : subTreePaths) { sdbusplus::message::object_path objPath(path); std::string logID = objPath.filename(); if (logID.empty()) { continue; } deleteDumpEntry(asyncResp, logID, dumpType); } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/xyz/openbmc_project/dump/" + dumpTypeLowerCopy, 0, std::array{"xyz.openbmc_project.Dump.Entry." + dumpType}); } inline static void parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap& params, std::string& filename, std::string& timestamp, std::string& logfile) { for (auto property : params) { if (property.first == "Timestamp") { const std::string* value = std::get_if(&property.second); if (value != nullptr) { timestamp = *value; } } else if (property.first == "Filename") { const std::string* value = std::get_if(&property.second); if (value != nullptr) { filename = *value; } } else if (property.first == "Log") { const std::string* value = std::get_if(&property.second); if (value != nullptr) { logfile = *value; } } } } constexpr char const* postCodeIface = "xyz.openbmc_project.State.Boot.PostCode"; inline void requestRoutesSystemLogServiceCollection(App& app) { /** * Functions triggers appropriate requests on DBus */ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/") .privileges(redfish::privileges::getLogServiceCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } // Collections don't include the static data added by SubRoute // because it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#LogServiceCollection.LogServiceCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices"; asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; asyncResp->res.jsonValue["Description"] = "Collection of LogServices for this Computer System"; nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; logServiceArray = nlohmann::json::array(); nlohmann::json::object_t eventLog; eventLog["@odata.id"] = "/redfish/v1/Systems/system/LogServices/EventLog"; logServiceArray.push_back(std::move(eventLog)); #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG nlohmann::json::object_t dumpLog; dumpLog["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Dump"; logServiceArray.push_back(std::move(dumpLog)); #endif #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG nlohmann::json::object_t crashdump; crashdump["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump"; logServiceArray.push_back(std::move(crashdump)); #endif #ifdef BMCWEB_ENABLE_REDFISH_HOST_LOGGER nlohmann::json::object_t hostlogger; hostlogger["@odata.id"] = "/redfish/v1/Systems/system/LogServices/HostLogger"; logServiceArray.push_back(std::move(hostlogger)); #endif asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size(); crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, const dbus::utility::MapperGetSubTreePathsResponse& subtreePath) { if (ec) { BMCWEB_LOG_ERROR << ec; return; } for (const auto& pathStr : subtreePath) { if (pathStr.find("PostCode") != std::string::npos) { nlohmann::json& logServiceArrayLocal = asyncResp->res.jsonValue["Members"]; logServiceArrayLocal.push_back( {{"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"}}); asyncResp->res.jsonValue["Members@odata.count"] = logServiceArrayLocal.size(); return; } } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 0, std::array{postCodeIface}); }); } inline void requestRoutesEventLogService(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/") .privileges(redfish::privileges::getLogService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { 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 redfishDateTimeOffset = crow::utility::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"}}; }); } inline void requestRoutesJournalEventLogClear(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog/") .privileges({{"ConfigureComponents"}}) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } // Clear the EventLog by deleting the log files std::vector 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 crow::connections::systemBus->async_method_call( [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"); }); } static int fillEventLogEntryJson(const std::string& logEntryID, const std::string& logEntry, nlohmann::json& logEntryJson) { // The redfish log format is " ," // First get the Timestamp size_t space = logEntry.find_first_of(' '); if (space == std::string::npos) { return 1; } 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 1; } std::string_view entry(logEntry); entry.remove_prefix(entryStart); // Use split to separate the entry into its fields std::vector logEntryFields; boost::split(logEntryFields, entry, boost::is_any_of(","), boost::token_compress_on); // We need at least a MessageId to be valid if (logEntryFields.empty()) { return 1; } 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 0; } std::string msg = message->message; // Get the MessageArgs from the log if there are any std::span 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_8_0.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + logEntryID}, {"Name", "System Event Log Entry"}, {"Id", logEntryID}, {"Message", std::move(msg)}, {"MessageId", std::move(messageID)}, {"MessageArgs", messageArgs}, {"EntryType", "Event"}, {"Severity", message->messageSeverity}, {"Created", std::move(timestamp)}}; return 0; } inline void requestRoutesJournalEventLogEntryCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") .privileges(redfish::privileges::getLogEntryCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { query_param::QueryCapabilities capabilities = { .canDelegateTop = true, .canDelegateSkip = true, }; query_param::Query delegatedQuery; if (!redfish::setUpRedfishRouteWithDelegation( app, req, asyncResp, delegatedQuery, capabilities)) { 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"; 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 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)) { entryCount++; // Handle paging using skip (number of entries to skip // from the start) and top (number of entries to // display) if (entryCount <= delegatedQuery.skip || entryCount > delegatedQuery.skip + delegatedQuery.top) { continue; } std::string idStr; if (!getUniqueEntryID(logEntry, idStr, firstEntry)) { continue; } if (firstEntry) { firstEntry = false; } logEntryArray.push_back({}); nlohmann::json& bmcLogEntry = logEntryArray.back(); if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) { messages::internalError(asyncResp->res); return; } } } asyncResp->res.jsonValue["Members@odata.count"] = entryCount; if (delegatedQuery.skip + delegatedQuery.top < entryCount) { asyncResp->res.jsonValue["Members@odata.nextLink"] = "/redfish/v1/Systems/system/LogServices/EventLog/Entries?$skip=" + std::to_string(delegatedQuery.skip + delegatedQuery.top); } }); } inline void requestRoutesJournalEventLogEntry(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { 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 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; } if (firstEntry) { firstEntry = false; } if (idStr == targetID) { if (fillEventLogEntryJson(idStr, logEntry, asyncResp->res.jsonValue) != 0) { messages::internalError(asyncResp->res); return; } return; } } } // Requested ID was not found messages::resourceMissingAtURI(asyncResp->res, crow::utility::urlFromPieces(targetID)); }); } inline void requestRoutesDBusEventLogEntryCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") .privileges(redfish::privileges::getLogEntryCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } // Collections don't include the static data added by SubRoute // because it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#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"; // DBus implementation of EventLog/Entries // Make call to Logging Service to find all log entry objects crow::connections::systemBus->async_method_call( [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; bool resolved = false; 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(&propertyMap.second); } else if (propertyMap.first == "Timestamp") { timestamp = std::get_if(&propertyMap.second); } else if (propertyMap.first == "UpdateTimestamp") { updateTimestamp = std::get_if(&propertyMap.second); } else if (propertyMap.first == "Severity") { severity = std::get_if( &propertyMap.second); } else if (propertyMap.first == "Message") { message = std::get_if( &propertyMap.second); } else if (propertyMap.first == "Resolved") { const bool* resolveptr = std::get_if(&propertyMap.second); if (resolveptr == nullptr) { messages::internalError(asyncResp->res); return; } resolved = *resolveptr; } } 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( &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_8_0.LogEntry"; thisEntry["@odata.id"] = "/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; thisEntry["EntryType"] = "Event"; thisEntry["Severity"] = translateSeverityDbusToRedfish(*severity); thisEntry["Created"] = crow::utility::getDateTimeUintMs(*timestamp); thisEntry["Modified"] = crow::utility::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(); }, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); }); } inline void requestRoutesDBusEventLogEntry(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { 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 crow::connections::systemBus->async_method_call( [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; bool resolved = false; for (const auto& propertyMap : resp) { if (propertyMap.first == "Id") { id = std::get_if(&propertyMap.second); } else if (propertyMap.first == "Timestamp") { timestamp = std::get_if(&propertyMap.second); } else if (propertyMap.first == "UpdateTimestamp") { updateTimestamp = std::get_if(&propertyMap.second); } else if (propertyMap.first == "Severity") { severity = std::get_if(&propertyMap.second); } else if (propertyMap.first == "Message") { message = std::get_if(&propertyMap.second); } else if (propertyMap.first == "Resolved") { const bool* resolveptr = std::get_if(&propertyMap.second); if (resolveptr == nullptr) { messages::internalError(asyncResp->res); return; } resolved = *resolveptr; } else if (propertyMap.first == "Path") { filePath = std::get_if(&propertyMap.second); } } if (id == nullptr || message == nullptr || severity == nullptr || timestamp == nullptr || updateTimestamp == nullptr) { messages::internalError(asyncResp->res); return; } asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_8_0.LogEntry"; asyncResp->res.jsonValue["@odata.id"] = "/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; asyncResp->res.jsonValue["EntryType"] = "Event"; asyncResp->res.jsonValue["Severity"] = translateSeverityDbusToRedfish(*severity); asyncResp->res.jsonValue["Created"] = crow::utility::getDateTimeUintMs(*timestamp); asyncResp->res.jsonValue["Modified"] = crow::utility::getDateTimeUintMs(*updateTimestamp); if (filePath != nullptr) { asyncResp->res.jsonValue["AdditionalDataURI"] = "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + std::to_string(*id) + "/attachment"; } }, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging/entry/" + entryID, "org.freedesktop.DBus.Properties", "GetAll", ""); }); BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries//") .privileges(redfish::privileges::patchLogEntry) .methods(boost::beast::http::verb::patch)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& entryId) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } std::optional resolved; if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved)) { return; } BMCWEB_LOG_DEBUG << "Set Resolved"; crow::connections::systemBus->async_method_call( [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)); }); BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries//") .privileges(redfish::privileges::deleteLogEntry) .methods(boost::beast::http::verb::delete_)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { 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 crow::connections::systemBus->async_method_call( respHandler, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging/entry/" + entryID, "xyz.openbmc_project.Object.Delete", "Delete"); }); } inline void requestRoutesDBusEventLogEntryDownload(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries//attachment") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (!http_helpers::isOctetAccepted(req.getHeaderValue("Accept"))) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } std::string entryID = param; dbus::utility::escapePathForDbus(entryID); crow::connections::systemBus->async_method_call( [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; } long long int 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 data(static_cast(size)); long long int 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("Content-Type", "application/octet-stream"); asyncResp->res.addHeader("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"); }); } constexpr const char* hostLoggerFolderPath = "/var/log/console"; inline bool getHostLoggerFiles(const std::string& hostLoggerFilePath, std::vector& hostLoggerFiles) { std::error_code ec; std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec); if (ec) { BMCWEB_LOG_ERROR << ec.message(); return false; } for (const std::filesystem::directory_entry& it : logPath) { std::string filename = it.path().filename(); // Prefix of each log files is "log". Find the file and save the // path if (boost::starts_with(filename, "log")) { hostLoggerFiles.emplace_back(it.path()); } } // As the log files rotate, they are appended with a ".#" that is higher for // the older logs. Since we start from oldest logs, sort the name in // descending order. std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(), AlphanumLess()); return true; } inline bool getHostLoggerEntries(std::vector& hostLoggerFiles, uint64_t skip, uint64_t top, std::vector& logEntries, size_t& logCount) { GzFileReader logFile; // Go though all log files and expose host logs. for (const std::filesystem::path& it : hostLoggerFiles) { if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount)) { BMCWEB_LOG_ERROR << "fail to expose host logs"; return false; } } // Get lastMessage from constructor by getter std::string lastMessage = logFile.getLastMessage(); if (!lastMessage.empty()) { logCount++; if (logCount > skip && logCount <= (skip + top)) { logEntries.push_back(lastMessage); } } return true; } inline void fillHostLoggerEntryJson(const std::string& logEntryID, const std::string& msg, nlohmann::json& logEntryJson) { // Fill in the log entry with the gathered data. logEntryJson = { {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/" + logEntryID}, {"Name", "Host Logger Entry"}, {"Id", logEntryID}, {"Message", msg}, {"EntryType", "Oem"}, {"Severity", "OK"}, {"OemRecordFormat", "Host Logger Entry"}}; } inline void requestRoutesSystemHostLogger(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/HostLogger/") .privileges(redfish::privileges::getLogService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/HostLogger"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; asyncResp->res.jsonValue["Name"] = "Host Logger Service"; asyncResp->res.jsonValue["Description"] = "Host Logger Service"; asyncResp->res.jsonValue["Id"] = "HostLogger"; asyncResp->res.jsonValue["Entries"]["@odata.id"] = "/redfish/v1/Systems/system/LogServices/HostLogger/Entries"; }); } inline void requestRoutesSystemHostLoggerCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { query_param::QueryCapabilities capabilities = { .canDelegateTop = true, .canDelegateSkip = true, }; query_param::Query delegatedQuery; if (!redfish::setUpRedfishRouteWithDelegation( app, req, asyncResp, delegatedQuery, capabilities)) { return; } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/HostLogger/Entries"; asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["Name"] = "HostLogger Entries"; asyncResp->res.jsonValue["Description"] = "Collection of HostLogger Entries"; nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; logEntryArray = nlohmann::json::array(); asyncResp->res.jsonValue["Members@odata.count"] = 0; std::vector hostLoggerFiles; if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) { BMCWEB_LOG_ERROR << "fail to get host log file path"; return; } size_t logCount = 0; // This vector only store the entries we want to expose that // control by skip and top. std::vector logEntries; if (!getHostLoggerEntries(hostLoggerFiles, delegatedQuery.skip, delegatedQuery.top, logEntries, logCount)) { messages::internalError(asyncResp->res); return; } // If vector is empty, that means skip value larger than total // log count if (logEntries.empty()) { asyncResp->res.jsonValue["Members@odata.count"] = logCount; return; } if (!logEntries.empty()) { for (size_t i = 0; i < logEntries.size(); i++) { logEntryArray.push_back({}); nlohmann::json& hostLogEntry = logEntryArray.back(); fillHostLoggerEntryJson(std::to_string(delegatedQuery.skip + i), logEntries[i], hostLogEntry); } asyncResp->res.jsonValue["Members@odata.count"] = logCount; if (delegatedQuery.skip + delegatedQuery.top < logCount) { asyncResp->res.jsonValue["Members@odata.nextLink"] = "/redfish/v1/Systems/system/LogServices/HostLogger/Entries?$skip=" + std::to_string(delegatedQuery.skip + delegatedQuery.top); } } }); } inline void requestRoutesSystemHostLoggerLogEntry(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/HostLogger/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } const std::string& targetID = param; uint64_t idInt = 0; // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) const char* end = targetID.data() + targetID.size(); auto [ptr, ec] = std::from_chars(targetID.data(), end, idInt); if (ec == std::errc::invalid_argument) { messages::resourceMissingAtURI(asyncResp->res, req.urlView); return; } if (ec == std::errc::result_out_of_range) { messages::resourceMissingAtURI(asyncResp->res, req.urlView); return; } std::vector hostLoggerFiles; if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) { BMCWEB_LOG_ERROR << "fail to get host log file path"; return; } size_t logCount = 0; uint64_t top = 1; std::vector logEntries; // We can get specific entry by skip and top. For example, if we // want to get nth entry, we can set skip = n-1 and top = 1 to // get that entry if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries, logCount)) { messages::internalError(asyncResp->res); return; } if (!logEntries.empty()) { fillHostLoggerEntryJson(targetID, logEntries[0], asyncResp->res.jsonValue); return; } // Requested ID was not found messages::resourceMissingAtURI(asyncResp->res, req.urlView); }); } inline void requestRoutesBMCLogServiceCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/") .privileges(redfish::privileges::getLogServiceCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } // Collections don't include the static data added by SubRoute // because it has a duplicate entry for members asyncResp->res.jsonValue["@odata.type"] = "#LogServiceCollection.LogServiceCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices"; asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; asyncResp->res.jsonValue["Description"] = "Collection of LogServices for this Manager"; nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; logServiceArray = nlohmann::json::array(); #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG logServiceArray.push_back( {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump"}}); #endif #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL logServiceArray.push_back( {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); #endif asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size(); }); } inline void requestRoutesBMCJournalLogService(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") .privileges(redfish::privileges::getLogService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal"; asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; asyncResp->res.jsonValue["Id"] = "BMC Journal"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; std::pair redfishDateTimeOffset = crow::utility::getDateTimeOffsetNow(); asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; asyncResp->res.jsonValue["DateTimeLocalOffset"] = redfishDateTimeOffset.second; asyncResp->res.jsonValue["Entries"]["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; }); } static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, sd_journal* journal, nlohmann::json& bmcJournalLogEntryJson) { // Get the Log Entry contents int ret = 0; std::string message; std::string_view syslogID; ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: " << strerror(-ret); } if (!syslogID.empty()) { message += std::string(syslogID) + ": "; } std::string_view msg; ret = getJournalMetadata(journal, "MESSAGE", msg); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); return 1; } message += std::string(msg); // Get the severity from the PRIORITY field long int severity = 8; // Default to an invalid priority ret = getJournalMetadata(journal, "PRIORITY", 10, severity); if (ret < 0) { BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); } // Get the Created time from the timestamp std::string entryTimeStr; if (!getEntryTimestamp(journal, entryTimeStr)) { return 1; } // Fill in the log entry with the gathered data bmcJournalLogEntryJson = { {"@odata.type", "#LogEntry.v1_8_0.LogEntry"}, {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + bmcJournalLogEntryID}, {"Name", "BMC Journal Entry"}, {"Id", bmcJournalLogEntryID}, {"Message", std::move(message)}, {"EntryType", "Oem"}, {"Severity", severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, {"OemRecordFormat", "BMC Journal Entry"}, {"Created", std::move(entryTimeStr)}}; return 0; } inline void requestRoutesBMCJournalLogEntryCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") .privileges(redfish::privileges::getLogEntryCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { query_param::QueryCapabilities capabilities = { .canDelegateTop = true, .canDelegateSkip = true, }; query_param::Query delegatedQuery; if (!redfish::setUpRedfishRouteWithDelegation( app, req, asyncResp, delegatedQuery, capabilities)) { 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/Managers/bmc/LogServices/Journal/Entries"; asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; asyncResp->res.jsonValue["Description"] = "Collection of BMC Journal Entries"; nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; logEntryArray = nlohmann::json::array(); // Go through the journal and use the timestamp to create a // unique ID for each entry sd_journal* journalTmp = nullptr; int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); messages::internalError(asyncResp->res); return; } std::unique_ptr journal( journalTmp, sd_journal_close); journalTmp = nullptr; uint64_t entryCount = 0; // Reset the unique ID on the first entry bool firstEntry = true; SD_JOURNAL_FOREACH(journal.get()) { entryCount++; // Handle paging using skip (number of entries to skip from // the start) and top (number of entries to display) if (entryCount <= delegatedQuery.skip || entryCount > delegatedQuery.skip + delegatedQuery.top) { continue; } std::string idStr; if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) { continue; } if (firstEntry) { firstEntry = false; } logEntryArray.push_back({}); nlohmann::json& bmcJournalLogEntry = logEntryArray.back(); if (fillBMCJournalLogEntryJson(idStr, journal.get(), bmcJournalLogEntry) != 0) { messages::internalError(asyncResp->res); return; } } asyncResp->res.jsonValue["Members@odata.count"] = entryCount; if (delegatedQuery.skip + delegatedQuery.top < entryCount) { asyncResp->res.jsonValue["Members@odata.nextLink"] = "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + std::to_string(delegatedQuery.skip + delegatedQuery.top); } }); } inline void requestRoutesBMCJournalLogEntry(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& entryID) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } // Convert the unique ID back to a timestamp to find the entry uint64_t ts = 0; uint64_t index = 0; if (!getTimestampFromID(asyncResp, entryID, ts, index)) { return; } sd_journal* journalTmp = nullptr; int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); messages::internalError(asyncResp->res); return; } std::unique_ptr journal( journalTmp, sd_journal_close); journalTmp = nullptr; // Go to the timestamp in the log and move to the entry at the // index tracking the unique ID std::string idStr; bool firstEntry = true; ret = sd_journal_seek_realtime_usec(journal.get(), ts); if (ret < 0) { BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" << strerror(-ret); messages::internalError(asyncResp->res); return; } for (uint64_t i = 0; i <= index; i++) { sd_journal_next(journal.get()); if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) { messages::internalError(asyncResp->res); return; } if (firstEntry) { firstEntry = false; } } // Confirm that the entry ID matches what was requested if (idStr != entryID) { messages::resourceMissingAtURI(asyncResp->res, req.urlView); return; } if (fillBMCJournalLogEntryJson(entryID, journal.get(), asyncResp->res.jsonValue) != 0) { messages::internalError(asyncResp->res); return; } }); } inline void requestRoutesBMCDumpService(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") .privileges(redfish::privileges::getLogService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Dump"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; asyncResp->res.jsonValue["Name"] = "Dump LogService"; asyncResp->res.jsonValue["Description"] = "BMC Dump LogService"; asyncResp->res.jsonValue["Id"] = "Dump"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; std::pair redfishDateTimeOffset = crow::utility::getDateTimeOffsetNow(); asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; asyncResp->res.jsonValue["DateTimeLocalOffset"] = redfishDateTimeOffset.second; asyncResp->res.jsonValue["Entries"]["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] = "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog"; asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] ["target"] = "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData"; }); } inline void requestRoutesBMCDumpEntryCollection(App& app) { /** * Functions triggers appropriate requests on DBus */ BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") .privileges(redfish::privileges::getLogEntryCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; asyncResp->res.jsonValue["Name"] = "BMC Dump Entries"; asyncResp->res.jsonValue["Description"] = "Collection of BMC Dump Entries"; getDumpEntryCollection(asyncResp, "BMC"); }); } inline void requestRoutesBMCDumpEntry(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } getDumpEntryById(asyncResp, param, "BMC"); }); BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries//") .privileges(redfish::privileges::deleteLogEntry) .methods(boost::beast::http::verb::delete_)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } deleteDumpEntry(asyncResp, param, "bmc"); }); } inline void requestRoutesBMCDumpCreate(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") .privileges(redfish::privileges::postLogService) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } createDump(asyncResp, req, "BMC"); }); } inline void requestRoutesBMCDumpClear(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/") .privileges(redfish::privileges::postLogService) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } clearDump(asyncResp, "BMC"); }); } inline void requestRoutesSystemDumpService(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/") .privileges(redfish::privileges::getLogService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Dump"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; asyncResp->res.jsonValue["Name"] = "Dump LogService"; asyncResp->res.jsonValue["Description"] = "System Dump LogService"; asyncResp->res.jsonValue["Id"] = "Dump"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; std::pair redfishDateTimeOffset = crow::utility::getDateTimeOffsetNow(); asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; asyncResp->res.jsonValue["DateTimeLocalOffset"] = redfishDateTimeOffset.second; asyncResp->res.jsonValue["Entries"]["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Dump/Entries"; asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] = "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog"; asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] ["target"] = "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData"; }); } inline void requestRoutesSystemDumpEntryCollection(App& app) { /** * Functions triggers appropriate requests on DBus */ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/") .privileges(redfish::privileges::getLogEntryCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Dump/Entries"; asyncResp->res.jsonValue["Name"] = "System Dump Entries"; asyncResp->res.jsonValue["Description"] = "Collection of System Dump Entries"; getDumpEntryCollection(asyncResp, "System"); }); } inline void requestRoutesSystemDumpEntry(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } getDumpEntryById(asyncResp, param, "System"); }); BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries//") .privileges(redfish::privileges::deleteLogEntry) .methods(boost::beast::http::verb::delete_)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } deleteDumpEntry(asyncResp, param, "system"); }); } inline void requestRoutesSystemDumpCreate(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") .privileges(redfish::privileges::postLogService) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } createDump(asyncResp, req, "System"); }); } inline void requestRoutesSystemDumpClear(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog/") .privileges(redfish::privileges::postLogService) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } clearDump(asyncResp, "System"); }); } inline void requestRoutesCrashdumpService(App& app) { // Note: Deviated from redfish privilege registry for GET & HEAD // method for security reasons. /** * Functions triggers appropriate requests on DBus */ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") // This is incorrect, should be: //.privileges(redfish::privileges::getLogService) .privileges({{"ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } // Copy over the static data to include the entries added by // SubRoute asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; std::pair redfishDateTimeOffset = crow::utility::getDateTimeOffsetNow(); asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; asyncResp->res.jsonValue["DateTimeLocalOffset"] = redfishDateTimeOffset.second; asyncResp->res.jsonValue["Entries"]["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] = "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog"; asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] ["target"] = "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData"; }); } void inline requestRoutesCrashdumpClear(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog/") // This is incorrect, should be: //.privileges(redfish::privileges::postLogService) .privileges({{"ConfigureComponents"}}) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, const std::string&) { if (ec) { messages::internalError(asyncResp->res); return; } messages::success(asyncResp->res); }, crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); }); } static void logCrashdumpEntry(const std::shared_ptr& asyncResp, const std::string& logID, nlohmann::json& logEntryJson) { auto getStoredLogCallback = [asyncResp, logID, &logEntryJson](const boost::system::error_code ec, const dbus::utility::DBusPropertiesMap& params) { if (ec) { BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); if (ec.value() == boost::system::linux_error::bad_request_descriptor) { messages::resourceNotFound(asyncResp->res, "LogEntry", logID); } else { messages::internalError(asyncResp->res); } return; } std::string timestamp{}; std::string filename{}; std::string logfile{}; parseCrashdumpParameters(params, filename, timestamp, logfile); if (filename.empty() || timestamp.empty()) { messages::resourceMissingAtURI(asyncResp->res, crow::utility::urlFromPieces(logID)); return; } std::string crashdumpURI = "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + logID + "/" + filename; nlohmann::json logEntry = { {"@odata.type", "#LogEntry.v1_7_0.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + logID}, {"Name", "CPU Crashdump"}, {"Id", logID}, {"EntryType", "Oem"}, {"AdditionalDataURI", std::move(crashdumpURI)}, {"DiagnosticDataType", "OEM"}, {"OEMDiagnosticDataType", "PECICrashdump"}, {"Created", std::move(timestamp)}}; // If logEntryJson references an array of LogEntry resources // ('Members' list), then push this as a new entry, otherwise set it // directly if (logEntryJson.is_array()) { logEntryJson.push_back(logEntry); asyncResp->res.jsonValue["Members@odata.count"] = logEntryJson.size(); } else { logEntryJson = logEntry; } }; crow::connections::systemBus->async_method_call( std::move(getStoredLogCallback), crashdumpObject, crashdumpPath + std::string("/") + logID, "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); } inline void requestRoutesCrashdumpEntryCollection(App& app) { // Note: Deviated from redfish privilege registry for GET & HEAD // method for security reasons. /** * Functions triggers appropriate requests on DBus */ BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") // This is incorrect, should be. //.privileges(redfish::privileges::postLogEntryCollection) .privileges({{"ConfigureComponents"}}) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, const std::vector& resp) { if (ec) { if (ec.value() != boost::system::errc::no_such_file_or_directory) { BMCWEB_LOG_DEBUG << "failed to get entries ec: " << ec.message(); messages::internalError(asyncResp->res); return; } } asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; asyncResp->res.jsonValue["Description"] = "Collection of Crashdump Entries"; asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); asyncResp->res.jsonValue["Members@odata.count"] = 0; for (const std::string& path : resp) { const sdbusplus::message::object_path objPath(path); // Get the log ID std::string logID = objPath.filename(); if (logID.empty()) { continue; } // Add the log entry to the array logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue["Members"]); } }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, std::array{crashdumpInterface}); }); } inline void requestRoutesCrashdumpEntry(App& app) { // Note: Deviated from redfish privilege registry for GET & HEAD // method for security reasons. BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries//") // this is incorrect, should be // .privileges(redfish::privileges::getLogEntry) .privileges({{"ConfigureComponents"}}) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& param) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } const std::string& logID = param; logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); }); } inline void requestRoutesCrashdumpFile(App& app) { // Note: Deviated from redfish privilege registry for GET & HEAD // method for security reasons. BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries///") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& logID, const std::string& fileName) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } auto getStoredLogCallback = [asyncResp, logID, fileName, url(boost::urls::url(req.urlView))]( const boost::system::error_code ec, const std::vector< std::pair>& resp) { if (ec) { BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); messages::internalError(asyncResp->res); return; } std::string dbusFilename{}; std::string dbusTimestamp{}; std::string dbusFilepath{}; parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, dbusFilepath); if (dbusFilename.empty() || dbusTimestamp.empty() || dbusFilepath.empty()) { messages::resourceMissingAtURI(asyncResp->res, url); return; } // Verify the file name parameter is correct if (fileName != dbusFilename) { messages::resourceMissingAtURI(asyncResp->res, url); return; } if (!std::filesystem::exists(dbusFilepath)) { messages::resourceMissingAtURI(asyncResp->res, url); return; } std::ifstream ifs(dbusFilepath, std::ios::in | std::ios::binary); asyncResp->res.body() = std::string(std::istreambuf_iterator{ifs}, {}); // Configure this to be a file download when accessed // from a browser asyncResp->res.addHeader("Content-Disposition", "attachment"); }; crow::connections::systemBus->async_method_call( std::move(getStoredLogCallback), crashdumpObject, crashdumpPath + std::string("/") + logID, "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); }); } enum class OEMDiagnosticType { onDemand, telemetry, invalid, }; inline OEMDiagnosticType getOEMDiagnosticType(const std::string_view& oemDiagStr) { if (oemDiagStr == "OnDemand") { return OEMDiagnosticType::onDemand; } if (oemDiagStr == "Telemetry") { return OEMDiagnosticType::telemetry; } return OEMDiagnosticType::invalid; } inline void requestRoutesCrashdumpCollect(App& app) { // Note: Deviated from redfish privilege registry for GET & HEAD // method for security reasons. BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/") // The below is incorrect; Should be ConfigureManager //.privileges(redfish::privileges::postLogService) .privileges({{"ConfigureComponents"}}) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } std::string diagnosticDataType; std::string oemDiagnosticDataType; if (!redfish::json_util::readJsonAction( req, asyncResp->res, "DiagnosticDataType", diagnosticDataType, "OEMDiagnosticDataType", oemDiagnosticDataType)) { return; } if (diagnosticDataType != "OEM") { BMCWEB_LOG_ERROR << "Only OEM DiagnosticDataType supported for Crashdump"; messages::actionParameterValueFormatError( asyncResp->res, diagnosticDataType, "DiagnosticDataType", "CollectDiagnosticData"); return; } OEMDiagnosticType oemDiagType = getOEMDiagnosticType(oemDiagnosticDataType); std::string iface; std::string method; std::string taskMatchStr; if (oemDiagType == OEMDiagnosticType::onDemand) { iface = crashdumpOnDemandInterface; method = "GenerateOnDemandLog"; taskMatchStr = "type='signal'," "interface='org.freedesktop.DBus.Properties'," "member='PropertiesChanged'," "arg0namespace='com.intel.crashdump'"; } else if (oemDiagType == OEMDiagnosticType::telemetry) { iface = crashdumpTelemetryInterface; method = "GenerateTelemetryLog"; taskMatchStr = "type='signal'," "interface='org.freedesktop.DBus.Properties'," "member='PropertiesChanged'," "arg0namespace='com.intel.crashdump'"; } else { BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: " << oemDiagnosticDataType; messages::actionParameterValueFormatError( asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType", "CollectDiagnosticData"); return; } auto collectCrashdumpCallback = [asyncResp, payload(task::Payload(req)), taskMatchStr](const boost::system::error_code ec, const std::string&) mutable { if (ec) { if (ec.value() == boost::system::errc::operation_not_supported) { messages::resourceInStandby(asyncResp->res); } else if (ec.value() == boost::system::errc::device_or_resource_busy) { messages::serviceTemporarilyUnavailable(asyncResp->res, "60"); } else { messages::internalError(asyncResp->res); } return; } std::shared_ptr task = task::TaskData::createTask( [](boost::system::error_code err, sdbusplus::message::message&, const std::shared_ptr& taskData) { if (!err) { taskData->messages.emplace_back(messages::taskCompletedOK( std::to_string(taskData->index))); taskData->state = "Completed"; } return task::completed; }, taskMatchStr); task->startTimer(std::chrono::minutes(5)); task->populateResp(asyncResp->res); task->payload.emplace(std::move(payload)); }; crow::connections::systemBus->async_method_call( std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath, iface, method); }); } /** * DBusLogServiceActionsClear class supports POST method for ClearLog action. */ inline void requestRoutesDBusLogServiceActionsClear(App& app) { /** * Function handles POST method request. * The Clear Log actions does not require any parameter.The action deletes * all entries found in the Entries collection for this Log Service. */ BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog/") .privileges(redfish::privileges::postLogService) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } BMCWEB_LOG_DEBUG << "Do delete all entries."; // Process response from Logging service. auto respHandler = [asyncResp](const boost::system::error_code ec) { BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; if (ec) { // TODO Handle for specific error code BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; asyncResp->res.result( boost::beast::http::status::internal_server_error); return; } asyncResp->res.result(boost::beast::http::status::no_content); }; // Make call to Logging service to request Clear Log crow::connections::systemBus->async_method_call( respHandler, "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); }); } /**************************************************** * Redfish PostCode interfaces * using DBUS interface: getPostCodesTS ******************************************************/ inline void requestRoutesPostCodesLogService(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") .privileges(redfish::privileges::getLogService) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/PostCodes"; asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; asyncResp->res.jsonValue["Id"] = "BIOS POST Code Log"; asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; asyncResp->res.jsonValue["Entries"]["@odata.id"] = "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; std::pair redfishDateTimeOffset = crow::utility::getDateTimeOffsetNow(); asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; asyncResp->res.jsonValue["DateTimeLocalOffset"] = redfishDateTimeOffset.second; asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}}; }); } inline void requestRoutesPostCodesClear(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog/") // The following privilege is incorrect; It should be ConfigureManager //.privileges(redfish::privileges::postLogService) .privileges({{"ConfigureComponents"}}) .methods(boost::beast::http::verb::post)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; // Make call to post-code service to request clear all crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec) { if (ec) { // TODO Handle for specific error code BMCWEB_LOG_ERROR << "doClearPostCodes resp_handler got error " << ec; asyncResp->res.result( boost::beast::http::status::internal_server_error); messages::internalError(asyncResp->res); return; } }, "xyz.openbmc_project.State.Boot.PostCode0", "/xyz/openbmc_project/State/Boot/PostCode0", "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); }); } static void fillPostCodeEntry( const std::shared_ptr& aResp, const boost::container::flat_map< uint64_t, std::tuple>>& postcode, const uint16_t bootIndex, const uint64_t codeIndex = 0, const uint64_t skip = 0, const uint64_t top = 0) { // Get the Message from the MessageRegistry const registries::Message* message = registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); uint64_t currentCodeIndex = 0; nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"]; uint64_t firstCodeTimeUs = 0; for (const std::pair>>& code : postcode) { currentCodeIndex++; std::string postcodeEntryID = "B" + std::to_string(bootIndex) + "-" + std::to_string(currentCodeIndex); // 1 based index in EntryID string uint64_t usecSinceEpoch = code.first; uint64_t usTimeOffset = 0; if (1 == currentCodeIndex) { // already incremented firstCodeTimeUs = code.first; } else { usTimeOffset = code.first - firstCodeTimeUs; } // skip if no specific codeIndex is specified and currentCodeIndex does // not fall between top and skip if ((codeIndex == 0) && (currentCodeIndex <= skip || currentCodeIndex > top)) { continue; } // skip if a specific codeIndex is specified and does not match the // currentIndex if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) { // This is done for simplicity. 1st entry is needed to calculate // time offset. To improve efficiency, one can get to the entry // directly (possibly with flatmap's nth method) continue; } // currentCodeIndex is within top and skip or equal to specified code // index // Get the Created time from the timestamp std::string entryTimeStr; entryTimeStr = crow::utility::getDateTimeUint(usecSinceEpoch / 1000 / 1000); // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) std::ostringstream hexCode; hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex << std::get<0>(code.second); std::ostringstream timeOffsetStr; // Set Fixed -Point Notation timeOffsetStr << std::fixed; // Set precision to 4 digits timeOffsetStr << std::setprecision(4); // Add double to stream timeOffsetStr << static_cast(usTimeOffset) / 1000 / 1000; std::vector messageArgs = { std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; // Get MessageArgs template from message registry std::string msg; if (message != nullptr) { msg = message->message; // fill in this post code value int i = 0; for (const std::string& messageArg : messageArgs) { std::string argStr = "%" + std::to_string(++i); size_t argPos = msg.find(argStr); if (argPos != std::string::npos) { msg.replace(argPos, argStr.length(), messageArg); } } } // Get Severity template from message registry std::string severity; if (message != nullptr) { severity = message->messageSeverity; } // add to AsyncResp logEntryArray.push_back({}); nlohmann::json& bmcLogEntry = logEntryArray.back(); bmcLogEntry = { {"@odata.type", "#LogEntry.v1_8_0.LogEntry"}, {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + postcodeEntryID}, {"Name", "POST Code Log Entry"}, {"Id", postcodeEntryID}, {"Message", std::move(msg)}, {"MessageId", "OpenBMC.0.2.BIOSPOSTCode"}, {"MessageArgs", std::move(messageArgs)}, {"EntryType", "Event"}, {"Severity", std::move(severity)}, {"Created", entryTimeStr}}; if (!std::get>(code.second).empty()) { bmcLogEntry["AdditionalDataURI"] = "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + postcodeEntryID + "/attachment"; } } } static void getPostCodeForEntry(const std::shared_ptr& aResp, const uint16_t bootIndex, const uint64_t codeIndex) { crow::connections::systemBus->async_method_call( [aResp, bootIndex, codeIndex](const boost::system::error_code ec, const boost::container::flat_map< uint64_t, std::tuple>>& postcode) { if (ec) { BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; messages::internalError(aResp->res); return; } // skip the empty postcode boots if (postcode.empty()) { return; } fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); aResp->res.jsonValue["Members@odata.count"] = aResp->res.jsonValue["Members"].size(); }, "xyz.openbmc_project.State.Boot.PostCode0", "/xyz/openbmc_project/State/Boot/PostCode0", "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", bootIndex); } static void getPostCodeForBoot(const std::shared_ptr& aResp, const uint16_t bootIndex, const uint16_t bootCount, const uint64_t entryCount, const uint64_t skip, const uint64_t top) { crow::connections::systemBus->async_method_call( [aResp, bootIndex, bootCount, entryCount, skip, top](const boost::system::error_code ec, const boost::container::flat_map< uint64_t, std::tuple>>& postcode) { if (ec) { BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; messages::internalError(aResp->res); return; } uint64_t endCount = entryCount; if (!postcode.empty()) { endCount = entryCount + postcode.size(); if ((skip < endCount) && ((top + skip) > entryCount)) { uint64_t thisBootSkip = std::max(skip, entryCount) - entryCount; uint64_t thisBootTop = std::min(top + skip, endCount) - entryCount; fillPostCodeEntry(aResp, postcode, bootIndex, 0, thisBootSkip, thisBootTop); } aResp->res.jsonValue["Members@odata.count"] = endCount; } // continue to previous bootIndex if (bootIndex < bootCount) { getPostCodeForBoot(aResp, static_cast(bootIndex + 1), bootCount, endCount, skip, top); } else { aResp->res.jsonValue["Members@odata.nextLink"] = "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" + std::to_string(skip + top); } }, "xyz.openbmc_project.State.Boot.PostCode0", "/xyz/openbmc_project/State/Boot/PostCode0", "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", bootIndex); } static void getCurrentBootNumber(const std::shared_ptr& aResp, const uint64_t skip, const uint64_t top) { uint64_t entryCount = 0; sdbusplus::asio::getProperty( *crow::connections::systemBus, "xyz.openbmc_project.State.Boot.PostCode0", "/xyz/openbmc_project/State/Boot/PostCode0", "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", [aResp, entryCount, skip, top](const boost::system::error_code ec, const uint16_t bootCount) { if (ec) { BMCWEB_LOG_DEBUG << "DBUS response error " << ec; messages::internalError(aResp->res); return; } getPostCodeForBoot(aResp, 1, bootCount, entryCount, skip, top); }); } inline void requestRoutesPostCodesEntryCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") .privileges(redfish::privileges::getLogEntryCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { query_param::QueryCapabilities capabilities = { .canDelegateTop = true, .canDelegateSkip = true, }; query_param::Query delegatedQuery; if (!redfish::setUpRedfishRouteWithDelegation( app, req, asyncResp, delegatedQuery, capabilities)) { return; } asyncResp->res.jsonValue["@odata.type"] = "#LogEntryCollection.LogEntryCollection"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; asyncResp->res.jsonValue["Description"] = "Collection of POST Code Log Entries"; asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); asyncResp->res.jsonValue["Members@odata.count"] = 0; getCurrentBootNumber(asyncResp, delegatedQuery.skip, delegatedQuery.top); }); } /** * @brief Parse post code ID and get the current value and index value * eg: postCodeID=B1-2, currentValue=1, index=2 * * @param[in] postCodeID Post Code ID * @param[out] currentValue Current value * @param[out] index Index value * * @return bool true if the parsing is successful, false the parsing fails */ inline static bool parsePostCode(const std::string& postCodeID, uint64_t& currentValue, uint16_t& index) { std::vector split; boost::algorithm::split(split, postCodeID, boost::is_any_of("-")); if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B') { return false; } // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) const char* start = split[0].data() + 1; // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) const char* end = split[0].data() + split[0].size(); auto [ptrIndex, ecIndex] = std::from_chars(start, end, index); if (ptrIndex != end || ecIndex != std::errc()) { return false; } start = split[1].data(); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) end = split[1].data() + split[1].size(); auto [ptrValue, ecValue] = std::from_chars(start, end, currentValue); return ptrValue == end && ecValue != std::errc(); } inline void requestRoutesPostCodesEntryAdditionalData(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries//attachment/") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& postCodeID) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } if (!http_helpers::isOctetAccepted(req.getHeaderValue("Accept"))) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } uint64_t currentValue = 0; uint16_t index = 0; if (!parsePostCode(postCodeID, currentValue, index)) { messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); return; } crow::connections::systemBus->async_method_call( [asyncResp, postCodeID, currentValue]( const boost::system::error_code ec, const std::vector>>& postcodes) { if (ec.value() == EBADR) { messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); return; } if (ec) { BMCWEB_LOG_DEBUG << "DBUS response error " << ec; messages::internalError(asyncResp->res); return; } size_t value = static_cast(currentValue) - 1; if (value == std::string::npos || postcodes.size() < currentValue) { BMCWEB_LOG_ERROR << "Wrong currentValue value"; messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); return; } const auto& [tID, c] = postcodes[value]; if (c.empty()) { BMCWEB_LOG_INFO << "No found post code data"; messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); return; } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) const char* d = reinterpret_cast(c.data()); std::string_view strData(d, c.size()); asyncResp->res.addHeader("Content-Type", "application/octet-stream"); asyncResp->res.addHeader("Content-Transfer-Encoding", "Base64"); asyncResp->res.body() = crow::utility::base64encode(strData); }, "xyz.openbmc_project.State.Boot.PostCode0", "/xyz/openbmc_project/State/Boot/PostCode0", "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index); }); } inline void requestRoutesPostCodesEntry(App& app) { BMCWEB_ROUTE( app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries//") .privileges(redfish::privileges::getLogEntry) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& targetID) { if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { return; } uint16_t bootIndex = 0; uint64_t codeIndex = 0; if (!parsePostCode(targetID, codeIndex, bootIndex)) { // Requested ID was not found messages::resourceMissingAtURI(asyncResp->res, req.urlView); return; } if (bootIndex == 0 || codeIndex == 0) { BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " << targetID; } asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; asyncResp->res.jsonValue["Description"] = "Collection of POST Code Log Entries"; asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); asyncResp->res.jsonValue["Members@odata.count"] = 0; getPostCodeForEntry(asyncResp, bootIndex, codeIndex); }); } } // namespace redfish