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