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); 75*bd79bce8SPatrick Williams auto [ptr, indexEc] = 76*bd79bce8SPatrick Williams std::from_chars(entryIDStrView.begin(), 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 } 82055713e4SEd Tanous if (index <= 1) 83055713e4SEd Tanous { 84055713e4SEd Tanous // Indexes go directly from no postfix (handled above) to _2 85055713e4SEd Tanous // so if we ever see _0 or _1, it's incorrect 86055713e4SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 87055713e4SEd Tanous return false; 88055713e4SEd Tanous } 89055713e4SEd Tanous 90055713e4SEd Tanous // URI indexes are one based, journald is zero based 91055713e4SEd Tanous index -= 1; 92b0983db2SEd Tanous return true; 93b0983db2SEd Tanous } 94b0983db2SEd Tanous 95055713e4SEd Tanous inline std::string getUniqueEntryID(uint64_t index, uint64_t curTs, 96055713e4SEd Tanous sd_id128_t& curBootID) 97b0983db2SEd Tanous { 98b0983db2SEd Tanous // make entryID as <bootID>_<timestamp>[_<index>] 99b0983db2SEd Tanous std::array<char, SD_ID128_STRING_MAX> bootIDStr{}; 100b0983db2SEd Tanous sd_id128_to_string(curBootID, bootIDStr.data()); 101055713e4SEd Tanous std::string postfix; 102b0983db2SEd Tanous if (index > 0) 103b0983db2SEd Tanous { 104055713e4SEd Tanous postfix = std::format("_{}", index + 1); 105b0983db2SEd Tanous } 106055713e4SEd Tanous return std::format("{}_{}{}", bootIDStr.data(), curTs, postfix); 107b0983db2SEd Tanous } 108b0983db2SEd Tanous 109b0983db2SEd Tanous inline int getJournalMetadata(sd_journal* journal, std::string_view field, 110b0983db2SEd Tanous std::string_view& contents) 111b0983db2SEd Tanous { 112b0983db2SEd Tanous const char* data = nullptr; 113b0983db2SEd Tanous size_t length = 0; 114b0983db2SEd Tanous int ret = 0; 115b0983db2SEd Tanous // Get the metadata from the requested field of the journal entry 116b0983db2SEd Tanous // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 117b0983db2SEd Tanous const void** dataVoid = reinterpret_cast<const void**>(&data); 118b0983db2SEd Tanous 119b0983db2SEd Tanous ret = sd_journal_get_data(journal, field.data(), dataVoid, &length); 120b0983db2SEd Tanous if (ret < 0) 121b0983db2SEd Tanous { 122b0983db2SEd Tanous return ret; 123b0983db2SEd Tanous } 124b0983db2SEd Tanous contents = std::string_view(data, length); 125b0983db2SEd Tanous // Only use the content after the "=" character. 126b0983db2SEd Tanous contents.remove_prefix(std::min(contents.find('=') + 1, contents.size())); 127b0983db2SEd Tanous return ret; 128b0983db2SEd Tanous } 129b0983db2SEd Tanous 130b0983db2SEd Tanous inline int getJournalMetadataInt(sd_journal* journal, std::string_view field, 131b0983db2SEd Tanous const int& base, long int& contents) 132b0983db2SEd Tanous { 133b0983db2SEd Tanous int ret = 0; 134b0983db2SEd Tanous std::string_view metadata; 135b0983db2SEd Tanous // Get the metadata from the requested field of the journal entry 136b0983db2SEd Tanous ret = getJournalMetadata(journal, field, metadata); 137b0983db2SEd Tanous if (ret < 0) 138b0983db2SEd Tanous { 139b0983db2SEd Tanous return ret; 140b0983db2SEd Tanous } 141b0983db2SEd Tanous contents = strtol(metadata.data(), nullptr, base); 142b0983db2SEd Tanous return ret; 143b0983db2SEd Tanous } 144b0983db2SEd Tanous 145b0983db2SEd Tanous inline bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) 146b0983db2SEd Tanous { 147b0983db2SEd Tanous int ret = 0; 148b0983db2SEd Tanous uint64_t timestamp = 0; 149b0983db2SEd Tanous ret = sd_journal_get_realtime_usec(journal, ×tamp); 150b0983db2SEd Tanous if (ret < 0) 151b0983db2SEd Tanous { 152b0983db2SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); 153b0983db2SEd Tanous return false; 154b0983db2SEd Tanous } 155b0983db2SEd Tanous entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp); 156b0983db2SEd Tanous return true; 157b0983db2SEd Tanous } 158b0983db2SEd Tanous 159*bd79bce8SPatrick Williams inline bool fillBMCJournalLogEntryJson( 160*bd79bce8SPatrick Williams const std::string& bmcJournalLogEntryID, sd_journal* journal, 161b0983db2SEd Tanous nlohmann::json::object_t& bmcJournalLogEntryJson) 162b0983db2SEd Tanous { 163b0983db2SEd Tanous // Get the Log Entry contents 164b0983db2SEd Tanous std::string message; 165b0983db2SEd Tanous std::string_view syslogID; 166055713e4SEd Tanous int ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 167b0983db2SEd Tanous if (ret < 0) 168b0983db2SEd Tanous { 169b0983db2SEd Tanous BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}", 170b0983db2SEd Tanous strerror(-ret)); 171b0983db2SEd Tanous } 172b0983db2SEd Tanous if (!syslogID.empty()) 173b0983db2SEd Tanous { 174b0983db2SEd Tanous message += std::string(syslogID) + ": "; 175b0983db2SEd Tanous } 176b0983db2SEd Tanous 177b0983db2SEd Tanous std::string_view msg; 178b0983db2SEd Tanous ret = getJournalMetadata(journal, "MESSAGE", msg); 179b0983db2SEd Tanous if (ret < 0) 180b0983db2SEd Tanous { 181b0983db2SEd Tanous BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret)); 182055713e4SEd Tanous return false; 183b0983db2SEd Tanous } 184b0983db2SEd Tanous message += std::string(msg); 185b0983db2SEd Tanous 186b0983db2SEd Tanous // Get the severity from the PRIORITY field 187b0983db2SEd Tanous long int severity = 8; // Default to an invalid priority 188b0983db2SEd Tanous ret = getJournalMetadataInt(journal, "PRIORITY", 10, severity); 189b0983db2SEd Tanous if (ret < 0) 190b0983db2SEd Tanous { 191b0983db2SEd Tanous BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret)); 192b0983db2SEd Tanous } 193b0983db2SEd Tanous 194b0983db2SEd Tanous // Get the Created time from the timestamp 195b0983db2SEd Tanous std::string entryTimeStr; 196b0983db2SEd Tanous if (!getEntryTimestamp(journal, entryTimeStr)) 197b0983db2SEd Tanous { 198055713e4SEd Tanous return false; 199b0983db2SEd Tanous } 200b0983db2SEd Tanous 201b0983db2SEd Tanous // Fill in the log entry with the gathered data 202b0983db2SEd Tanous bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 203b0983db2SEd Tanous bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( 204b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}", 205b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID); 206b0983db2SEd Tanous bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; 207b0983db2SEd Tanous bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; 208b0983db2SEd Tanous bmcJournalLogEntryJson["Message"] = std::move(message); 209539d8c6bSEd Tanous bmcJournalLogEntryJson["EntryType"] = log_entry::LogEntryType::Oem; 210b0983db2SEd Tanous log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; 211b0983db2SEd Tanous if (severity <= 2) 212b0983db2SEd Tanous { 213b0983db2SEd Tanous severityEnum = log_entry::EventSeverity::Critical; 214b0983db2SEd Tanous } 215b0983db2SEd Tanous else if (severity <= 4) 216b0983db2SEd Tanous { 217b0983db2SEd Tanous severityEnum = log_entry::EventSeverity::Warning; 218b0983db2SEd Tanous } 219b0983db2SEd Tanous 220b0983db2SEd Tanous bmcJournalLogEntryJson["Severity"] = severityEnum; 221b0983db2SEd Tanous bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; 222b0983db2SEd Tanous bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); 223055713e4SEd Tanous return true; 224b0983db2SEd Tanous } 225b0983db2SEd Tanous 22684177a2fSEd Tanous inline void handleManagersLogServiceJournalGet( 22784177a2fSEd Tanous App& app, const crow::Request& req, 228b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 22984177a2fSEd Tanous const std::string& managerId) 23084177a2fSEd Tanous { 231b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 232b0983db2SEd Tanous { 233b0983db2SEd Tanous return; 234b0983db2SEd Tanous } 235b0983db2SEd Tanous 236b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 237b0983db2SEd Tanous { 238b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 239b0983db2SEd Tanous return; 240b0983db2SEd Tanous } 241b0983db2SEd Tanous 24284177a2fSEd Tanous asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 243b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = 244b0983db2SEd Tanous boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 245b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 246b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 247b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 248b0983db2SEd Tanous asyncResp->res.jsonValue["Id"] = "Journal"; 249b0983db2SEd Tanous asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 250b0983db2SEd Tanous 251b0983db2SEd Tanous std::pair<std::string, std::string> redfishDateTimeOffset = 252b0983db2SEd Tanous redfish::time_utils::getDateTimeOffsetNow(); 253b0983db2SEd Tanous asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 254b0983db2SEd Tanous asyncResp->res.jsonValue["DateTimeLocalOffset"] = 255b0983db2SEd Tanous redfishDateTimeOffset.second; 256b0983db2SEd Tanous 257b0983db2SEd Tanous asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 258b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 259b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 260b0983db2SEd Tanous } 261b0983db2SEd Tanous 262055713e4SEd Tanous struct JournalReadState 263055713e4SEd Tanous { 264055713e4SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal; 265055713e4SEd Tanous uint64_t index = 0; 266055713e4SEd Tanous sd_id128_t prevBootID{}; 267055713e4SEd Tanous uint64_t prevTs = 0; 268055713e4SEd Tanous }; 269055713e4SEd Tanous 270*bd79bce8SPatrick Williams inline void readJournalEntries( 271*bd79bce8SPatrick Williams uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 272055713e4SEd Tanous JournalReadState&& readState) 273055713e4SEd Tanous { 274055713e4SEd Tanous nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"]; 275055713e4SEd Tanous nlohmann::json::array_t* logEntryArray = 276055713e4SEd Tanous logEntry.get_ptr<nlohmann::json::array_t*>(); 277055713e4SEd Tanous if (logEntryArray == nullptr) 278055713e4SEd Tanous { 279055713e4SEd Tanous messages::internalError(asyncResp->res); 280055713e4SEd Tanous return; 281055713e4SEd Tanous } 282055713e4SEd Tanous 283055713e4SEd Tanous // The Journal APIs unfortunately do blocking calls to the filesystem, and 284055713e4SEd Tanous // can be somewhat expensive. Short of creating our own io_uring based 285055713e4SEd Tanous // implementation of sd-journal, which would be difficult, the best thing we 286055713e4SEd Tanous // can do is to only parse a certain number of entries at a time. The 287055713e4SEd Tanous // current chunk size is selected arbitrarily to ensure that we're not 288055713e4SEd Tanous // trying to process thousands of entries at the same time. 289055713e4SEd Tanous // The implementation will process the number of entries, then return 290055713e4SEd Tanous // control to the io_context to let other operations continue. 291055713e4SEd Tanous size_t segmentCountRemaining = 10; 292055713e4SEd Tanous 293055713e4SEd Tanous // Reset the unique ID on the first entry 294055713e4SEd Tanous for (uint64_t entryCount = logEntryArray->size(); 295055713e4SEd Tanous entryCount < topEntryCount; entryCount++) 296055713e4SEd Tanous { 297055713e4SEd Tanous if (segmentCountRemaining == 0) 298055713e4SEd Tanous { 299055713e4SEd Tanous boost::asio::post(crow::connections::systemBus->get_io_context(), 300055713e4SEd Tanous [asyncResp, topEntryCount, 301055713e4SEd Tanous readState = std::move(readState)]() mutable { 302055713e4SEd Tanous readJournalEntries(topEntryCount, asyncResp, 303055713e4SEd Tanous std::move(readState)); 304055713e4SEd Tanous }); 305055713e4SEd Tanous return; 306055713e4SEd Tanous } 307055713e4SEd Tanous 308055713e4SEd Tanous // Get the entry timestamp 309055713e4SEd Tanous sd_id128_t curBootID{}; 310055713e4SEd Tanous uint64_t curTs = 0; 311055713e4SEd Tanous int ret = sd_journal_get_monotonic_usec(readState.journal.get(), &curTs, 312055713e4SEd Tanous &curBootID); 313055713e4SEd Tanous if (ret < 0) 314055713e4SEd Tanous { 315055713e4SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", 316055713e4SEd Tanous strerror(-ret)); 317055713e4SEd Tanous messages::internalError(asyncResp->res); 318055713e4SEd Tanous return; 319055713e4SEd Tanous } 320055713e4SEd Tanous 321055713e4SEd Tanous // If the timestamp isn't unique on the same boot, increment the index 322055713e4SEd Tanous bool sameBootIDs = sd_id128_equal(curBootID, readState.prevBootID) != 0; 323055713e4SEd Tanous if (sameBootIDs && (curTs == readState.prevTs)) 324055713e4SEd Tanous { 325055713e4SEd Tanous readState.index++; 326055713e4SEd Tanous } 327055713e4SEd Tanous else 328055713e4SEd Tanous { 329055713e4SEd Tanous // Otherwise, reset it 330055713e4SEd Tanous readState.index = 0; 331055713e4SEd Tanous } 332055713e4SEd Tanous 333055713e4SEd Tanous // Save the bootID 334055713e4SEd Tanous readState.prevBootID = curBootID; 335055713e4SEd Tanous 336055713e4SEd Tanous // Save the timestamp 337055713e4SEd Tanous readState.prevTs = curTs; 338055713e4SEd Tanous 339055713e4SEd Tanous std::string idStr = getUniqueEntryID(readState.index, curTs, curBootID); 340055713e4SEd Tanous 341055713e4SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 342055713e4SEd Tanous if (!fillBMCJournalLogEntryJson(idStr, readState.journal.get(), 343055713e4SEd Tanous bmcJournalLogEntry)) 344055713e4SEd Tanous { 345055713e4SEd Tanous messages::internalError(asyncResp->res); 346055713e4SEd Tanous return; 347055713e4SEd Tanous } 348055713e4SEd Tanous logEntryArray->emplace_back(std::move(bmcJournalLogEntry)); 349055713e4SEd Tanous 350055713e4SEd Tanous ret = sd_journal_next(readState.journal.get()); 351055713e4SEd Tanous if (ret < 0) 352055713e4SEd Tanous { 353055713e4SEd Tanous messages::internalError(asyncResp->res); 354055713e4SEd Tanous return; 355055713e4SEd Tanous } 356055713e4SEd Tanous if (ret == 0) 357055713e4SEd Tanous { 358055713e4SEd Tanous break; 359055713e4SEd Tanous } 360055713e4SEd Tanous segmentCountRemaining--; 361055713e4SEd Tanous } 362055713e4SEd Tanous } 363055713e4SEd Tanous 36484177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet( 36584177a2fSEd Tanous App& app, const crow::Request& req, 366b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 36784177a2fSEd Tanous const std::string& managerId) 36884177a2fSEd Tanous { 369b0983db2SEd Tanous query_param::QueryCapabilities capabilities = { 370b0983db2SEd Tanous .canDelegateTop = true, 371b0983db2SEd Tanous .canDelegateSkip = true, 372b0983db2SEd Tanous }; 373b0983db2SEd Tanous query_param::Query delegatedQuery; 37484177a2fSEd Tanous if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 37584177a2fSEd Tanous delegatedQuery, capabilities)) 376b0983db2SEd Tanous { 377b0983db2SEd Tanous return; 378b0983db2SEd Tanous } 379b0983db2SEd Tanous 380b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 381b0983db2SEd Tanous { 382b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 383b0983db2SEd Tanous return; 384b0983db2SEd Tanous } 385b0983db2SEd Tanous 386b0983db2SEd Tanous size_t skip = delegatedQuery.skip.value_or(0); 387b0983db2SEd Tanous size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 388b0983db2SEd Tanous 389b0983db2SEd Tanous // Collections don't include the static data added by SubRoute 390b0983db2SEd Tanous // because it has a duplicate entry for members 391b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 392b0983db2SEd Tanous "#LogEntryCollection.LogEntryCollection"; 393b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 394b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 395b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 396b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 397b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = 398b0983db2SEd Tanous "Collection of BMC Journal Entries"; 399055713e4SEd Tanous asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t(); 400b0983db2SEd Tanous 401b0983db2SEd Tanous // Go through the journal and use the timestamp to create a 402b0983db2SEd Tanous // unique ID for each entry 403b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 404b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 405b0983db2SEd Tanous if (ret < 0) 406b0983db2SEd Tanous { 407b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 408b0983db2SEd Tanous messages::internalError(asyncResp->res); 409b0983db2SEd Tanous return; 410b0983db2SEd Tanous } 411055713e4SEd Tanous 412b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 413b0983db2SEd Tanous journalTmp, sd_journal_close); 414b0983db2SEd Tanous journalTmp = nullptr; 415b0983db2SEd Tanous 416055713e4SEd Tanous // Seek to the end 417055713e4SEd Tanous if (sd_journal_seek_tail(journal.get()) < 0) 418b0983db2SEd Tanous { 419b0983db2SEd Tanous messages::internalError(asyncResp->res); 420b0983db2SEd Tanous return; 421b0983db2SEd Tanous } 422055713e4SEd Tanous 423055713e4SEd Tanous // Get the last entry 424055713e4SEd Tanous if (sd_journal_previous(journal.get()) < 0) 425055713e4SEd Tanous { 426055713e4SEd Tanous messages::internalError(asyncResp->res); 427055713e4SEd Tanous return; 428b0983db2SEd Tanous } 429055713e4SEd Tanous 430055713e4SEd Tanous // Get the last sequence number 431055713e4SEd Tanous uint64_t endSeqNum = 0; 432055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 433055713e4SEd Tanous { 434055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0) 435055713e4SEd Tanous { 436055713e4SEd Tanous messages::internalError(asyncResp->res); 437055713e4SEd Tanous return; 438055713e4SEd Tanous } 439055713e4SEd Tanous } 440055713e4SEd Tanous #endif 441055713e4SEd Tanous 442055713e4SEd Tanous // Seek to the beginning 443055713e4SEd Tanous if (sd_journal_seek_head(journal.get()) < 0) 444055713e4SEd Tanous { 445055713e4SEd Tanous messages::internalError(asyncResp->res); 446055713e4SEd Tanous return; 447055713e4SEd Tanous } 448055713e4SEd Tanous 449055713e4SEd Tanous // Get the first entry 450055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 451055713e4SEd Tanous { 452055713e4SEd Tanous messages::internalError(asyncResp->res); 453055713e4SEd Tanous return; 454055713e4SEd Tanous } 455055713e4SEd Tanous 456055713e4SEd Tanous // Get the first sequence number 457055713e4SEd Tanous uint64_t startSeqNum = 0; 458055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 459055713e4SEd Tanous { 460055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0) 461055713e4SEd Tanous { 462055713e4SEd Tanous messages::internalError(asyncResp->res); 463055713e4SEd Tanous return; 464055713e4SEd Tanous } 465055713e4SEd Tanous } 466055713e4SEd Tanous #endif 467055713e4SEd Tanous 468055713e4SEd Tanous BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum, 469055713e4SEd Tanous endSeqNum); 470055713e4SEd Tanous 471055713e4SEd Tanous // Add 1 to account for the last entry 472055713e4SEd Tanous uint64_t totalEntries = endSeqNum - startSeqNum + 1; 473055713e4SEd Tanous asyncResp->res.jsonValue["Members@odata.count"] = totalEntries; 474055713e4SEd Tanous if (skip + top < totalEntries) 475b0983db2SEd Tanous { 47684177a2fSEd Tanous asyncResp->res.jsonValue["Members@odata.nextLink"] = 47784177a2fSEd Tanous boost::urls::format( 478b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 479b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 480b0983db2SEd Tanous } 481055713e4SEd Tanous uint64_t index = 0; 482055713e4SEd Tanous sd_id128_t curBootID{}; 483055713e4SEd Tanous uint64_t curTs = 0; 484055713e4SEd Tanous if (skip > 0) 485055713e4SEd Tanous { 486055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), skip) < 0) 487055713e4SEd Tanous { 488055713e4SEd Tanous messages::internalError(asyncResp->res); 489055713e4SEd Tanous return; 490055713e4SEd Tanous } 491055713e4SEd Tanous 492055713e4SEd Tanous // Get the entry timestamp 493055713e4SEd Tanous ret = sd_journal_get_monotonic_usec(journal.get(), &curTs, &curBootID); 494055713e4SEd Tanous if (ret < 0) 495055713e4SEd Tanous { 496055713e4SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", 497055713e4SEd Tanous strerror(-ret)); 498055713e4SEd Tanous messages::internalError(asyncResp->res); 499055713e4SEd Tanous return; 500055713e4SEd Tanous } 501055713e4SEd Tanous 502055713e4SEd Tanous uint64_t endChunkSeqNum = 0; 503055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 504055713e4SEd Tanous { 505055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endChunkSeqNum, nullptr) < 506055713e4SEd Tanous 0) 507055713e4SEd Tanous { 508055713e4SEd Tanous messages::internalError(asyncResp->res); 509055713e4SEd Tanous return; 510055713e4SEd Tanous } 511055713e4SEd Tanous } 512055713e4SEd Tanous #endif 513055713e4SEd Tanous 514055713e4SEd Tanous // Seek to the first entry with the same timestamp and boot 515055713e4SEd Tanous ret = sd_journal_seek_monotonic_usec(journal.get(), curBootID, curTs); 516055713e4SEd Tanous if (ret < 0) 517055713e4SEd Tanous { 518055713e4SEd Tanous BMCWEB_LOG_ERROR("Failed to seek: {}", strerror(-ret)); 519055713e4SEd Tanous messages::internalError(asyncResp->res); 520055713e4SEd Tanous return; 521055713e4SEd Tanous } 522055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 523055713e4SEd Tanous { 524055713e4SEd Tanous messages::internalError(asyncResp->res); 525055713e4SEd Tanous return; 526055713e4SEd Tanous } 527055713e4SEd Tanous uint64_t startChunkSeqNum = 0; 528055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 529055713e4SEd Tanous { 530055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startChunkSeqNum, 531055713e4SEd Tanous nullptr) < 0) 532055713e4SEd Tanous { 533055713e4SEd Tanous messages::internalError(asyncResp->res); 534055713e4SEd Tanous return; 535055713e4SEd Tanous } 536055713e4SEd Tanous } 537055713e4SEd Tanous #endif 538055713e4SEd Tanous 539055713e4SEd Tanous // Get the difference between the start and end. Most of the time this 540055713e4SEd Tanous // will be 0 541055713e4SEd Tanous BMCWEB_LOG_DEBUG("start={} end={}", startChunkSeqNum, endChunkSeqNum); 542055713e4SEd Tanous index = endChunkSeqNum - startChunkSeqNum; 543055713e4SEd Tanous if (index > endChunkSeqNum) 544055713e4SEd Tanous { 545055713e4SEd Tanous // Detect underflows. Should never happen. 546055713e4SEd Tanous messages::internalError(asyncResp->res); 547055713e4SEd Tanous return; 548055713e4SEd Tanous } 549055713e4SEd Tanous if (index > 0) 550055713e4SEd Tanous { 551055713e4SEd Tanous BMCWEB_LOG_DEBUG("index = {}", index); 552055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), index) < 0) 553055713e4SEd Tanous { 554055713e4SEd Tanous messages::internalError(asyncResp->res); 555055713e4SEd Tanous return; 556055713e4SEd Tanous } 557055713e4SEd Tanous } 558055713e4SEd Tanous } 559055713e4SEd Tanous // If this is the first entry of this series, reset the timestamps so the 560055713e4SEd Tanous // Index doesn't increment 561055713e4SEd Tanous if (index == 0) 562055713e4SEd Tanous { 563055713e4SEd Tanous curBootID = {}; 564055713e4SEd Tanous curTs = 0; 565055713e4SEd Tanous } 566055713e4SEd Tanous else 567055713e4SEd Tanous { 568055713e4SEd Tanous index -= 1; 569055713e4SEd Tanous } 570055713e4SEd Tanous BMCWEB_LOG_DEBUG("Index was {}", index); 571055713e4SEd Tanous readJournalEntries(top, asyncResp, 572055713e4SEd Tanous {std::move(journal), index, curBootID, curTs}); 573b0983db2SEd Tanous } 574b0983db2SEd Tanous 57584177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet( 57684177a2fSEd Tanous App& app, const crow::Request& req, 577b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 57884177a2fSEd Tanous const std::string& managerId, const std::string& entryID) 57984177a2fSEd Tanous { 580b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 581b0983db2SEd Tanous { 582b0983db2SEd Tanous return; 583b0983db2SEd Tanous } 584b0983db2SEd Tanous 585b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 586b0983db2SEd Tanous { 587b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 588b0983db2SEd Tanous return; 589b0983db2SEd Tanous } 590b0983db2SEd Tanous 591b0983db2SEd Tanous // Convert the unique ID back to a timestamp to find the entry 592b0983db2SEd Tanous sd_id128_t bootID{}; 593b0983db2SEd Tanous uint64_t ts = 0; 594b0983db2SEd Tanous uint64_t index = 0; 595b0983db2SEd Tanous if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) 596b0983db2SEd Tanous { 597b0983db2SEd Tanous return; 598b0983db2SEd Tanous } 599b0983db2SEd Tanous 600b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 601b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 602b0983db2SEd Tanous if (ret < 0) 603b0983db2SEd Tanous { 604b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 605b0983db2SEd Tanous messages::internalError(asyncResp->res); 606b0983db2SEd Tanous return; 607b0983db2SEd Tanous } 608b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 609b0983db2SEd Tanous journalTmp, sd_journal_close); 610b0983db2SEd Tanous journalTmp = nullptr; 611b0983db2SEd Tanous // Go to the timestamp in the log and move to the entry at the 612b0983db2SEd Tanous // index tracking the unique ID 613b0983db2SEd Tanous ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); 614b0983db2SEd Tanous if (ret < 0) 615b0983db2SEd Tanous { 616b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", 617b0983db2SEd Tanous strerror(-ret)); 618b0983db2SEd Tanous messages::internalError(asyncResp->res); 619b0983db2SEd Tanous return; 620b0983db2SEd Tanous } 621055713e4SEd Tanous 622055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), index + 1) < 0) 623b0983db2SEd Tanous { 624b0983db2SEd Tanous messages::internalError(asyncResp->res); 625b0983db2SEd Tanous return; 626b0983db2SEd Tanous } 627055713e4SEd Tanous 628b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 629055713e4SEd Tanous if (!fillBMCJournalLogEntryJson(entryID, journal.get(), bmcJournalLogEntry)) 630b0983db2SEd Tanous { 631b0983db2SEd Tanous messages::internalError(asyncResp->res); 632b0983db2SEd Tanous return; 633b0983db2SEd Tanous } 634b0983db2SEd Tanous asyncResp->res.jsonValue.update(bmcJournalLogEntry); 635055713e4SEd Tanous } 63684177a2fSEd Tanous 63784177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app) 63884177a2fSEd Tanous { 63984177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 64084177a2fSEd Tanous .privileges(redfish::privileges::getLogService) 64184177a2fSEd Tanous .methods(boost::beast::http::verb::get)( 64284177a2fSEd Tanous std::bind_front(handleManagersLogServiceJournalGet, std::ref(app))); 64384177a2fSEd Tanous 64484177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 64584177a2fSEd Tanous .privileges(redfish::privileges::getLogEntryCollection) 64684177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 64784177a2fSEd Tanous handleManagersJournalLogEntryCollectionGet, std::ref(app))); 64884177a2fSEd Tanous 64984177a2fSEd Tanous BMCWEB_ROUTE( 65084177a2fSEd Tanous app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 65184177a2fSEd Tanous .privileges(redfish::privileges::getLogEntry) 65284177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 65384177a2fSEd Tanous handleManagersJournalEntriesLogEntryGet, std::ref(app))); 654b0983db2SEd Tanous } 655b0983db2SEd Tanous } // namespace redfish 656