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 } 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 159055713e4SEd Tanous inline bool 160b0983db2SEd Tanous fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 161b0983db2SEd Tanous sd_journal* journal, 162b0983db2SEd Tanous nlohmann::json::object_t& bmcJournalLogEntryJson) 163b0983db2SEd Tanous { 164b0983db2SEd Tanous // Get the Log Entry contents 165b0983db2SEd Tanous std::string message; 166b0983db2SEd Tanous std::string_view syslogID; 167055713e4SEd Tanous int ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 168b0983db2SEd Tanous if (ret < 0) 169b0983db2SEd Tanous { 170b0983db2SEd Tanous BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}", 171b0983db2SEd Tanous strerror(-ret)); 172b0983db2SEd Tanous } 173b0983db2SEd Tanous if (!syslogID.empty()) 174b0983db2SEd Tanous { 175b0983db2SEd Tanous message += std::string(syslogID) + ": "; 176b0983db2SEd Tanous } 177b0983db2SEd Tanous 178b0983db2SEd Tanous std::string_view msg; 179b0983db2SEd Tanous ret = getJournalMetadata(journal, "MESSAGE", msg); 180b0983db2SEd Tanous if (ret < 0) 181b0983db2SEd Tanous { 182b0983db2SEd Tanous BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret)); 183055713e4SEd Tanous return false; 184b0983db2SEd Tanous } 185b0983db2SEd Tanous message += std::string(msg); 186b0983db2SEd Tanous 187b0983db2SEd Tanous // Get the severity from the PRIORITY field 188b0983db2SEd Tanous long int severity = 8; // Default to an invalid priority 189b0983db2SEd Tanous ret = getJournalMetadataInt(journal, "PRIORITY", 10, severity); 190b0983db2SEd Tanous if (ret < 0) 191b0983db2SEd Tanous { 192b0983db2SEd Tanous BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret)); 193b0983db2SEd Tanous } 194b0983db2SEd Tanous 195b0983db2SEd Tanous // Get the Created time from the timestamp 196b0983db2SEd Tanous std::string entryTimeStr; 197b0983db2SEd Tanous if (!getEntryTimestamp(journal, entryTimeStr)) 198b0983db2SEd Tanous { 199055713e4SEd Tanous return false; 200b0983db2SEd Tanous } 201b0983db2SEd Tanous 202b0983db2SEd Tanous // Fill in the log entry with the gathered data 203b0983db2SEd Tanous bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 204b0983db2SEd Tanous bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( 205b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}", 206b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID); 207b0983db2SEd Tanous bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; 208b0983db2SEd Tanous bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; 209b0983db2SEd Tanous bmcJournalLogEntryJson["Message"] = std::move(message); 210*539d8c6bSEd Tanous bmcJournalLogEntryJson["EntryType"] = log_entry::LogEntryType::Oem; 211b0983db2SEd Tanous log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; 212b0983db2SEd Tanous if (severity <= 2) 213b0983db2SEd Tanous { 214b0983db2SEd Tanous severityEnum = log_entry::EventSeverity::Critical; 215b0983db2SEd Tanous } 216b0983db2SEd Tanous else if (severity <= 4) 217b0983db2SEd Tanous { 218b0983db2SEd Tanous severityEnum = log_entry::EventSeverity::Warning; 219b0983db2SEd Tanous } 220b0983db2SEd Tanous 221b0983db2SEd Tanous bmcJournalLogEntryJson["Severity"] = severityEnum; 222b0983db2SEd Tanous bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; 223b0983db2SEd Tanous bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); 224055713e4SEd Tanous return true; 225b0983db2SEd Tanous } 226b0983db2SEd Tanous 22784177a2fSEd Tanous inline void handleManagersLogServiceJournalGet( 22884177a2fSEd Tanous App& app, const crow::Request& req, 229b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 23084177a2fSEd Tanous const std::string& managerId) 23184177a2fSEd Tanous { 232b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 233b0983db2SEd Tanous { 234b0983db2SEd Tanous return; 235b0983db2SEd Tanous } 236b0983db2SEd Tanous 237b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 238b0983db2SEd Tanous { 239b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 240b0983db2SEd Tanous return; 241b0983db2SEd Tanous } 242b0983db2SEd Tanous 24384177a2fSEd Tanous asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 244b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = 245b0983db2SEd Tanous boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 246b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 247b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 248b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 249b0983db2SEd Tanous asyncResp->res.jsonValue["Id"] = "Journal"; 250b0983db2SEd Tanous asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 251b0983db2SEd Tanous 252b0983db2SEd Tanous std::pair<std::string, std::string> redfishDateTimeOffset = 253b0983db2SEd Tanous redfish::time_utils::getDateTimeOffsetNow(); 254b0983db2SEd Tanous asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 255b0983db2SEd Tanous asyncResp->res.jsonValue["DateTimeLocalOffset"] = 256b0983db2SEd Tanous redfishDateTimeOffset.second; 257b0983db2SEd Tanous 258b0983db2SEd Tanous asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 259b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 260b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 261b0983db2SEd Tanous } 262b0983db2SEd Tanous 263055713e4SEd Tanous struct JournalReadState 264055713e4SEd Tanous { 265055713e4SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal; 266055713e4SEd Tanous uint64_t index = 0; 267055713e4SEd Tanous sd_id128_t prevBootID{}; 268055713e4SEd Tanous uint64_t prevTs = 0; 269055713e4SEd Tanous }; 270055713e4SEd Tanous 271055713e4SEd Tanous inline void 272055713e4SEd Tanous readJournalEntries(uint64_t topEntryCount, 273055713e4SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 274055713e4SEd Tanous JournalReadState&& readState) 275055713e4SEd Tanous { 276055713e4SEd Tanous nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"]; 277055713e4SEd Tanous nlohmann::json::array_t* logEntryArray = 278055713e4SEd Tanous logEntry.get_ptr<nlohmann::json::array_t*>(); 279055713e4SEd Tanous if (logEntryArray == nullptr) 280055713e4SEd Tanous { 281055713e4SEd Tanous messages::internalError(asyncResp->res); 282055713e4SEd Tanous return; 283055713e4SEd Tanous } 284055713e4SEd Tanous 285055713e4SEd Tanous // The Journal APIs unfortunately do blocking calls to the filesystem, and 286055713e4SEd Tanous // can be somewhat expensive. Short of creating our own io_uring based 287055713e4SEd Tanous // implementation of sd-journal, which would be difficult, the best thing we 288055713e4SEd Tanous // can do is to only parse a certain number of entries at a time. The 289055713e4SEd Tanous // current chunk size is selected arbitrarily to ensure that we're not 290055713e4SEd Tanous // trying to process thousands of entries at the same time. 291055713e4SEd Tanous // The implementation will process the number of entries, then return 292055713e4SEd Tanous // control to the io_context to let other operations continue. 293055713e4SEd Tanous size_t segmentCountRemaining = 10; 294055713e4SEd Tanous 295055713e4SEd Tanous // Reset the unique ID on the first entry 296055713e4SEd Tanous for (uint64_t entryCount = logEntryArray->size(); 297055713e4SEd Tanous entryCount < topEntryCount; entryCount++) 298055713e4SEd Tanous { 299055713e4SEd Tanous if (segmentCountRemaining == 0) 300055713e4SEd Tanous { 301055713e4SEd Tanous boost::asio::post(crow::connections::systemBus->get_io_context(), 302055713e4SEd Tanous [asyncResp, topEntryCount, 303055713e4SEd Tanous readState = std::move(readState)]() mutable { 304055713e4SEd Tanous readJournalEntries(topEntryCount, asyncResp, 305055713e4SEd Tanous std::move(readState)); 306055713e4SEd Tanous }); 307055713e4SEd Tanous return; 308055713e4SEd Tanous } 309055713e4SEd Tanous 310055713e4SEd Tanous // Get the entry timestamp 311055713e4SEd Tanous sd_id128_t curBootID{}; 312055713e4SEd Tanous uint64_t curTs = 0; 313055713e4SEd Tanous int ret = sd_journal_get_monotonic_usec(readState.journal.get(), &curTs, 314055713e4SEd Tanous &curBootID); 315055713e4SEd Tanous if (ret < 0) 316055713e4SEd Tanous { 317055713e4SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", 318055713e4SEd Tanous strerror(-ret)); 319055713e4SEd Tanous messages::internalError(asyncResp->res); 320055713e4SEd Tanous return; 321055713e4SEd Tanous } 322055713e4SEd Tanous 323055713e4SEd Tanous // If the timestamp isn't unique on the same boot, increment the index 324055713e4SEd Tanous bool sameBootIDs = sd_id128_equal(curBootID, readState.prevBootID) != 0; 325055713e4SEd Tanous if (sameBootIDs && (curTs == readState.prevTs)) 326055713e4SEd Tanous { 327055713e4SEd Tanous readState.index++; 328055713e4SEd Tanous } 329055713e4SEd Tanous else 330055713e4SEd Tanous { 331055713e4SEd Tanous // Otherwise, reset it 332055713e4SEd Tanous readState.index = 0; 333055713e4SEd Tanous } 334055713e4SEd Tanous 335055713e4SEd Tanous // Save the bootID 336055713e4SEd Tanous readState.prevBootID = curBootID; 337055713e4SEd Tanous 338055713e4SEd Tanous // Save the timestamp 339055713e4SEd Tanous readState.prevTs = curTs; 340055713e4SEd Tanous 341055713e4SEd Tanous std::string idStr = getUniqueEntryID(readState.index, curTs, curBootID); 342055713e4SEd Tanous 343055713e4SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 344055713e4SEd Tanous if (!fillBMCJournalLogEntryJson(idStr, readState.journal.get(), 345055713e4SEd Tanous bmcJournalLogEntry)) 346055713e4SEd Tanous { 347055713e4SEd Tanous messages::internalError(asyncResp->res); 348055713e4SEd Tanous return; 349055713e4SEd Tanous } 350055713e4SEd Tanous logEntryArray->emplace_back(std::move(bmcJournalLogEntry)); 351055713e4SEd Tanous 352055713e4SEd Tanous ret = sd_journal_next(readState.journal.get()); 353055713e4SEd Tanous if (ret < 0) 354055713e4SEd Tanous { 355055713e4SEd Tanous messages::internalError(asyncResp->res); 356055713e4SEd Tanous return; 357055713e4SEd Tanous } 358055713e4SEd Tanous if (ret == 0) 359055713e4SEd Tanous { 360055713e4SEd Tanous break; 361055713e4SEd Tanous } 362055713e4SEd Tanous segmentCountRemaining--; 363055713e4SEd Tanous } 364055713e4SEd Tanous } 365055713e4SEd Tanous 36684177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet( 36784177a2fSEd Tanous App& app, const crow::Request& req, 368b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 36984177a2fSEd Tanous const std::string& managerId) 37084177a2fSEd Tanous { 371b0983db2SEd Tanous query_param::QueryCapabilities capabilities = { 372b0983db2SEd Tanous .canDelegateTop = true, 373b0983db2SEd Tanous .canDelegateSkip = true, 374b0983db2SEd Tanous }; 375b0983db2SEd Tanous query_param::Query delegatedQuery; 37684177a2fSEd Tanous if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 37784177a2fSEd Tanous delegatedQuery, capabilities)) 378b0983db2SEd Tanous { 379b0983db2SEd Tanous return; 380b0983db2SEd Tanous } 381b0983db2SEd Tanous 382b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 383b0983db2SEd Tanous { 384b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 385b0983db2SEd Tanous return; 386b0983db2SEd Tanous } 387b0983db2SEd Tanous 388b0983db2SEd Tanous size_t skip = delegatedQuery.skip.value_or(0); 389b0983db2SEd Tanous size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 390b0983db2SEd Tanous 391b0983db2SEd Tanous // Collections don't include the static data added by SubRoute 392b0983db2SEd Tanous // because it has a duplicate entry for members 393b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 394b0983db2SEd Tanous "#LogEntryCollection.LogEntryCollection"; 395b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 396b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 397b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 398b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 399b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = 400b0983db2SEd Tanous "Collection of BMC Journal Entries"; 401055713e4SEd Tanous asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t(); 402b0983db2SEd Tanous 403b0983db2SEd Tanous // Go through the journal and use the timestamp to create a 404b0983db2SEd Tanous // unique ID for each entry 405b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 406b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 407b0983db2SEd Tanous if (ret < 0) 408b0983db2SEd Tanous { 409b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 410b0983db2SEd Tanous messages::internalError(asyncResp->res); 411b0983db2SEd Tanous return; 412b0983db2SEd Tanous } 413055713e4SEd Tanous 414b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 415b0983db2SEd Tanous journalTmp, sd_journal_close); 416b0983db2SEd Tanous journalTmp = nullptr; 417b0983db2SEd Tanous 418055713e4SEd Tanous // Seek to the end 419055713e4SEd Tanous if (sd_journal_seek_tail(journal.get()) < 0) 420b0983db2SEd Tanous { 421b0983db2SEd Tanous messages::internalError(asyncResp->res); 422b0983db2SEd Tanous return; 423b0983db2SEd Tanous } 424055713e4SEd Tanous 425055713e4SEd Tanous // Get the last entry 426055713e4SEd Tanous if (sd_journal_previous(journal.get()) < 0) 427055713e4SEd Tanous { 428055713e4SEd Tanous messages::internalError(asyncResp->res); 429055713e4SEd Tanous return; 430b0983db2SEd Tanous } 431055713e4SEd Tanous 432055713e4SEd Tanous // Get the last sequence number 433055713e4SEd Tanous uint64_t endSeqNum = 0; 434055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 435055713e4SEd Tanous { 436055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0) 437055713e4SEd Tanous { 438055713e4SEd Tanous messages::internalError(asyncResp->res); 439055713e4SEd Tanous return; 440055713e4SEd Tanous } 441055713e4SEd Tanous } 442055713e4SEd Tanous #endif 443055713e4SEd Tanous 444055713e4SEd Tanous // Seek to the beginning 445055713e4SEd Tanous if (sd_journal_seek_head(journal.get()) < 0) 446055713e4SEd Tanous { 447055713e4SEd Tanous messages::internalError(asyncResp->res); 448055713e4SEd Tanous return; 449055713e4SEd Tanous } 450055713e4SEd Tanous 451055713e4SEd Tanous // Get the first entry 452055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 453055713e4SEd Tanous { 454055713e4SEd Tanous messages::internalError(asyncResp->res); 455055713e4SEd Tanous return; 456055713e4SEd Tanous } 457055713e4SEd Tanous 458055713e4SEd Tanous // Get the first sequence number 459055713e4SEd Tanous uint64_t startSeqNum = 0; 460055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 461055713e4SEd Tanous { 462055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0) 463055713e4SEd Tanous { 464055713e4SEd Tanous messages::internalError(asyncResp->res); 465055713e4SEd Tanous return; 466055713e4SEd Tanous } 467055713e4SEd Tanous } 468055713e4SEd Tanous #endif 469055713e4SEd Tanous 470055713e4SEd Tanous BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum, 471055713e4SEd Tanous endSeqNum); 472055713e4SEd Tanous 473055713e4SEd Tanous // Add 1 to account for the last entry 474055713e4SEd Tanous uint64_t totalEntries = endSeqNum - startSeqNum + 1; 475055713e4SEd Tanous asyncResp->res.jsonValue["Members@odata.count"] = totalEntries; 476055713e4SEd Tanous if (skip + top < totalEntries) 477b0983db2SEd Tanous { 47884177a2fSEd Tanous asyncResp->res.jsonValue["Members@odata.nextLink"] = 47984177a2fSEd Tanous boost::urls::format( 480b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 481b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 482b0983db2SEd Tanous } 483055713e4SEd Tanous uint64_t index = 0; 484055713e4SEd Tanous sd_id128_t curBootID{}; 485055713e4SEd Tanous uint64_t curTs = 0; 486055713e4SEd Tanous if (skip > 0) 487055713e4SEd Tanous { 488055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), skip) < 0) 489055713e4SEd Tanous { 490055713e4SEd Tanous messages::internalError(asyncResp->res); 491055713e4SEd Tanous return; 492055713e4SEd Tanous } 493055713e4SEd Tanous 494055713e4SEd Tanous // Get the entry timestamp 495055713e4SEd Tanous ret = sd_journal_get_monotonic_usec(journal.get(), &curTs, &curBootID); 496055713e4SEd Tanous if (ret < 0) 497055713e4SEd Tanous { 498055713e4SEd Tanous BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", 499055713e4SEd Tanous strerror(-ret)); 500055713e4SEd Tanous messages::internalError(asyncResp->res); 501055713e4SEd Tanous return; 502055713e4SEd Tanous } 503055713e4SEd Tanous 504055713e4SEd Tanous uint64_t endChunkSeqNum = 0; 505055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 506055713e4SEd Tanous { 507055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endChunkSeqNum, nullptr) < 508055713e4SEd Tanous 0) 509055713e4SEd Tanous { 510055713e4SEd Tanous messages::internalError(asyncResp->res); 511055713e4SEd Tanous return; 512055713e4SEd Tanous } 513055713e4SEd Tanous } 514055713e4SEd Tanous #endif 515055713e4SEd Tanous 516055713e4SEd Tanous // Seek to the first entry with the same timestamp and boot 517055713e4SEd Tanous ret = sd_journal_seek_monotonic_usec(journal.get(), curBootID, curTs); 518055713e4SEd Tanous if (ret < 0) 519055713e4SEd Tanous { 520055713e4SEd Tanous BMCWEB_LOG_ERROR("Failed to seek: {}", strerror(-ret)); 521055713e4SEd Tanous messages::internalError(asyncResp->res); 522055713e4SEd Tanous return; 523055713e4SEd Tanous } 524055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 525055713e4SEd Tanous { 526055713e4SEd Tanous messages::internalError(asyncResp->res); 527055713e4SEd Tanous return; 528055713e4SEd Tanous } 529055713e4SEd Tanous uint64_t startChunkSeqNum = 0; 530055713e4SEd Tanous #if SYSTEMD_VERSION >= 254 531055713e4SEd Tanous { 532055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startChunkSeqNum, 533055713e4SEd Tanous nullptr) < 0) 534055713e4SEd Tanous { 535055713e4SEd Tanous messages::internalError(asyncResp->res); 536055713e4SEd Tanous return; 537055713e4SEd Tanous } 538055713e4SEd Tanous } 539055713e4SEd Tanous #endif 540055713e4SEd Tanous 541055713e4SEd Tanous // Get the difference between the start and end. Most of the time this 542055713e4SEd Tanous // will be 0 543055713e4SEd Tanous BMCWEB_LOG_DEBUG("start={} end={}", startChunkSeqNum, endChunkSeqNum); 544055713e4SEd Tanous index = endChunkSeqNum - startChunkSeqNum; 545055713e4SEd Tanous if (index > endChunkSeqNum) 546055713e4SEd Tanous { 547055713e4SEd Tanous // Detect underflows. Should never happen. 548055713e4SEd Tanous messages::internalError(asyncResp->res); 549055713e4SEd Tanous return; 550055713e4SEd Tanous } 551055713e4SEd Tanous if (index > 0) 552055713e4SEd Tanous { 553055713e4SEd Tanous BMCWEB_LOG_DEBUG("index = {}", index); 554055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), index) < 0) 555055713e4SEd Tanous { 556055713e4SEd Tanous messages::internalError(asyncResp->res); 557055713e4SEd Tanous return; 558055713e4SEd Tanous } 559055713e4SEd Tanous } 560055713e4SEd Tanous } 561055713e4SEd Tanous // If this is the first entry of this series, reset the timestamps so the 562055713e4SEd Tanous // Index doesn't increment 563055713e4SEd Tanous if (index == 0) 564055713e4SEd Tanous { 565055713e4SEd Tanous curBootID = {}; 566055713e4SEd Tanous curTs = 0; 567055713e4SEd Tanous } 568055713e4SEd Tanous else 569055713e4SEd Tanous { 570055713e4SEd Tanous index -= 1; 571055713e4SEd Tanous } 572055713e4SEd Tanous BMCWEB_LOG_DEBUG("Index was {}", index); 573055713e4SEd Tanous readJournalEntries(top, asyncResp, 574055713e4SEd Tanous {std::move(journal), index, curBootID, curTs}); 575b0983db2SEd Tanous } 576b0983db2SEd Tanous 57784177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet( 57884177a2fSEd Tanous App& app, const crow::Request& req, 579b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 58084177a2fSEd Tanous const std::string& managerId, const std::string& entryID) 58184177a2fSEd Tanous { 582b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 583b0983db2SEd Tanous { 584b0983db2SEd Tanous return; 585b0983db2SEd Tanous } 586b0983db2SEd Tanous 587b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 588b0983db2SEd Tanous { 589b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 590b0983db2SEd Tanous return; 591b0983db2SEd Tanous } 592b0983db2SEd Tanous 593b0983db2SEd Tanous // Convert the unique ID back to a timestamp to find the entry 594b0983db2SEd Tanous sd_id128_t bootID{}; 595b0983db2SEd Tanous uint64_t ts = 0; 596b0983db2SEd Tanous uint64_t index = 0; 597b0983db2SEd Tanous if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) 598b0983db2SEd Tanous { 599b0983db2SEd Tanous return; 600b0983db2SEd Tanous } 601b0983db2SEd Tanous 602b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 603b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 604b0983db2SEd Tanous if (ret < 0) 605b0983db2SEd Tanous { 606b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 607b0983db2SEd Tanous messages::internalError(asyncResp->res); 608b0983db2SEd Tanous return; 609b0983db2SEd Tanous } 610b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 611b0983db2SEd Tanous journalTmp, sd_journal_close); 612b0983db2SEd Tanous journalTmp = nullptr; 613b0983db2SEd Tanous // Go to the timestamp in the log and move to the entry at the 614b0983db2SEd Tanous // index tracking the unique ID 615b0983db2SEd Tanous ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); 616b0983db2SEd Tanous if (ret < 0) 617b0983db2SEd Tanous { 618b0983db2SEd Tanous BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", 619b0983db2SEd Tanous strerror(-ret)); 620b0983db2SEd Tanous messages::internalError(asyncResp->res); 621b0983db2SEd Tanous return; 622b0983db2SEd Tanous } 623055713e4SEd Tanous 624055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), index + 1) < 0) 625b0983db2SEd Tanous { 626b0983db2SEd Tanous messages::internalError(asyncResp->res); 627b0983db2SEd Tanous return; 628b0983db2SEd Tanous } 629055713e4SEd Tanous 630055713e4SEd Tanous std::string idStr = getUniqueEntryID(index, ts, bootID); 631b0983db2SEd Tanous 632b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 633055713e4SEd Tanous if (!fillBMCJournalLogEntryJson(entryID, journal.get(), bmcJournalLogEntry)) 634b0983db2SEd Tanous { 635b0983db2SEd Tanous messages::internalError(asyncResp->res); 636b0983db2SEd Tanous return; 637b0983db2SEd Tanous } 638b0983db2SEd Tanous asyncResp->res.jsonValue.update(bmcJournalLogEntry); 639055713e4SEd Tanous } 64084177a2fSEd Tanous 64184177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app) 64284177a2fSEd Tanous { 64384177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 64484177a2fSEd Tanous .privileges(redfish::privileges::getLogService) 64584177a2fSEd Tanous .methods(boost::beast::http::verb::get)( 64684177a2fSEd Tanous std::bind_front(handleManagersLogServiceJournalGet, std::ref(app))); 64784177a2fSEd Tanous 64884177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 64984177a2fSEd Tanous .privileges(redfish::privileges::getLogEntryCollection) 65084177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 65184177a2fSEd Tanous handleManagersJournalLogEntryCollectionGet, std::ref(app))); 65284177a2fSEd Tanous 65384177a2fSEd Tanous BMCWEB_ROUTE( 65484177a2fSEd Tanous app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 65584177a2fSEd Tanous .privileges(redfish::privileges::getLogEntry) 65684177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 65784177a2fSEd Tanous handleManagersJournalEntriesLogEntryGet, std::ref(app))); 658b0983db2SEd Tanous } 659b0983db2SEd Tanous } // namespace redfish 660