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