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     }
82*055713e4SEd Tanous     if (index <= 1)
83*055713e4SEd Tanous     {
84*055713e4SEd Tanous         // Indexes go directly from no postfix (handled above) to _2
85*055713e4SEd Tanous         // so if we ever see _0 or _1, it's incorrect
86*055713e4SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView);
87*055713e4SEd Tanous         return false;
88*055713e4SEd Tanous     }
89*055713e4SEd Tanous 
90*055713e4SEd Tanous     // URI indexes are one based, journald is zero based
91*055713e4SEd Tanous     index -= 1;
92b0983db2SEd Tanous     return true;
93b0983db2SEd Tanous }
94b0983db2SEd Tanous 
95*055713e4SEd Tanous inline std::string getUniqueEntryID(uint64_t index, uint64_t curTs,
96*055713e4SEd 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());
101*055713e4SEd Tanous     std::string postfix;
102b0983db2SEd Tanous     if (index > 0)
103b0983db2SEd Tanous     {
104*055713e4SEd Tanous         postfix = std::format("_{}", index + 1);
105b0983db2SEd Tanous     }
106*055713e4SEd 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, &timestamp);
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*055713e4SEd 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;
167*055713e4SEd 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));
183*055713e4SEd 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     {
199*055713e4SEd 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);
210b0983db2SEd Tanous     bmcJournalLogEntryJson["EntryType"] = "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);
224*055713e4SEd 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 
263*055713e4SEd Tanous struct JournalReadState
264*055713e4SEd Tanous {
265*055713e4SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal;
266*055713e4SEd Tanous     uint64_t index = 0;
267*055713e4SEd Tanous     sd_id128_t prevBootID{};
268*055713e4SEd Tanous     uint64_t prevTs = 0;
269*055713e4SEd Tanous };
270*055713e4SEd Tanous 
271*055713e4SEd Tanous inline void
272*055713e4SEd Tanous     readJournalEntries(uint64_t topEntryCount,
273*055713e4SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
274*055713e4SEd Tanous                        JournalReadState&& readState)
275*055713e4SEd Tanous {
276*055713e4SEd Tanous     nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"];
277*055713e4SEd Tanous     nlohmann::json::array_t* logEntryArray =
278*055713e4SEd Tanous         logEntry.get_ptr<nlohmann::json::array_t*>();
279*055713e4SEd Tanous     if (logEntryArray == nullptr)
280*055713e4SEd Tanous     {
281*055713e4SEd Tanous         messages::internalError(asyncResp->res);
282*055713e4SEd Tanous         return;
283*055713e4SEd Tanous     }
284*055713e4SEd Tanous 
285*055713e4SEd Tanous     // The Journal APIs unfortunately do blocking calls to the filesystem, and
286*055713e4SEd Tanous     // can be somewhat expensive.  Short of creating our own io_uring based
287*055713e4SEd Tanous     // implementation of sd-journal, which would be difficult, the best thing we
288*055713e4SEd Tanous     // can do is to only parse a certain number of entries at a time.  The
289*055713e4SEd Tanous     // current chunk size is selected arbitrarily to ensure that we're not
290*055713e4SEd Tanous     // trying to process thousands of entries at the same time.
291*055713e4SEd Tanous     // The implementation will process the number of entries, then return
292*055713e4SEd Tanous     // control to the io_context to let other operations continue.
293*055713e4SEd Tanous     size_t segmentCountRemaining = 10;
294*055713e4SEd Tanous 
295*055713e4SEd Tanous     // Reset the unique ID on the first entry
296*055713e4SEd Tanous     for (uint64_t entryCount = logEntryArray->size();
297*055713e4SEd Tanous          entryCount < topEntryCount; entryCount++)
298*055713e4SEd Tanous     {
299*055713e4SEd Tanous         if (segmentCountRemaining == 0)
300*055713e4SEd Tanous         {
301*055713e4SEd Tanous             boost::asio::post(crow::connections::systemBus->get_io_context(),
302*055713e4SEd Tanous                               [asyncResp, topEntryCount,
303*055713e4SEd Tanous                                readState = std::move(readState)]() mutable {
304*055713e4SEd Tanous                 readJournalEntries(topEntryCount, asyncResp,
305*055713e4SEd Tanous                                    std::move(readState));
306*055713e4SEd Tanous             });
307*055713e4SEd Tanous             return;
308*055713e4SEd Tanous         }
309*055713e4SEd Tanous 
310*055713e4SEd Tanous         // Get the entry timestamp
311*055713e4SEd Tanous         sd_id128_t curBootID{};
312*055713e4SEd Tanous         uint64_t curTs = 0;
313*055713e4SEd Tanous         int ret = sd_journal_get_monotonic_usec(readState.journal.get(), &curTs,
314*055713e4SEd Tanous                                                 &curBootID);
315*055713e4SEd Tanous         if (ret < 0)
316*055713e4SEd Tanous         {
317*055713e4SEd Tanous             BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}",
318*055713e4SEd Tanous                              strerror(-ret));
319*055713e4SEd Tanous             messages::internalError(asyncResp->res);
320*055713e4SEd Tanous             return;
321*055713e4SEd Tanous         }
322*055713e4SEd Tanous 
323*055713e4SEd Tanous         // If the timestamp isn't unique on the same boot, increment the index
324*055713e4SEd Tanous         bool sameBootIDs = sd_id128_equal(curBootID, readState.prevBootID) != 0;
325*055713e4SEd Tanous         if (sameBootIDs && (curTs == readState.prevTs))
326*055713e4SEd Tanous         {
327*055713e4SEd Tanous             readState.index++;
328*055713e4SEd Tanous         }
329*055713e4SEd Tanous         else
330*055713e4SEd Tanous         {
331*055713e4SEd Tanous             // Otherwise, reset it
332*055713e4SEd Tanous             readState.index = 0;
333*055713e4SEd Tanous         }
334*055713e4SEd Tanous 
335*055713e4SEd Tanous         // Save the bootID
336*055713e4SEd Tanous         readState.prevBootID = curBootID;
337*055713e4SEd Tanous 
338*055713e4SEd Tanous         // Save the timestamp
339*055713e4SEd Tanous         readState.prevTs = curTs;
340*055713e4SEd Tanous 
341*055713e4SEd Tanous         std::string idStr = getUniqueEntryID(readState.index, curTs, curBootID);
342*055713e4SEd Tanous 
343*055713e4SEd Tanous         nlohmann::json::object_t bmcJournalLogEntry;
344*055713e4SEd Tanous         if (!fillBMCJournalLogEntryJson(idStr, readState.journal.get(),
345*055713e4SEd Tanous                                         bmcJournalLogEntry))
346*055713e4SEd Tanous         {
347*055713e4SEd Tanous             messages::internalError(asyncResp->res);
348*055713e4SEd Tanous             return;
349*055713e4SEd Tanous         }
350*055713e4SEd Tanous         logEntryArray->emplace_back(std::move(bmcJournalLogEntry));
351*055713e4SEd Tanous 
352*055713e4SEd Tanous         ret = sd_journal_next(readState.journal.get());
353*055713e4SEd Tanous         if (ret < 0)
354*055713e4SEd Tanous         {
355*055713e4SEd Tanous             messages::internalError(asyncResp->res);
356*055713e4SEd Tanous             return;
357*055713e4SEd Tanous         }
358*055713e4SEd Tanous         if (ret == 0)
359*055713e4SEd Tanous         {
360*055713e4SEd Tanous             break;
361*055713e4SEd Tanous         }
362*055713e4SEd Tanous         segmentCountRemaining--;
363*055713e4SEd Tanous     }
364*055713e4SEd Tanous }
365*055713e4SEd 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";
401*055713e4SEd 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     }
413*055713e4SEd 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 
418*055713e4SEd Tanous     // Seek to the end
419*055713e4SEd Tanous     if (sd_journal_seek_tail(journal.get()) < 0)
420b0983db2SEd Tanous     {
421b0983db2SEd Tanous         messages::internalError(asyncResp->res);
422b0983db2SEd Tanous         return;
423b0983db2SEd Tanous     }
424*055713e4SEd Tanous 
425*055713e4SEd Tanous     // Get the last entry
426*055713e4SEd Tanous     if (sd_journal_previous(journal.get()) < 0)
427*055713e4SEd Tanous     {
428*055713e4SEd Tanous         messages::internalError(asyncResp->res);
429*055713e4SEd Tanous         return;
430b0983db2SEd Tanous     }
431*055713e4SEd Tanous 
432*055713e4SEd Tanous     // Get the last sequence number
433*055713e4SEd Tanous     uint64_t endSeqNum = 0;
434*055713e4SEd Tanous #if SYSTEMD_VERSION >= 254
435*055713e4SEd Tanous     {
436*055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0)
437*055713e4SEd Tanous         {
438*055713e4SEd Tanous             messages::internalError(asyncResp->res);
439*055713e4SEd Tanous             return;
440*055713e4SEd Tanous         }
441*055713e4SEd Tanous     }
442*055713e4SEd Tanous #endif
443*055713e4SEd Tanous 
444*055713e4SEd Tanous     // Seek to the beginning
445*055713e4SEd Tanous     if (sd_journal_seek_head(journal.get()) < 0)
446*055713e4SEd Tanous     {
447*055713e4SEd Tanous         messages::internalError(asyncResp->res);
448*055713e4SEd Tanous         return;
449*055713e4SEd Tanous     }
450*055713e4SEd Tanous 
451*055713e4SEd Tanous     // Get the first entry
452*055713e4SEd Tanous     if (sd_journal_next(journal.get()) < 0)
453*055713e4SEd Tanous     {
454*055713e4SEd Tanous         messages::internalError(asyncResp->res);
455*055713e4SEd Tanous         return;
456*055713e4SEd Tanous     }
457*055713e4SEd Tanous 
458*055713e4SEd Tanous     // Get the first sequence number
459*055713e4SEd Tanous     uint64_t startSeqNum = 0;
460*055713e4SEd Tanous #if SYSTEMD_VERSION >= 254
461*055713e4SEd Tanous     {
462*055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0)
463*055713e4SEd Tanous         {
464*055713e4SEd Tanous             messages::internalError(asyncResp->res);
465*055713e4SEd Tanous             return;
466*055713e4SEd Tanous         }
467*055713e4SEd Tanous     }
468*055713e4SEd Tanous #endif
469*055713e4SEd Tanous 
470*055713e4SEd Tanous     BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum,
471*055713e4SEd Tanous                      endSeqNum);
472*055713e4SEd Tanous 
473*055713e4SEd Tanous     // Add 1 to account for the last entry
474*055713e4SEd Tanous     uint64_t totalEntries = endSeqNum - startSeqNum + 1;
475*055713e4SEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = totalEntries;
476*055713e4SEd 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     }
483*055713e4SEd Tanous     uint64_t index = 0;
484*055713e4SEd Tanous     sd_id128_t curBootID{};
485*055713e4SEd Tanous     uint64_t curTs = 0;
486*055713e4SEd Tanous     if (skip > 0)
487*055713e4SEd Tanous     {
488*055713e4SEd Tanous         if (sd_journal_next_skip(journal.get(), skip) < 0)
489*055713e4SEd Tanous         {
490*055713e4SEd Tanous             messages::internalError(asyncResp->res);
491*055713e4SEd Tanous             return;
492*055713e4SEd Tanous         }
493*055713e4SEd Tanous 
494*055713e4SEd Tanous         // Get the entry timestamp
495*055713e4SEd Tanous         ret = sd_journal_get_monotonic_usec(journal.get(), &curTs, &curBootID);
496*055713e4SEd Tanous         if (ret < 0)
497*055713e4SEd Tanous         {
498*055713e4SEd Tanous             BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}",
499*055713e4SEd Tanous                              strerror(-ret));
500*055713e4SEd Tanous             messages::internalError(asyncResp->res);
501*055713e4SEd Tanous             return;
502*055713e4SEd Tanous         }
503*055713e4SEd Tanous 
504*055713e4SEd Tanous         uint64_t endChunkSeqNum = 0;
505*055713e4SEd Tanous #if SYSTEMD_VERSION >= 254
506*055713e4SEd Tanous         {
507*055713e4SEd Tanous             if (sd_journal_get_seqnum(journal.get(), &endChunkSeqNum, nullptr) <
508*055713e4SEd Tanous                 0)
509*055713e4SEd Tanous             {
510*055713e4SEd Tanous                 messages::internalError(asyncResp->res);
511*055713e4SEd Tanous                 return;
512*055713e4SEd Tanous             }
513*055713e4SEd Tanous         }
514*055713e4SEd Tanous #endif
515*055713e4SEd Tanous 
516*055713e4SEd Tanous         // Seek to the first entry with the same timestamp and boot
517*055713e4SEd Tanous         ret = sd_journal_seek_monotonic_usec(journal.get(), curBootID, curTs);
518*055713e4SEd Tanous         if (ret < 0)
519*055713e4SEd Tanous         {
520*055713e4SEd Tanous             BMCWEB_LOG_ERROR("Failed to seek: {}", strerror(-ret));
521*055713e4SEd Tanous             messages::internalError(asyncResp->res);
522*055713e4SEd Tanous             return;
523*055713e4SEd Tanous         }
524*055713e4SEd Tanous         if (sd_journal_next(journal.get()) < 0)
525*055713e4SEd Tanous         {
526*055713e4SEd Tanous             messages::internalError(asyncResp->res);
527*055713e4SEd Tanous             return;
528*055713e4SEd Tanous         }
529*055713e4SEd Tanous         uint64_t startChunkSeqNum = 0;
530*055713e4SEd Tanous #if SYSTEMD_VERSION >= 254
531*055713e4SEd Tanous         {
532*055713e4SEd Tanous             if (sd_journal_get_seqnum(journal.get(), &startChunkSeqNum,
533*055713e4SEd Tanous                                       nullptr) < 0)
534*055713e4SEd Tanous             {
535*055713e4SEd Tanous                 messages::internalError(asyncResp->res);
536*055713e4SEd Tanous                 return;
537*055713e4SEd Tanous             }
538*055713e4SEd Tanous         }
539*055713e4SEd Tanous #endif
540*055713e4SEd Tanous 
541*055713e4SEd Tanous         // Get the difference between the start and end.  Most of the time this
542*055713e4SEd Tanous         // will be 0
543*055713e4SEd Tanous         BMCWEB_LOG_DEBUG("start={} end={}", startChunkSeqNum, endChunkSeqNum);
544*055713e4SEd Tanous         index = endChunkSeqNum - startChunkSeqNum;
545*055713e4SEd Tanous         if (index > endChunkSeqNum)
546*055713e4SEd Tanous         {
547*055713e4SEd Tanous             // Detect underflows.  Should never happen.
548*055713e4SEd Tanous             messages::internalError(asyncResp->res);
549*055713e4SEd Tanous             return;
550*055713e4SEd Tanous         }
551*055713e4SEd Tanous         if (index > 0)
552*055713e4SEd Tanous         {
553*055713e4SEd Tanous             BMCWEB_LOG_DEBUG("index = {}", index);
554*055713e4SEd Tanous             if (sd_journal_next_skip(journal.get(), index) < 0)
555*055713e4SEd Tanous             {
556*055713e4SEd Tanous                 messages::internalError(asyncResp->res);
557*055713e4SEd Tanous                 return;
558*055713e4SEd Tanous             }
559*055713e4SEd Tanous         }
560*055713e4SEd Tanous     }
561*055713e4SEd Tanous     // If this is the first entry of this series, reset the timestamps so the
562*055713e4SEd Tanous     // Index doesn't increment
563*055713e4SEd Tanous     if (index == 0)
564*055713e4SEd Tanous     {
565*055713e4SEd Tanous         curBootID = {};
566*055713e4SEd Tanous         curTs = 0;
567*055713e4SEd Tanous     }
568*055713e4SEd Tanous     else
569*055713e4SEd Tanous     {
570*055713e4SEd Tanous         index -= 1;
571*055713e4SEd Tanous     }
572*055713e4SEd Tanous     BMCWEB_LOG_DEBUG("Index was {}", index);
573*055713e4SEd Tanous     readJournalEntries(top, asyncResp,
574*055713e4SEd 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     }
623*055713e4SEd Tanous 
624*055713e4SEd Tanous     if (sd_journal_next_skip(journal.get(), index + 1) < 0)
625b0983db2SEd Tanous     {
626b0983db2SEd Tanous         messages::internalError(asyncResp->res);
627b0983db2SEd Tanous         return;
628b0983db2SEd Tanous     }
629*055713e4SEd Tanous 
630*055713e4SEd Tanous     std::string idStr = getUniqueEntryID(index, ts, bootID);
631b0983db2SEd Tanous 
632b0983db2SEd Tanous     nlohmann::json::object_t bmcJournalLogEntry;
633*055713e4SEd 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);
639*055713e4SEd 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