1*b0983db2SEd Tanous #pragma once 2*b0983db2SEd Tanous 3*b0983db2SEd Tanous #include "app.hpp" 4*b0983db2SEd Tanous #include "error_messages.hpp" 5*b0983db2SEd Tanous #include "generated/enums/log_entry.hpp" 6*b0983db2SEd Tanous #include "query.hpp" 7*b0983db2SEd Tanous #include "registries/base_message_registry.hpp" 8*b0983db2SEd Tanous #include "registries/privilege_registry.hpp" 9*b0983db2SEd Tanous #include "utils/time_utils.hpp" 10*b0983db2SEd Tanous 11*b0983db2SEd Tanous #include <systemd/sd-journal.h> 12*b0983db2SEd Tanous 13*b0983db2SEd Tanous #include <boost/beast/http/verb.hpp> 14*b0983db2SEd Tanous 15*b0983db2SEd Tanous #include <array> 16*b0983db2SEd Tanous #include <memory> 17*b0983db2SEd Tanous #include <string> 18*b0983db2SEd Tanous #include <string_view> 19*b0983db2SEd Tanous 20*b0983db2SEd Tanous namespace redfish 21*b0983db2SEd Tanous { 22*b0983db2SEd Tanous // Entry is formed like "BootID_timestamp" or "BootID_timestamp_index" 23*b0983db2SEd Tanous inline bool 24*b0983db2SEd Tanous getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 25*b0983db2SEd Tanous std::string_view entryIDStrView, sd_id128_t& bootID, 26*b0983db2SEd Tanous uint64_t& timestamp, uint64_t& index) 27*b0983db2SEd Tanous { 28*b0983db2SEd Tanous // Convert the unique ID back to a bootID + timestamp to find the entry 29*b0983db2SEd Tanous auto underscore1Pos = entryIDStrView.find('_'); 30*b0983db2SEd Tanous if (underscore1Pos == std::string_view::npos) 31*b0983db2SEd Tanous { 32*b0983db2SEd Tanous // EntryID has no bootID or timestamp 33*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 34*b0983db2SEd Tanous return false; 35*b0983db2SEd Tanous } 36*b0983db2SEd Tanous 37*b0983db2SEd Tanous // EntryID has bootID + timestamp 38*b0983db2SEd Tanous 39*b0983db2SEd Tanous // Convert entryIDViewString to BootID 40*b0983db2SEd Tanous // NOTE: bootID string which needs to be null-terminated for 41*b0983db2SEd Tanous // sd_id128_from_string() 42*b0983db2SEd Tanous std::string bootIDStr(entryIDStrView.substr(0, underscore1Pos)); 43*b0983db2SEd Tanous if (sd_id128_from_string(bootIDStr.c_str(), &bootID) < 0) 44*b0983db2SEd Tanous { 45*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 46*b0983db2SEd Tanous return false; 47*b0983db2SEd Tanous } 48*b0983db2SEd Tanous 49*b0983db2SEd Tanous // Get the timestamp from entryID 50*b0983db2SEd Tanous entryIDStrView.remove_prefix(underscore1Pos + 1); 51*b0983db2SEd Tanous 52*b0983db2SEd Tanous auto [timestampEnd, tstampEc] = std::from_chars( 53*b0983db2SEd Tanous entryIDStrView.begin(), entryIDStrView.end(), timestamp); 54*b0983db2SEd Tanous if (tstampEc != std::errc()) 55*b0983db2SEd Tanous { 56*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 57*b0983db2SEd Tanous return false; 58*b0983db2SEd Tanous } 59*b0983db2SEd Tanous entryIDStrView = std::string_view( 60*b0983db2SEd Tanous timestampEnd, 61*b0983db2SEd Tanous static_cast<size_t>(std::distance(timestampEnd, entryIDStrView.end()))); 62*b0983db2SEd Tanous if (entryIDStrView.empty()) 63*b0983db2SEd Tanous { 64*b0983db2SEd Tanous index = 0U; 65*b0983db2SEd Tanous return true; 66*b0983db2SEd Tanous } 67*b0983db2SEd Tanous // Timestamp might include optional index, if two events happened at the 68*b0983db2SEd Tanous // same "time". 69*b0983db2SEd Tanous if (entryIDStrView[0] != '_') 70*b0983db2SEd Tanous { 71*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 72*b0983db2SEd Tanous return false; 73*b0983db2SEd Tanous } 74*b0983db2SEd Tanous entryIDStrView.remove_prefix(1); 75*b0983db2SEd Tanous auto [ptr, indexEc] = std::from_chars(entryIDStrView.begin(), 76*b0983db2SEd Tanous entryIDStrView.end(), index); 77*b0983db2SEd Tanous if (indexEc != std::errc() || ptr != entryIDStrView.end()) 78*b0983db2SEd Tanous { 79*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 80*b0983db2SEd Tanous return false; 81*b0983db2SEd Tanous } 82*b0983db2SEd Tanous return true; 83*b0983db2SEd Tanous } 84*b0983db2SEd Tanous 85*b0983db2SEd Tanous inline bool getUniqueEntryID(sd_journal* journal, std::string& entryID, 86*b0983db2SEd Tanous const bool firstEntry = true) 87*b0983db2SEd Tanous { 88*b0983db2SEd Tanous int ret = 0; 89*b0983db2SEd Tanous static sd_id128_t prevBootID{}; 90*b0983db2SEd Tanous static uint64_t prevTs = 0; 91*b0983db2SEd Tanous static int index = 0; 92*b0983db2SEd Tanous if (firstEntry) 93*b0983db2SEd Tanous { 94*b0983db2SEd Tanous prevBootID = {}; 95*b0983db2SEd Tanous prevTs = 0; 96*b0983db2SEd Tanous } 97*b0983db2SEd Tanous 98*b0983db2SEd Tanous // Get the entry timestamp 99*b0983db2SEd Tanous uint64_t curTs = 0; 100*b0983db2SEd Tanous sd_id128_t curBootID{}; 101*b0983db2SEd Tanous ret = sd_journal_get_monotonic_usec(journal, &curTs, &curBootID); 102*b0983db2SEd Tanous if (ret < 0) 103*b0983db2SEd Tanous { 104*b0983db2SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); 105*b0983db2SEd Tanous return false; 106*b0983db2SEd Tanous } 107*b0983db2SEd Tanous // If the timestamp isn't unique on the same boot, increment the index 108*b0983db2SEd Tanous bool sameBootIDs = sd_id128_equal(curBootID, prevBootID) != 0; 109*b0983db2SEd Tanous if (sameBootIDs && (curTs == prevTs)) 110*b0983db2SEd Tanous { 111*b0983db2SEd Tanous index++; 112*b0983db2SEd Tanous } 113*b0983db2SEd Tanous else 114*b0983db2SEd Tanous { 115*b0983db2SEd Tanous // Otherwise, reset it 116*b0983db2SEd Tanous index = 0; 117*b0983db2SEd Tanous } 118*b0983db2SEd Tanous 119*b0983db2SEd Tanous if (!sameBootIDs) 120*b0983db2SEd Tanous { 121*b0983db2SEd Tanous // Save the bootID 122*b0983db2SEd Tanous prevBootID = curBootID; 123*b0983db2SEd Tanous } 124*b0983db2SEd Tanous // Save the timestamp 125*b0983db2SEd Tanous prevTs = curTs; 126*b0983db2SEd Tanous 127*b0983db2SEd Tanous // make entryID as <bootID>_<timestamp>[_<index>] 128*b0983db2SEd Tanous std::array<char, SD_ID128_STRING_MAX> bootIDStr{}; 129*b0983db2SEd Tanous sd_id128_to_string(curBootID, bootIDStr.data()); 130*b0983db2SEd Tanous entryID = std::format("{}_{}", bootIDStr.data(), curTs); 131*b0983db2SEd Tanous if (index > 0) 132*b0983db2SEd Tanous { 133*b0983db2SEd Tanous entryID += "_" + std::to_string(index); 134*b0983db2SEd Tanous } 135*b0983db2SEd Tanous return true; 136*b0983db2SEd Tanous } 137*b0983db2SEd Tanous 138*b0983db2SEd Tanous inline int getJournalMetadata(sd_journal* journal, std::string_view field, 139*b0983db2SEd Tanous std::string_view& contents) 140*b0983db2SEd Tanous { 141*b0983db2SEd Tanous const char* data = nullptr; 142*b0983db2SEd Tanous size_t length = 0; 143*b0983db2SEd Tanous int ret = 0; 144*b0983db2SEd Tanous // Get the metadata from the requested field of the journal entry 145*b0983db2SEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 146*b0983db2SEd Tanous const void** dataVoid = reinterpret_cast<const void**>(&data); 147*b0983db2SEd Tanous 148*b0983db2SEd Tanous ret = sd_journal_get_data(journal, field.data(), dataVoid, &length); 149*b0983db2SEd Tanous if (ret < 0) 150*b0983db2SEd Tanous { 151*b0983db2SEd Tanous return ret; 152*b0983db2SEd Tanous } 153*b0983db2SEd Tanous contents = std::string_view(data, length); 154*b0983db2SEd Tanous // Only use the content after the "=" character. 155*b0983db2SEd Tanous contents.remove_prefix(std::min(contents.find('=') + 1, contents.size())); 156*b0983db2SEd Tanous return ret; 157*b0983db2SEd Tanous } 158*b0983db2SEd Tanous 159*b0983db2SEd Tanous inline int getJournalMetadataInt(sd_journal* journal, std::string_view field, 160*b0983db2SEd Tanous const int& base, long int& contents) 161*b0983db2SEd Tanous { 162*b0983db2SEd Tanous int ret = 0; 163*b0983db2SEd Tanous std::string_view metadata; 164*b0983db2SEd Tanous // Get the metadata from the requested field of the journal entry 165*b0983db2SEd Tanous ret = getJournalMetadata(journal, field, metadata); 166*b0983db2SEd Tanous if (ret < 0) 167*b0983db2SEd Tanous { 168*b0983db2SEd Tanous return ret; 169*b0983db2SEd Tanous } 170*b0983db2SEd Tanous contents = strtol(metadata.data(), nullptr, base); 171*b0983db2SEd Tanous return ret; 172*b0983db2SEd Tanous } 173*b0983db2SEd Tanous 174*b0983db2SEd Tanous inline bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) 175*b0983db2SEd Tanous { 176*b0983db2SEd Tanous int ret = 0; 177*b0983db2SEd Tanous uint64_t timestamp = 0; 178*b0983db2SEd Tanous ret = sd_journal_get_realtime_usec(journal, ×tamp); 179*b0983db2SEd Tanous if (ret < 0) 180*b0983db2SEd Tanous { 181*b0983db2SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); 182*b0983db2SEd Tanous return false; 183*b0983db2SEd Tanous } 184*b0983db2SEd Tanous entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp); 185*b0983db2SEd Tanous return true; 186*b0983db2SEd Tanous } 187*b0983db2SEd Tanous 188*b0983db2SEd Tanous inline int 189*b0983db2SEd Tanous fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 190*b0983db2SEd Tanous sd_journal* journal, 191*b0983db2SEd Tanous nlohmann::json::object_t& bmcJournalLogEntryJson) 192*b0983db2SEd Tanous { 193*b0983db2SEd Tanous // Get the Log Entry contents 194*b0983db2SEd Tanous int ret = 0; 195*b0983db2SEd Tanous 196*b0983db2SEd Tanous std::string message; 197*b0983db2SEd Tanous std::string_view syslogID; 198*b0983db2SEd Tanous ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 199*b0983db2SEd Tanous if (ret < 0) 200*b0983db2SEd Tanous { 201*b0983db2SEd Tanous BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}", 202*b0983db2SEd Tanous strerror(-ret)); 203*b0983db2SEd Tanous } 204*b0983db2SEd Tanous if (!syslogID.empty()) 205*b0983db2SEd Tanous { 206*b0983db2SEd Tanous message += std::string(syslogID) + ": "; 207*b0983db2SEd Tanous } 208*b0983db2SEd Tanous 209*b0983db2SEd Tanous std::string_view msg; 210*b0983db2SEd Tanous ret = getJournalMetadata(journal, "MESSAGE", msg); 211*b0983db2SEd Tanous if (ret < 0) 212*b0983db2SEd Tanous { 213*b0983db2SEd Tanous BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret)); 214*b0983db2SEd Tanous return 1; 215*b0983db2SEd Tanous } 216*b0983db2SEd Tanous message += std::string(msg); 217*b0983db2SEd Tanous 218*b0983db2SEd Tanous // Get the severity from the PRIORITY field 219*b0983db2SEd Tanous long int severity = 8; // Default to an invalid priority 220*b0983db2SEd Tanous ret = getJournalMetadataInt(journal, "PRIORITY", 10, severity); 221*b0983db2SEd Tanous if (ret < 0) 222*b0983db2SEd Tanous { 223*b0983db2SEd Tanous BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret)); 224*b0983db2SEd Tanous } 225*b0983db2SEd Tanous 226*b0983db2SEd Tanous // Get the Created time from the timestamp 227*b0983db2SEd Tanous std::string entryTimeStr; 228*b0983db2SEd Tanous if (!getEntryTimestamp(journal, entryTimeStr)) 229*b0983db2SEd Tanous { 230*b0983db2SEd Tanous return 1; 231*b0983db2SEd Tanous } 232*b0983db2SEd Tanous 233*b0983db2SEd Tanous // Fill in the log entry with the gathered data 234*b0983db2SEd Tanous bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 235*b0983db2SEd Tanous bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( 236*b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}", 237*b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID); 238*b0983db2SEd Tanous bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; 239*b0983db2SEd Tanous bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; 240*b0983db2SEd Tanous bmcJournalLogEntryJson["Message"] = std::move(message); 241*b0983db2SEd Tanous bmcJournalLogEntryJson["EntryType"] = "Oem"; 242*b0983db2SEd Tanous log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; 243*b0983db2SEd Tanous if (severity <= 2) 244*b0983db2SEd Tanous { 245*b0983db2SEd Tanous severityEnum = log_entry::EventSeverity::Critical; 246*b0983db2SEd Tanous } 247*b0983db2SEd Tanous else if (severity <= 4) 248*b0983db2SEd Tanous { 249*b0983db2SEd Tanous severityEnum = log_entry::EventSeverity::Warning; 250*b0983db2SEd Tanous } 251*b0983db2SEd Tanous 252*b0983db2SEd Tanous bmcJournalLogEntryJson["Severity"] = severityEnum; 253*b0983db2SEd Tanous bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; 254*b0983db2SEd Tanous bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); 255*b0983db2SEd Tanous return 0; 256*b0983db2SEd Tanous } 257*b0983db2SEd Tanous 258*b0983db2SEd Tanous inline void requestRoutesBMCJournalLogService(App& app) 259*b0983db2SEd Tanous { 260*b0983db2SEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 261*b0983db2SEd Tanous .privileges(redfish::privileges::getLogService) 262*b0983db2SEd Tanous .methods(boost::beast::http::verb::get)( 263*b0983db2SEd Tanous [&app](const crow::Request& req, 264*b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 265*b0983db2SEd Tanous const std::string& managerId) { 266*b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 267*b0983db2SEd Tanous { 268*b0983db2SEd Tanous return; 269*b0983db2SEd Tanous } 270*b0983db2SEd Tanous 271*b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 272*b0983db2SEd Tanous { 273*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 274*b0983db2SEd Tanous return; 275*b0983db2SEd Tanous } 276*b0983db2SEd Tanous 277*b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 278*b0983db2SEd Tanous "#LogService.v1_2_0.LogService"; 279*b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = 280*b0983db2SEd Tanous boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 281*b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 282*b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 283*b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 284*b0983db2SEd Tanous asyncResp->res.jsonValue["Id"] = "Journal"; 285*b0983db2SEd Tanous asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 286*b0983db2SEd Tanous 287*b0983db2SEd Tanous std::pair<std::string, std::string> redfishDateTimeOffset = 288*b0983db2SEd Tanous redfish::time_utils::getDateTimeOffsetNow(); 289*b0983db2SEd Tanous asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 290*b0983db2SEd Tanous asyncResp->res.jsonValue["DateTimeLocalOffset"] = 291*b0983db2SEd Tanous redfishDateTimeOffset.second; 292*b0983db2SEd Tanous 293*b0983db2SEd Tanous asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 294*b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 295*b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 296*b0983db2SEd Tanous }); 297*b0983db2SEd Tanous } 298*b0983db2SEd Tanous 299*b0983db2SEd Tanous inline void requestRoutesBMCJournalLogEntryCollection(App& app) 300*b0983db2SEd Tanous { 301*b0983db2SEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 302*b0983db2SEd Tanous .privileges(redfish::privileges::getLogEntryCollection) 303*b0983db2SEd Tanous .methods(boost::beast::http::verb::get)( 304*b0983db2SEd Tanous [&app](const crow::Request& req, 305*b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 306*b0983db2SEd Tanous const std::string& managerId) { 307*b0983db2SEd Tanous query_param::QueryCapabilities capabilities = { 308*b0983db2SEd Tanous .canDelegateTop = true, 309*b0983db2SEd Tanous .canDelegateSkip = true, 310*b0983db2SEd Tanous }; 311*b0983db2SEd Tanous query_param::Query delegatedQuery; 312*b0983db2SEd Tanous if (!redfish::setUpRedfishRouteWithDelegation( 313*b0983db2SEd Tanous app, req, asyncResp, delegatedQuery, capabilities)) 314*b0983db2SEd Tanous { 315*b0983db2SEd Tanous return; 316*b0983db2SEd Tanous } 317*b0983db2SEd Tanous 318*b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 319*b0983db2SEd Tanous { 320*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 321*b0983db2SEd Tanous return; 322*b0983db2SEd Tanous } 323*b0983db2SEd Tanous 324*b0983db2SEd Tanous size_t skip = delegatedQuery.skip.value_or(0); 325*b0983db2SEd Tanous size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 326*b0983db2SEd Tanous 327*b0983db2SEd Tanous // Collections don't include the static data added by SubRoute 328*b0983db2SEd Tanous // because it has a duplicate entry for members 329*b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 330*b0983db2SEd Tanous "#LogEntryCollection.LogEntryCollection"; 331*b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 332*b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 333*b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 334*b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 335*b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = 336*b0983db2SEd Tanous "Collection of BMC Journal Entries"; 337*b0983db2SEd Tanous nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 338*b0983db2SEd Tanous logEntryArray = nlohmann::json::array(); 339*b0983db2SEd Tanous 340*b0983db2SEd Tanous // Go through the journal and use the timestamp to create a 341*b0983db2SEd Tanous // unique ID for each entry 342*b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 343*b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 344*b0983db2SEd Tanous if (ret < 0) 345*b0983db2SEd Tanous { 346*b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 347*b0983db2SEd Tanous messages::internalError(asyncResp->res); 348*b0983db2SEd Tanous return; 349*b0983db2SEd Tanous } 350*b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 351*b0983db2SEd Tanous journalTmp, sd_journal_close); 352*b0983db2SEd Tanous journalTmp = nullptr; 353*b0983db2SEd Tanous uint64_t entryCount = 0; 354*b0983db2SEd Tanous // Reset the unique ID on the first entry 355*b0983db2SEd Tanous bool firstEntry = true; 356*b0983db2SEd Tanous SD_JOURNAL_FOREACH(journal.get()) 357*b0983db2SEd Tanous { 358*b0983db2SEd Tanous entryCount++; 359*b0983db2SEd Tanous // Handle paging using skip (number of entries to skip from 360*b0983db2SEd Tanous // the start) and top (number of entries to display) 361*b0983db2SEd Tanous if (entryCount <= skip || entryCount > skip + top) 362*b0983db2SEd Tanous { 363*b0983db2SEd Tanous continue; 364*b0983db2SEd Tanous } 365*b0983db2SEd Tanous 366*b0983db2SEd Tanous std::string idStr; 367*b0983db2SEd Tanous if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 368*b0983db2SEd Tanous { 369*b0983db2SEd Tanous continue; 370*b0983db2SEd Tanous } 371*b0983db2SEd Tanous firstEntry = false; 372*b0983db2SEd Tanous 373*b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 374*b0983db2SEd Tanous if (fillBMCJournalLogEntryJson(idStr, journal.get(), 375*b0983db2SEd Tanous bmcJournalLogEntry) != 0) 376*b0983db2SEd Tanous { 377*b0983db2SEd Tanous messages::internalError(asyncResp->res); 378*b0983db2SEd Tanous return; 379*b0983db2SEd Tanous } 380*b0983db2SEd Tanous logEntryArray.emplace_back(std::move(bmcJournalLogEntry)); 381*b0983db2SEd Tanous } 382*b0983db2SEd Tanous asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 383*b0983db2SEd Tanous if (skip + top < entryCount) 384*b0983db2SEd Tanous { 385*b0983db2SEd Tanous asyncResp->res 386*b0983db2SEd Tanous .jsonValue["Members@odata.nextLink"] = boost::urls::format( 387*b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 388*b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 389*b0983db2SEd Tanous } 390*b0983db2SEd Tanous }); 391*b0983db2SEd Tanous } 392*b0983db2SEd Tanous 393*b0983db2SEd Tanous inline void requestRoutesBMCJournalLogEntry(App& app) 394*b0983db2SEd Tanous { 395*b0983db2SEd Tanous BMCWEB_ROUTE( 396*b0983db2SEd Tanous app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 397*b0983db2SEd Tanous .privileges(redfish::privileges::getLogEntry) 398*b0983db2SEd Tanous .methods(boost::beast::http::verb::get)( 399*b0983db2SEd Tanous [&app](const crow::Request& req, 400*b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 401*b0983db2SEd Tanous const std::string& managerId, const std::string& entryID) { 402*b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 403*b0983db2SEd Tanous { 404*b0983db2SEd Tanous return; 405*b0983db2SEd Tanous } 406*b0983db2SEd Tanous 407*b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 408*b0983db2SEd Tanous { 409*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 410*b0983db2SEd Tanous return; 411*b0983db2SEd Tanous } 412*b0983db2SEd Tanous 413*b0983db2SEd Tanous // Convert the unique ID back to a timestamp to find the entry 414*b0983db2SEd Tanous sd_id128_t bootID{}; 415*b0983db2SEd Tanous uint64_t ts = 0; 416*b0983db2SEd Tanous uint64_t index = 0; 417*b0983db2SEd Tanous if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) 418*b0983db2SEd Tanous { 419*b0983db2SEd Tanous return; 420*b0983db2SEd Tanous } 421*b0983db2SEd Tanous 422*b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 423*b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 424*b0983db2SEd Tanous if (ret < 0) 425*b0983db2SEd Tanous { 426*b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 427*b0983db2SEd Tanous messages::internalError(asyncResp->res); 428*b0983db2SEd Tanous return; 429*b0983db2SEd Tanous } 430*b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 431*b0983db2SEd Tanous journalTmp, sd_journal_close); 432*b0983db2SEd Tanous journalTmp = nullptr; 433*b0983db2SEd Tanous // Go to the timestamp in the log and move to the entry at the 434*b0983db2SEd Tanous // index tracking the unique ID 435*b0983db2SEd Tanous std::string idStr; 436*b0983db2SEd Tanous bool firstEntry = true; 437*b0983db2SEd Tanous ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); 438*b0983db2SEd Tanous if (ret < 0) 439*b0983db2SEd Tanous { 440*b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", 441*b0983db2SEd Tanous strerror(-ret)); 442*b0983db2SEd Tanous messages::internalError(asyncResp->res); 443*b0983db2SEd Tanous return; 444*b0983db2SEd Tanous } 445*b0983db2SEd Tanous for (uint64_t i = 0; i <= index; i++) 446*b0983db2SEd Tanous { 447*b0983db2SEd Tanous sd_journal_next(journal.get()); 448*b0983db2SEd Tanous if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 449*b0983db2SEd Tanous { 450*b0983db2SEd Tanous messages::internalError(asyncResp->res); 451*b0983db2SEd Tanous return; 452*b0983db2SEd Tanous } 453*b0983db2SEd Tanous firstEntry = false; 454*b0983db2SEd Tanous } 455*b0983db2SEd Tanous // Confirm that the entry ID matches what was requested 456*b0983db2SEd Tanous if (idStr != entryID) 457*b0983db2SEd Tanous { 458*b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 459*b0983db2SEd Tanous return; 460*b0983db2SEd Tanous } 461*b0983db2SEd Tanous 462*b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 463*b0983db2SEd Tanous if (fillBMCJournalLogEntryJson(entryID, journal.get(), 464*b0983db2SEd Tanous bmcJournalLogEntry) != 0) 465*b0983db2SEd Tanous { 466*b0983db2SEd Tanous messages::internalError(asyncResp->res); 467*b0983db2SEd Tanous return; 468*b0983db2SEd Tanous } 469*b0983db2SEd Tanous asyncResp->res.jsonValue.update(bmcJournalLogEntry); 470*b0983db2SEd Tanous }); 471*b0983db2SEd Tanous } 472*b0983db2SEd Tanous } // namespace redfish 473