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