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
getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view entryIDStrView,sd_id128_t & bootID,uint64_t & timestamp,uint64_t & index)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);
75bd79bce8SPatrick Williams auto [ptr, indexEc] =
76bd79bce8SPatrick 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
getUniqueEntryID(uint64_t index,uint64_t curTs,sd_id128_t & curBootID)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
getJournalMetadata(sd_journal * journal,std::string_view field,std::string_view & contents)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
getJournalMetadataInt(sd_journal * journal,std::string_view field,const int & base,long int & contents)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
getEntryTimestamp(sd_journal * journal,std::string & entryTimestamp)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
fillBMCJournalLogEntryJson(const std::string & bmcJournalLogEntryID,sd_journal * journal,nlohmann::json::object_t & bmcJournalLogEntryJson)159bd79bce8SPatrick Williams inline bool fillBMCJournalLogEntryJson(
160bd79bce8SPatrick 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
handleManagersLogServiceJournalGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)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
readJournalEntries(uint64_t topEntryCount,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,JournalReadState && readState)270bd79bce8SPatrick Williams inline void readJournalEntries(
271bd79bce8SPatrick 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
handleManagersJournalLogEntryCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)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;
432*058f54edSPatrick Williams #if LIBSYSTEMD_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;
458*058f54edSPatrick Williams #if LIBSYSTEMD_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;
503*058f54edSPatrick Williams #if LIBSYSTEMD_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;
528*058f54edSPatrick Williams #if LIBSYSTEMD_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
handleManagersJournalEntriesLogEntryGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & entryID)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
requestRoutesBMCJournalLogService(App & app)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