xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision e85d6b167dcce7ea0c4adee5ea7113f7b6135319)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 #include "registries.hpp"
20 #include "registries/base_message_registry.hpp"
21 #include "registries/openbmc_message_registry.hpp"
22 
23 #include <systemd/sd-journal.h>
24 
25 #include <boost/algorithm/string/split.hpp>
26 #include <boost/beast/core/span.hpp>
27 #include <boost/container/flat_map.hpp>
28 #include <error_messages.hpp>
29 #include <filesystem>
30 #include <string_view>
31 #include <variant>
32 
33 namespace redfish
34 {
35 
36 constexpr char const *CrashdumpObject = "com.intel.crashdump";
37 constexpr char const *CrashdumpPath = "/com/intel/crashdump";
38 constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand";
39 constexpr char const *CrashdumpInterface = "com.intel.crashdump";
40 constexpr char const *CrashdumpOnDemandInterface =
41     "com.intel.crashdump.OnDemand";
42 constexpr char const *CrashdumpRawPECIInterface =
43     "com.intel.crashdump.SendRawPeci";
44 
45 namespace message_registries
46 {
47 static const Message *getMessageFromRegistry(
48     const std::string &messageKey,
49     const boost::beast::span<const MessageEntry> registry)
50 {
51     boost::beast::span<const MessageEntry>::const_iterator messageIt =
52         std::find_if(registry.cbegin(), registry.cend(),
53                      [&messageKey](const MessageEntry &messageEntry) {
54                          return !std::strcmp(messageEntry.first,
55                                              messageKey.c_str());
56                      });
57     if (messageIt != registry.cend())
58     {
59         return &messageIt->second;
60     }
61 
62     return nullptr;
63 }
64 
65 static const Message *getMessage(const std::string_view &messageID)
66 {
67     // Redfish MessageIds are in the form
68     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
69     // the right Message
70     std::vector<std::string> fields;
71     fields.reserve(4);
72     boost::split(fields, messageID, boost::is_any_of("."));
73     std::string &registryName = fields[0];
74     std::string &messageKey = fields[3];
75 
76     // Find the right registry and check it for the MessageKey
77     if (std::string(base::header.registryPrefix) == registryName)
78     {
79         return getMessageFromRegistry(
80             messageKey, boost::beast::span<const MessageEntry>(base::registry));
81     }
82     if (std::string(openbmc::header.registryPrefix) == registryName)
83     {
84         return getMessageFromRegistry(
85             messageKey,
86             boost::beast::span<const MessageEntry>(openbmc::registry));
87     }
88     return nullptr;
89 }
90 } // namespace message_registries
91 
92 namespace fs = std::filesystem;
93 
94 using GetManagedPropertyType = boost::container::flat_map<
95     std::string,
96     sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
97                                 int32_t, uint32_t, int64_t, uint64_t, double>>;
98 
99 using GetManagedObjectsType = boost::container::flat_map<
100     sdbusplus::message::object_path,
101     boost::container::flat_map<std::string, GetManagedPropertyType>>;
102 
103 inline std::string translateSeverityDbusToRedfish(const std::string &s)
104 {
105     if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
106     {
107         return "Critical";
108     }
109     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
110     {
111         return "Critical";
112     }
113     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
114     {
115         return "OK";
116     }
117     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
118     {
119         return "Critical";
120     }
121     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
122     {
123         return "Critical";
124     }
125     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
126     {
127         return "OK";
128     }
129     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
130     {
131         return "OK";
132     }
133     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
134     {
135         return "Warning";
136     }
137     return "";
138 }
139 
140 static int getJournalMetadata(sd_journal *journal,
141                               const std::string_view &field,
142                               std::string_view &contents)
143 {
144     const char *data = nullptr;
145     size_t length = 0;
146     int ret = 0;
147     // Get the metadata from the requested field of the journal entry
148     ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
149                               &length);
150     if (ret < 0)
151     {
152         return ret;
153     }
154     contents = std::string_view(data, length);
155     // Only use the content after the "=" character.
156     contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
157     return ret;
158 }
159 
160 static int getJournalMetadata(sd_journal *journal,
161                               const std::string_view &field, const int &base,
162                               int &contents)
163 {
164     int ret = 0;
165     std::string_view metadata;
166     // Get the metadata from the requested field of the journal entry
167     ret = getJournalMetadata(journal, field, metadata);
168     if (ret < 0)
169     {
170         return ret;
171     }
172     contents = strtol(metadata.data(), nullptr, base);
173     return ret;
174 }
175 
176 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
177 {
178     int ret = 0;
179     uint64_t timestamp = 0;
180     ret = sd_journal_get_realtime_usec(journal, &timestamp);
181     if (ret < 0)
182     {
183         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
184                          << strerror(-ret);
185         return false;
186     }
187     time_t t =
188         static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
189     struct tm *loctime = localtime(&t);
190     char entryTime[64] = {};
191     if (NULL != loctime)
192     {
193         strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
194     }
195     // Insert the ':' into the timezone
196     std::string_view t1(entryTime);
197     std::string_view t2(entryTime);
198     if (t1.size() > 2 && t2.size() > 2)
199     {
200         t1.remove_suffix(2);
201         t2.remove_prefix(t2.size() - 2);
202     }
203     entryTimestamp = std::string(t1) + ":" + std::string(t2);
204     return true;
205 }
206 
207 static bool getSkipParam(crow::Response &res, const crow::Request &req,
208                          long &skip)
209 {
210     char *skipParam = req.urlParams.get("$skip");
211     if (skipParam != nullptr)
212     {
213         char *ptr = nullptr;
214         skip = std::strtol(skipParam, &ptr, 10);
215         if (*skipParam == '\0' || *ptr != '\0')
216         {
217 
218             messages::queryParameterValueTypeError(res, std::string(skipParam),
219                                                    "$skip");
220             return false;
221         }
222         if (skip < 0)
223         {
224 
225             messages::queryParameterOutOfRange(res, std::to_string(skip),
226                                                "$skip", "greater than 0");
227             return false;
228         }
229     }
230     return true;
231 }
232 
233 static constexpr const long maxEntriesPerPage = 1000;
234 static bool getTopParam(crow::Response &res, const crow::Request &req,
235                         long &top)
236 {
237     char *topParam = req.urlParams.get("$top");
238     if (topParam != nullptr)
239     {
240         char *ptr = nullptr;
241         top = std::strtol(topParam, &ptr, 10);
242         if (*topParam == '\0' || *ptr != '\0')
243         {
244             messages::queryParameterValueTypeError(res, std::string(topParam),
245                                                    "$top");
246             return false;
247         }
248         if (top < 1 || top > maxEntriesPerPage)
249         {
250 
251             messages::queryParameterOutOfRange(
252                 res, std::to_string(top), "$top",
253                 "1-" + std::to_string(maxEntriesPerPage));
254             return false;
255         }
256     }
257     return true;
258 }
259 
260 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID,
261                              const bool firstEntry = true)
262 {
263     int ret = 0;
264     static uint64_t prevTs = 0;
265     static int index = 0;
266     if (firstEntry)
267     {
268         prevTs = 0;
269     }
270 
271     // Get the entry timestamp
272     uint64_t curTs = 0;
273     ret = sd_journal_get_realtime_usec(journal, &curTs);
274     if (ret < 0)
275     {
276         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
277                          << strerror(-ret);
278         return false;
279     }
280     // If the timestamp isn't unique, increment the index
281     if (curTs == prevTs)
282     {
283         index++;
284     }
285     else
286     {
287         // Otherwise, reset it
288         index = 0;
289     }
290     // Save the timestamp
291     prevTs = curTs;
292 
293     entryID = std::to_string(curTs);
294     if (index > 0)
295     {
296         entryID += "_" + std::to_string(index);
297     }
298     return true;
299 }
300 
301 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID,
302                              const bool firstEntry = true)
303 {
304     static uint64_t prevTs = 0;
305     static int index = 0;
306     if (firstEntry)
307     {
308         prevTs = 0;
309     }
310 
311     // Get the entry timestamp
312     uint64_t curTs = 0;
313     std::tm timeStruct = {};
314     std::istringstream entryStream(logEntry);
315     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
316     {
317         curTs = std::mktime(&timeStruct);
318     }
319     // If the timestamp isn't unique, increment the index
320     if (curTs == prevTs)
321     {
322         index++;
323     }
324     else
325     {
326         // Otherwise, reset it
327         index = 0;
328     }
329     // Save the timestamp
330     prevTs = curTs;
331 
332     entryID = std::to_string(curTs);
333     if (index > 0)
334     {
335         entryID += "_" + std::to_string(index);
336     }
337     return true;
338 }
339 
340 static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
341                                uint64_t &timestamp, uint16_t &index)
342 {
343     if (entryID.empty())
344     {
345         return false;
346     }
347     // Convert the unique ID back to a timestamp to find the entry
348     std::string_view tsStr(entryID);
349 
350     auto underscorePos = tsStr.find("_");
351     if (underscorePos != tsStr.npos)
352     {
353         // Timestamp has an index
354         tsStr.remove_suffix(tsStr.size() - underscorePos);
355         std::string_view indexStr(entryID);
356         indexStr.remove_prefix(underscorePos + 1);
357         std::size_t pos;
358         try
359         {
360             index = std::stoul(std::string(indexStr), &pos);
361         }
362         catch (std::invalid_argument)
363         {
364             messages::resourceMissingAtURI(res, entryID);
365             return false;
366         }
367         catch (std::out_of_range)
368         {
369             messages::resourceMissingAtURI(res, entryID);
370             return false;
371         }
372         if (pos != indexStr.size())
373         {
374             messages::resourceMissingAtURI(res, entryID);
375             return false;
376         }
377     }
378     // Timestamp has no index
379     std::size_t pos;
380     try
381     {
382         timestamp = std::stoull(std::string(tsStr), &pos);
383     }
384     catch (std::invalid_argument)
385     {
386         messages::resourceMissingAtURI(res, entryID);
387         return false;
388     }
389     catch (std::out_of_range)
390     {
391         messages::resourceMissingAtURI(res, entryID);
392         return false;
393     }
394     if (pos != tsStr.size())
395     {
396         messages::resourceMissingAtURI(res, entryID);
397         return false;
398     }
399     return true;
400 }
401 
402 static bool
403     getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
404 {
405     static const std::filesystem::path redfishLogDir = "/var/log";
406     static const std::string redfishLogFilename = "redfish";
407 
408     // Loop through the directory looking for redfish log files
409     for (const std::filesystem::directory_entry &dirEnt :
410          std::filesystem::directory_iterator(redfishLogDir))
411     {
412         // If we find a redfish log file, save the path
413         std::string filename = dirEnt.path().filename();
414         if (boost::starts_with(filename, redfishLogFilename))
415         {
416             redfishLogFiles.emplace_back(redfishLogDir / filename);
417         }
418     }
419     // As the log files rotate, they are appended with a ".#" that is higher for
420     // the older logs. Since we don't expect more than 10 log files, we
421     // can just sort the list to get them in order from newest to oldest
422     std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
423 
424     return !redfishLogFiles.empty();
425 }
426 
427 class SystemLogServiceCollection : public Node
428 {
429   public:
430     template <typename CrowApp>
431     SystemLogServiceCollection(CrowApp &app) :
432         Node(app, "/redfish/v1/Systems/system/LogServices/")
433     {
434         entityPrivileges = {
435             {boost::beast::http::verb::get, {{"Login"}}},
436             {boost::beast::http::verb::head, {{"Login"}}},
437             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
438             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
439             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
440             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
441     }
442 
443   private:
444     /**
445      * Functions triggers appropriate requests on DBus
446      */
447     void doGet(crow::Response &res, const crow::Request &req,
448                const std::vector<std::string> &params) override
449     {
450         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
451         // Collections don't include the static data added by SubRoute because
452         // it has a duplicate entry for members
453         asyncResp->res.jsonValue["@odata.type"] =
454             "#LogServiceCollection.LogServiceCollection";
455         asyncResp->res.jsonValue["@odata.context"] =
456             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
457         asyncResp->res.jsonValue["@odata.id"] =
458             "/redfish/v1/Systems/system/LogServices";
459         asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
460         asyncResp->res.jsonValue["Description"] =
461             "Collection of LogServices for this Computer System";
462         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
463         logServiceArray = nlohmann::json::array();
464         logServiceArray.push_back(
465             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
466 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
467         logServiceArray.push_back(
468             {{"@odata.id",
469               "/redfish/v1/Systems/system/LogServices/Crashdump"}});
470 #endif
471         asyncResp->res.jsonValue["Members@odata.count"] =
472             logServiceArray.size();
473     }
474 };
475 
476 class EventLogService : public Node
477 {
478   public:
479     template <typename CrowApp>
480     EventLogService(CrowApp &app) :
481         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
482     {
483         entityPrivileges = {
484             {boost::beast::http::verb::get, {{"Login"}}},
485             {boost::beast::http::verb::head, {{"Login"}}},
486             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
487             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
488             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
489             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
490     }
491 
492   private:
493     void doGet(crow::Response &res, const crow::Request &req,
494                const std::vector<std::string> &params) override
495     {
496         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
497 
498         asyncResp->res.jsonValue["@odata.id"] =
499             "/redfish/v1/Systems/system/LogServices/EventLog";
500         asyncResp->res.jsonValue["@odata.type"] =
501             "#LogService.v1_1_0.LogService";
502         asyncResp->res.jsonValue["@odata.context"] =
503             "/redfish/v1/$metadata#LogService.LogService";
504         asyncResp->res.jsonValue["Name"] = "Event Log Service";
505         asyncResp->res.jsonValue["Description"] = "System Event Log Service";
506         asyncResp->res.jsonValue["Id"] = "Event Log";
507         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
508         asyncResp->res.jsonValue["Entries"] = {
509             {"@odata.id",
510              "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
511         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
512 
513             {"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
514                        "Actions/LogService.ClearLog"}};
515     }
516 };
517 
518 class EventLogClear : public Node
519 {
520   public:
521     EventLogClear(CrowApp &app) :
522         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
523                   "LogService.ClearLog/")
524     {
525         entityPrivileges = {
526             {boost::beast::http::verb::get, {{"Login"}}},
527             {boost::beast::http::verb::head, {{"Login"}}},
528             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
529             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
530             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
531             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
532     }
533 
534   private:
535     void doPost(crow::Response &res, const crow::Request &req,
536                 const std::vector<std::string> &params) override
537     {
538         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
539 
540         // Clear the EventLog by deleting the log files
541         std::vector<std::filesystem::path> redfishLogFiles;
542         if (getRedfishLogFiles(redfishLogFiles))
543         {
544             for (const std::filesystem::path &file : redfishLogFiles)
545             {
546                 std::error_code ec;
547                 std::filesystem::remove(file, ec);
548             }
549         }
550 
551         // Reload rsyslog so it knows to start new log files
552         crow::connections::systemBus->async_method_call(
553             [asyncResp](const boost::system::error_code ec) {
554                 if (ec)
555                 {
556                     BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
557                     messages::internalError(asyncResp->res);
558                     return;
559                 }
560 
561                 messages::success(asyncResp->res);
562             },
563             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
564             "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
565             "replace");
566     }
567 };
568 
569 static int fillEventLogEntryJson(const std::string &logEntryID,
570                                  const std::string logEntry,
571                                  nlohmann::json &logEntryJson)
572 {
573     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
574     // First get the Timestamp
575     size_t space = logEntry.find_first_of(" ");
576     if (space == std::string::npos)
577     {
578         return 1;
579     }
580     std::string timestamp = logEntry.substr(0, space);
581     // Then get the log contents
582     size_t entryStart = logEntry.find_first_not_of(" ", space);
583     if (entryStart == std::string::npos)
584     {
585         return 1;
586     }
587     std::string_view entry(logEntry);
588     entry.remove_prefix(entryStart);
589     // Use split to separate the entry into its fields
590     std::vector<std::string> logEntryFields;
591     boost::split(logEntryFields, entry, boost::is_any_of(","),
592                  boost::token_compress_on);
593     // We need at least a MessageId to be valid
594     if (logEntryFields.size() < 1)
595     {
596         return 1;
597     }
598     std::string &messageID = logEntryFields[0];
599 
600     // Get the Message from the MessageRegistry
601     const message_registries::Message *message =
602         message_registries::getMessage(messageID);
603 
604     std::string msg;
605     std::string severity;
606     if (message != nullptr)
607     {
608         msg = message->message;
609         severity = message->severity;
610     }
611 
612     // Get the MessageArgs from the log if there are any
613     boost::beast::span<std::string> messageArgs;
614     if (logEntryFields.size() > 1)
615     {
616         std::string &messageArgsStart = logEntryFields[1];
617         // If the first string is empty, assume there are no MessageArgs
618         std::size_t messageArgsSize = 0;
619         if (!messageArgsStart.empty())
620         {
621             messageArgsSize = logEntryFields.size() - 1;
622         }
623 
624         messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
625 
626         // Fill the MessageArgs into the Message
627         int i = 0;
628         for (const std::string &messageArg : messageArgs)
629         {
630             std::string argStr = "%" + std::to_string(++i);
631             size_t argPos = msg.find(argStr);
632             if (argPos != std::string::npos)
633             {
634                 msg.replace(argPos, argStr.length(), messageArg);
635             }
636         }
637     }
638 
639     // Get the Created time from the timestamp. The log timestamp is in RFC3339
640     // format which matches the Redfish format except for the fractional seconds
641     // between the '.' and the '+', so just remove them.
642     std::size_t dot = timestamp.find_first_of(".");
643     std::size_t plus = timestamp.find_first_of("+");
644     if (dot != std::string::npos && plus != std::string::npos)
645     {
646         timestamp.erase(dot, plus - dot);
647     }
648 
649     // Fill in the log entry with the gathered data
650     logEntryJson = {
651         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
652         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
653         {"@odata.id",
654          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/#" +
655              logEntryID},
656         {"Name", "System Event Log Entry"},
657         {"Id", logEntryID},
658         {"Message", std::move(msg)},
659         {"MessageId", std::move(messageID)},
660         {"MessageArgs", std::move(messageArgs)},
661         {"EntryType", "Event"},
662         {"Severity", std::move(severity)},
663         {"Created", std::move(timestamp)}};
664     return 0;
665 }
666 
667 class JournalEventLogEntryCollection : public Node
668 {
669   public:
670     template <typename CrowApp>
671     JournalEventLogEntryCollection(CrowApp &app) :
672         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
673     {
674         entityPrivileges = {
675             {boost::beast::http::verb::get, {{"Login"}}},
676             {boost::beast::http::verb::head, {{"Login"}}},
677             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
678             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
679             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
680             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
681     }
682 
683   private:
684     void doGet(crow::Response &res, const crow::Request &req,
685                const std::vector<std::string> &params) override
686     {
687         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
688         long skip = 0;
689         long top = maxEntriesPerPage; // Show max entries by default
690         if (!getSkipParam(asyncResp->res, req, skip))
691         {
692             return;
693         }
694         if (!getTopParam(asyncResp->res, req, top))
695         {
696             return;
697         }
698         // Collections don't include the static data added by SubRoute because
699         // it has a duplicate entry for members
700         asyncResp->res.jsonValue["@odata.type"] =
701             "#LogEntryCollection.LogEntryCollection";
702         asyncResp->res.jsonValue["@odata.context"] =
703             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
704         asyncResp->res.jsonValue["@odata.id"] =
705             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
706         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
707         asyncResp->res.jsonValue["Description"] =
708             "Collection of System Event Log Entries";
709 
710         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
711         logEntryArray = nlohmann::json::array();
712         // Go through the log files and create a unique ID for each entry
713         std::vector<std::filesystem::path> redfishLogFiles;
714         getRedfishLogFiles(redfishLogFiles);
715         uint64_t entryCount = 0;
716         std::string logEntry;
717 
718         // Oldest logs are in the last file, so start there and loop backwards
719         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
720              it++)
721         {
722             std::ifstream logStream(*it);
723             if (!logStream.is_open())
724             {
725                 continue;
726             }
727 
728             // Reset the unique ID on the first entry
729             bool firstEntry = true;
730             while (std::getline(logStream, logEntry))
731             {
732                 entryCount++;
733                 // Handle paging using skip (number of entries to skip from the
734                 // start) and top (number of entries to display)
735                 if (entryCount <= skip || entryCount > skip + top)
736                 {
737                     continue;
738                 }
739 
740                 std::string idStr;
741                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
742                 {
743                     continue;
744                 }
745 
746                 if (firstEntry)
747                 {
748                     firstEntry = false;
749                 }
750 
751                 logEntryArray.push_back({});
752                 nlohmann::json &bmcLogEntry = logEntryArray.back();
753                 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
754                 {
755                     messages::internalError(asyncResp->res);
756                     return;
757                 }
758             }
759         }
760         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
761         if (skip + top < entryCount)
762         {
763             asyncResp->res.jsonValue["Members@odata.nextLink"] =
764                 "/redfish/v1/Systems/system/LogServices/EventLog/"
765                 "Entries?$skip=" +
766                 std::to_string(skip + top);
767         }
768     }
769 };
770 
771 class DBusEventLogEntryCollection : public Node
772 {
773   public:
774     template <typename CrowApp>
775     DBusEventLogEntryCollection(CrowApp &app) :
776         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
777     {
778         entityPrivileges = {
779             {boost::beast::http::verb::get, {{"Login"}}},
780             {boost::beast::http::verb::head, {{"Login"}}},
781             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
782             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
783             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
784             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
785     }
786 
787   private:
788     void doGet(crow::Response &res, const crow::Request &req,
789                const std::vector<std::string> &params) override
790     {
791         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
792 
793         // Collections don't include the static data added by SubRoute because
794         // it has a duplicate entry for members
795         asyncResp->res.jsonValue["@odata.type"] =
796             "#LogEntryCollection.LogEntryCollection";
797         asyncResp->res.jsonValue["@odata.context"] =
798             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
799         asyncResp->res.jsonValue["@odata.id"] =
800             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
801         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
802         asyncResp->res.jsonValue["Description"] =
803             "Collection of System Event Log Entries";
804 
805         // DBus implementation of EventLog/Entries
806         // Make call to Logging Service to find all log entry objects
807         crow::connections::systemBus->async_method_call(
808             [asyncResp](const boost::system::error_code ec,
809                         GetManagedObjectsType &resp) {
810                 if (ec)
811                 {
812                     // TODO Handle for specific error code
813                     BMCWEB_LOG_ERROR
814                         << "getLogEntriesIfaceData resp_handler got error "
815                         << ec;
816                     messages::internalError(asyncResp->res);
817                     return;
818                 }
819                 nlohmann::json &entriesArray =
820                     asyncResp->res.jsonValue["Members"];
821                 entriesArray = nlohmann::json::array();
822                 for (auto &objectPath : resp)
823                 {
824                     for (auto &interfaceMap : objectPath.second)
825                     {
826                         if (interfaceMap.first !=
827                             "xyz.openbmc_project.Logging.Entry")
828                         {
829                             BMCWEB_LOG_DEBUG << "Bailing early on "
830                                              << interfaceMap.first;
831                             continue;
832                         }
833                         entriesArray.push_back({});
834                         nlohmann::json &thisEntry = entriesArray.back();
835                         uint32_t *id;
836                         std::time_t timestamp;
837                         std::string *severity, *message;
838                         bool *resolved;
839                         for (auto &propertyMap : interfaceMap.second)
840                         {
841                             if (propertyMap.first == "Id")
842                             {
843                                 id = sdbusplus::message::variant_ns::get_if<
844                                     uint32_t>(&propertyMap.second);
845                                 if (id == nullptr)
846                                 {
847                                     messages::propertyMissing(asyncResp->res,
848                                                               "Id");
849                                 }
850                             }
851                             else if (propertyMap.first == "Timestamp")
852                             {
853                                 const uint64_t *millisTimeStamp =
854                                     std::get_if<uint64_t>(&propertyMap.second);
855                                 if (millisTimeStamp == nullptr)
856                                 {
857                                     messages::propertyMissing(asyncResp->res,
858                                                               "Timestamp");
859                                 }
860                                 // Retrieve Created property with format:
861                                 // yyyy-mm-ddThh:mm:ss
862                                 std::chrono::milliseconds chronoTimeStamp(
863                                     *millisTimeStamp);
864                                 timestamp =
865                                     std::chrono::duration_cast<
866                                         std::chrono::seconds>(chronoTimeStamp)
867                                         .count();
868                             }
869                             else if (propertyMap.first == "Severity")
870                             {
871                                 severity = std::get_if<std::string>(
872                                     &propertyMap.second);
873                                 if (severity == nullptr)
874                                 {
875                                     messages::propertyMissing(asyncResp->res,
876                                                               "Severity");
877                                 }
878                             }
879                             else if (propertyMap.first == "Message")
880                             {
881                                 message = std::get_if<std::string>(
882                                     &propertyMap.second);
883                                 if (message == nullptr)
884                                 {
885                                     messages::propertyMissing(asyncResp->res,
886                                                               "Message");
887                                 }
888                             }
889                         }
890                         thisEntry = {
891                             {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
892                             {"@odata.context", "/redfish/v1/"
893                                                "$metadata#LogEntry.LogEntry"},
894                             {"@odata.id",
895                              "/redfish/v1/Systems/system/LogServices/EventLog/"
896                              "Entries/" +
897                                  std::to_string(*id)},
898                             {"Name", "System Event Log Entry"},
899                             {"Id", std::to_string(*id)},
900                             {"Message", *message},
901                             {"EntryType", "Event"},
902                             {"Severity",
903                              translateSeverityDbusToRedfish(*severity)},
904                             {"Created", crow::utility::getDateTime(timestamp)}};
905                     }
906                 }
907                 std::sort(entriesArray.begin(), entriesArray.end(),
908                           [](const nlohmann::json &left,
909                              const nlohmann::json &right) {
910                               return (left["Id"] <= right["Id"]);
911                           });
912                 asyncResp->res.jsonValue["Members@odata.count"] =
913                     entriesArray.size();
914             },
915             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
916             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
917     }
918 };
919 
920 class DBusEventLogEntry : public Node
921 {
922   public:
923     DBusEventLogEntry(CrowApp &app) :
924         Node(app,
925              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
926              std::string())
927     {
928         entityPrivileges = {
929             {boost::beast::http::verb::get, {{"Login"}}},
930             {boost::beast::http::verb::head, {{"Login"}}},
931             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
932             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
933             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
934             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
935     }
936 
937   private:
938     void doGet(crow::Response &res, const crow::Request &req,
939                const std::vector<std::string> &params) override
940     {
941         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
942         if (params.size() != 1)
943         {
944             messages::internalError(asyncResp->res);
945             return;
946         }
947         const std::string &entryID = params[0];
948 
949         // DBus implementation of EventLog/Entries
950         // Make call to Logging Service to find all log entry objects
951         crow::connections::systemBus->async_method_call(
952             [asyncResp, entryID](const boost::system::error_code ec,
953                                  GetManagedPropertyType &resp) {
954                 if (ec)
955                 {
956                     BMCWEB_LOG_ERROR
957                         << "EventLogEntry (DBus) resp_handler got error " << ec;
958                     messages::internalError(asyncResp->res);
959                     return;
960                 }
961                 uint32_t *id;
962                 std::time_t timestamp;
963                 std::string *severity, *message;
964                 bool *resolved;
965                 for (auto &propertyMap : resp)
966                 {
967                     if (propertyMap.first == "Id")
968                     {
969                         id = std::get_if<uint32_t>(&propertyMap.second);
970                         if (id == nullptr)
971                         {
972                             messages::propertyMissing(asyncResp->res, "Id");
973                         }
974                     }
975                     else if (propertyMap.first == "Timestamp")
976                     {
977                         const uint64_t *millisTimeStamp =
978                             std::get_if<uint64_t>(&propertyMap.second);
979                         if (millisTimeStamp == nullptr)
980                         {
981                             messages::propertyMissing(asyncResp->res,
982                                                       "Timestamp");
983                         }
984                         // Retrieve Created property with format:
985                         // yyyy-mm-ddThh:mm:ss
986                         std::chrono::milliseconds chronoTimeStamp(
987                             *millisTimeStamp);
988                         timestamp =
989                             std::chrono::duration_cast<std::chrono::seconds>(
990                                 chronoTimeStamp)
991                                 .count();
992                     }
993                     else if (propertyMap.first == "Severity")
994                     {
995                         severity =
996                             std::get_if<std::string>(&propertyMap.second);
997                         if (severity == nullptr)
998                         {
999                             messages::propertyMissing(asyncResp->res,
1000                                                       "Severity");
1001                         }
1002                     }
1003                     else if (propertyMap.first == "Message")
1004                     {
1005                         message = std::get_if<std::string>(&propertyMap.second);
1006                         if (message == nullptr)
1007                         {
1008                             messages::propertyMissing(asyncResp->res,
1009                                                       "Message");
1010                         }
1011                     }
1012                 }
1013                 asyncResp->res.jsonValue = {
1014                     {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1015                     {"@odata.context", "/redfish/v1/"
1016                                        "$metadata#LogEntry.LogEntry"},
1017                     {"@odata.id",
1018                      "/redfish/v1/Systems/system/LogServices/EventLog/"
1019                      "Entries/" +
1020                          std::to_string(*id)},
1021                     {"Name", "System Event Log Entry"},
1022                     {"Id", std::to_string(*id)},
1023                     {"Message", *message},
1024                     {"EntryType", "Event"},
1025                     {"Severity", translateSeverityDbusToRedfish(*severity)},
1026                     {"Created", crow::utility::getDateTime(timestamp)}};
1027             },
1028             "xyz.openbmc_project.Logging",
1029             "/xyz/openbmc_project/logging/entry/" + entryID,
1030             "org.freedesktop.DBus.Properties", "GetAll",
1031             "xyz.openbmc_project.Logging.Entry");
1032     }
1033 
1034     void doDelete(crow::Response &res, const crow::Request &req,
1035                   const std::vector<std::string> &params) override
1036     {
1037 
1038         BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1039 
1040         auto asyncResp = std::make_shared<AsyncResp>(res);
1041 
1042         if (params.size() != 1)
1043         {
1044             messages::internalError(asyncResp->res);
1045             return;
1046         }
1047         std::string entryID = params[0];
1048 
1049         dbus::utility::escapePathForDbus(entryID);
1050 
1051         // Process response from Logging service.
1052         auto respHandler = [asyncResp](const boost::system::error_code ec) {
1053             BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1054             if (ec)
1055             {
1056                 // TODO Handle for specific error code
1057                 BMCWEB_LOG_ERROR
1058                     << "EventLogEntry (DBus) doDelete respHandler got error "
1059                     << ec;
1060                 asyncResp->res.result(
1061                     boost::beast::http::status::internal_server_error);
1062                 return;
1063             }
1064 
1065             asyncResp->res.result(boost::beast::http::status::ok);
1066         };
1067 
1068         // Make call to Logging service to request Delete Log
1069         crow::connections::systemBus->async_method_call(
1070             respHandler, "xyz.openbmc_project.Logging",
1071             "/xyz/openbmc_project/logging/entry/" + entryID,
1072             "xyz.openbmc_project.Object.Delete", "Delete");
1073     }
1074 };
1075 
1076 class BMCLogServiceCollection : public Node
1077 {
1078   public:
1079     template <typename CrowApp>
1080     BMCLogServiceCollection(CrowApp &app) :
1081         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
1082     {
1083         entityPrivileges = {
1084             {boost::beast::http::verb::get, {{"Login"}}},
1085             {boost::beast::http::verb::head, {{"Login"}}},
1086             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1087             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1088             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1089             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1090     }
1091 
1092   private:
1093     /**
1094      * Functions triggers appropriate requests on DBus
1095      */
1096     void doGet(crow::Response &res, const crow::Request &req,
1097                const std::vector<std::string> &params) override
1098     {
1099         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1100         // Collections don't include the static data added by SubRoute because
1101         // it has a duplicate entry for members
1102         asyncResp->res.jsonValue["@odata.type"] =
1103             "#LogServiceCollection.LogServiceCollection";
1104         asyncResp->res.jsonValue["@odata.context"] =
1105             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
1106         asyncResp->res.jsonValue["@odata.id"] =
1107             "/redfish/v1/Managers/bmc/LogServices";
1108         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
1109         asyncResp->res.jsonValue["Description"] =
1110             "Collection of LogServices for this Manager";
1111         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
1112         logServiceArray = nlohmann::json::array();
1113 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
1114         logServiceArray.push_back(
1115             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
1116 #endif
1117         asyncResp->res.jsonValue["Members@odata.count"] =
1118             logServiceArray.size();
1119     }
1120 };
1121 
1122 class BMCJournalLogService : public Node
1123 {
1124   public:
1125     template <typename CrowApp>
1126     BMCJournalLogService(CrowApp &app) :
1127         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
1128     {
1129         entityPrivileges = {
1130             {boost::beast::http::verb::get, {{"Login"}}},
1131             {boost::beast::http::verb::head, {{"Login"}}},
1132             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1133             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1134             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1135             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1136     }
1137 
1138   private:
1139     void doGet(crow::Response &res, const crow::Request &req,
1140                const std::vector<std::string> &params) override
1141     {
1142         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1143         asyncResp->res.jsonValue["@odata.type"] =
1144             "#LogService.v1_1_0.LogService";
1145         asyncResp->res.jsonValue["@odata.id"] =
1146             "/redfish/v1/Managers/bmc/LogServices/Journal";
1147         asyncResp->res.jsonValue["@odata.context"] =
1148             "/redfish/v1/$metadata#LogService.LogService";
1149         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
1150         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
1151         asyncResp->res.jsonValue["Id"] = "BMC Journal";
1152         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1153         asyncResp->res.jsonValue["Entries"] = {
1154             {"@odata.id",
1155              "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
1156     }
1157 };
1158 
1159 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1160                                       sd_journal *journal,
1161                                       nlohmann::json &bmcJournalLogEntryJson)
1162 {
1163     // Get the Log Entry contents
1164     int ret = 0;
1165 
1166     std::string_view msg;
1167     ret = getJournalMetadata(journal, "MESSAGE", msg);
1168     if (ret < 0)
1169     {
1170         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1171         return 1;
1172     }
1173 
1174     // Get the severity from the PRIORITY field
1175     int severity = 8; // Default to an invalid priority
1176     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
1177     if (ret < 0)
1178     {
1179         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1180         return 1;
1181     }
1182 
1183     // Get the Created time from the timestamp
1184     std::string entryTimeStr;
1185     if (!getEntryTimestamp(journal, entryTimeStr))
1186     {
1187         return 1;
1188     }
1189 
1190     // Fill in the log entry with the gathered data
1191     bmcJournalLogEntryJson = {
1192         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1193         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1194         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1195                           bmcJournalLogEntryID},
1196         {"Name", "BMC Journal Entry"},
1197         {"Id", bmcJournalLogEntryID},
1198         {"Message", msg},
1199         {"EntryType", "Oem"},
1200         {"Severity",
1201          severity <= 2 ? "Critical"
1202                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
1203         {"OemRecordFormat", "BMC Journal Entry"},
1204         {"Created", std::move(entryTimeStr)}};
1205     return 0;
1206 }
1207 
1208 class BMCJournalLogEntryCollection : public Node
1209 {
1210   public:
1211     template <typename CrowApp>
1212     BMCJournalLogEntryCollection(CrowApp &app) :
1213         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
1214     {
1215         entityPrivileges = {
1216             {boost::beast::http::verb::get, {{"Login"}}},
1217             {boost::beast::http::verb::head, {{"Login"}}},
1218             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1219             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1220             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1221             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1222     }
1223 
1224   private:
1225     void doGet(crow::Response &res, const crow::Request &req,
1226                const std::vector<std::string> &params) override
1227     {
1228         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1229         static constexpr const long maxEntriesPerPage = 1000;
1230         long skip = 0;
1231         long top = maxEntriesPerPage; // Show max entries by default
1232         if (!getSkipParam(asyncResp->res, req, skip))
1233         {
1234             return;
1235         }
1236         if (!getTopParam(asyncResp->res, req, top))
1237         {
1238             return;
1239         }
1240         // Collections don't include the static data added by SubRoute because
1241         // it has a duplicate entry for members
1242         asyncResp->res.jsonValue["@odata.type"] =
1243             "#LogEntryCollection.LogEntryCollection";
1244         asyncResp->res.jsonValue["@odata.id"] =
1245             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1246         asyncResp->res.jsonValue["@odata.context"] =
1247             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
1248         asyncResp->res.jsonValue["@odata.id"] =
1249             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1250         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1251         asyncResp->res.jsonValue["Description"] =
1252             "Collection of BMC Journal Entries";
1253         asyncResp->res.jsonValue["@odata.id"] =
1254             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
1255         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1256         logEntryArray = nlohmann::json::array();
1257 
1258         // Go through the journal and use the timestamp to create a unique ID
1259         // for each entry
1260         sd_journal *journalTmp = nullptr;
1261         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1262         if (ret < 0)
1263         {
1264             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1265             messages::internalError(asyncResp->res);
1266             return;
1267         }
1268         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1269             journalTmp, sd_journal_close);
1270         journalTmp = nullptr;
1271         uint64_t entryCount = 0;
1272         // Reset the unique ID on the first entry
1273         bool firstEntry = true;
1274         SD_JOURNAL_FOREACH(journal.get())
1275         {
1276             entryCount++;
1277             // Handle paging using skip (number of entries to skip from the
1278             // start) and top (number of entries to display)
1279             if (entryCount <= skip || entryCount > skip + top)
1280             {
1281                 continue;
1282             }
1283 
1284             std::string idStr;
1285             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1286             {
1287                 continue;
1288             }
1289 
1290             if (firstEntry)
1291             {
1292                 firstEntry = false;
1293             }
1294 
1295             logEntryArray.push_back({});
1296             nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1297             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1298                                            bmcJournalLogEntry) != 0)
1299             {
1300                 messages::internalError(asyncResp->res);
1301                 return;
1302             }
1303         }
1304         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1305         if (skip + top < entryCount)
1306         {
1307             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1308                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
1309                 std::to_string(skip + top);
1310         }
1311     }
1312 };
1313 
1314 class BMCJournalLogEntry : public Node
1315 {
1316   public:
1317     BMCJournalLogEntry(CrowApp &app) :
1318         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
1319              std::string())
1320     {
1321         entityPrivileges = {
1322             {boost::beast::http::verb::get, {{"Login"}}},
1323             {boost::beast::http::verb::head, {{"Login"}}},
1324             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1325             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1326             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1327             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1328     }
1329 
1330   private:
1331     void doGet(crow::Response &res, const crow::Request &req,
1332                const std::vector<std::string> &params) override
1333     {
1334         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1335         if (params.size() != 1)
1336         {
1337             messages::internalError(asyncResp->res);
1338             return;
1339         }
1340         const std::string &entryID = params[0];
1341         // Convert the unique ID back to a timestamp to find the entry
1342         uint64_t ts = 0;
1343         uint16_t index = 0;
1344         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
1345         {
1346             return;
1347         }
1348 
1349         sd_journal *journalTmp = nullptr;
1350         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1351         if (ret < 0)
1352         {
1353             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1354             messages::internalError(asyncResp->res);
1355             return;
1356         }
1357         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1358             journalTmp, sd_journal_close);
1359         journalTmp = nullptr;
1360         // Go to the timestamp in the log and move to the entry at the index
1361         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1362         for (int i = 0; i <= index; i++)
1363         {
1364             sd_journal_next(journal.get());
1365         }
1366         // Confirm that the entry ID matches what was requested
1367         std::string idStr;
1368         if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
1369         {
1370             messages::resourceMissingAtURI(asyncResp->res, entryID);
1371             return;
1372         }
1373 
1374         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1375                                        asyncResp->res.jsonValue) != 0)
1376         {
1377             messages::internalError(asyncResp->res);
1378             return;
1379         }
1380     }
1381 };
1382 
1383 class CrashdumpService : public Node
1384 {
1385   public:
1386     template <typename CrowApp>
1387     CrashdumpService(CrowApp &app) :
1388         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
1389     {
1390         entityPrivileges = {
1391             {boost::beast::http::verb::get, {{"Login"}}},
1392             {boost::beast::http::verb::head, {{"Login"}}},
1393             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1394             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1395             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1396             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1397     }
1398 
1399   private:
1400     /**
1401      * Functions triggers appropriate requests on DBus
1402      */
1403     void doGet(crow::Response &res, const crow::Request &req,
1404                const std::vector<std::string> &params) override
1405     {
1406         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1407         // Copy over the static data to include the entries added by SubRoute
1408         asyncResp->res.jsonValue["@odata.id"] =
1409             "/redfish/v1/Systems/system/LogServices/Crashdump";
1410         asyncResp->res.jsonValue["@odata.type"] =
1411             "#LogService.v1_1_0.LogService";
1412         asyncResp->res.jsonValue["@odata.context"] =
1413             "/redfish/v1/$metadata#LogService.LogService";
1414         asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1415         asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1416         asyncResp->res.jsonValue["Id"] = "Crashdump";
1417         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1418         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
1419         asyncResp->res.jsonValue["Entries"] = {
1420             {"@odata.id",
1421              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
1422         asyncResp->res.jsonValue["Actions"] = {
1423             {"Oem",
1424              {{"#Crashdump.OnDemand",
1425                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1426                            "Actions/Oem/Crashdump.OnDemand"}}}}}};
1427 
1428 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
1429         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
1430             {"#Crashdump.SendRawPeci",
1431              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1432                          "Actions/Oem/Crashdump.SendRawPeci"}}});
1433 #endif
1434     }
1435 };
1436 
1437 class CrashdumpEntryCollection : public Node
1438 {
1439   public:
1440     template <typename CrowApp>
1441     CrashdumpEntryCollection(CrowApp &app) :
1442         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
1443     {
1444         entityPrivileges = {
1445             {boost::beast::http::verb::get, {{"Login"}}},
1446             {boost::beast::http::verb::head, {{"Login"}}},
1447             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1448             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1449             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1450             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1451     }
1452 
1453   private:
1454     /**
1455      * Functions triggers appropriate requests on DBus
1456      */
1457     void doGet(crow::Response &res, const crow::Request &req,
1458                const std::vector<std::string> &params) override
1459     {
1460         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1461         // Collections don't include the static data added by SubRoute because
1462         // it has a duplicate entry for members
1463         auto getLogEntriesCallback = [asyncResp](
1464                                          const boost::system::error_code ec,
1465                                          const std::vector<std::string> &resp) {
1466             if (ec)
1467             {
1468                 if (ec.value() !=
1469                     boost::system::errc::no_such_file_or_directory)
1470                 {
1471                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1472                                      << ec.message();
1473                     messages::internalError(asyncResp->res);
1474                     return;
1475                 }
1476             }
1477             asyncResp->res.jsonValue["@odata.type"] =
1478                 "#LogEntryCollection.LogEntryCollection";
1479             asyncResp->res.jsonValue["@odata.id"] =
1480                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
1481             asyncResp->res.jsonValue["@odata.context"] =
1482                 "/redfish/v1/"
1483                 "$metadata#LogEntryCollection.LogEntryCollection";
1484             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
1485             asyncResp->res.jsonValue["Description"] =
1486                 "Collection of Crashdump Entries";
1487             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1488             logEntryArray = nlohmann::json::array();
1489             for (const std::string &objpath : resp)
1490             {
1491                 // Don't list the on-demand log
1492                 if (objpath.compare(CrashdumpOnDemandPath) == 0)
1493                 {
1494                     continue;
1495                 }
1496                 std::size_t lastPos = objpath.rfind("/");
1497                 if (lastPos != std::string::npos)
1498                 {
1499                     logEntryArray.push_back(
1500                         {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
1501                                        "Crashdump/Entries/" +
1502                                            objpath.substr(lastPos + 1)}});
1503                 }
1504             }
1505             asyncResp->res.jsonValue["Members@odata.count"] =
1506                 logEntryArray.size();
1507         };
1508         crow::connections::systemBus->async_method_call(
1509             std::move(getLogEntriesCallback),
1510             "xyz.openbmc_project.ObjectMapper",
1511             "/xyz/openbmc_project/object_mapper",
1512             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
1513             std::array<const char *, 1>{CrashdumpInterface});
1514     }
1515 };
1516 
1517 std::string getLogCreatedTime(const nlohmann::json &Crashdump)
1518 {
1519     nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data");
1520     if (cdIt != Crashdump.end())
1521     {
1522         nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1523         if (siIt != cdIt->end())
1524         {
1525             nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1526             if (tsIt != siIt->end())
1527             {
1528                 const std::string *logTime =
1529                     tsIt->get_ptr<const std::string *>();
1530                 if (logTime != nullptr)
1531                 {
1532                     return *logTime;
1533                 }
1534             }
1535         }
1536     }
1537     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1538 
1539     return std::string();
1540 }
1541 
1542 class CrashdumpEntry : public Node
1543 {
1544   public:
1545     CrashdumpEntry(CrowApp &app) :
1546         Node(app,
1547              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
1548              std::string())
1549     {
1550         entityPrivileges = {
1551             {boost::beast::http::verb::get, {{"Login"}}},
1552             {boost::beast::http::verb::head, {{"Login"}}},
1553             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1554             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1555             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1556             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1557     }
1558 
1559   private:
1560     void doGet(crow::Response &res, const crow::Request &req,
1561                const std::vector<std::string> &params) override
1562     {
1563         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1564         if (params.size() != 1)
1565         {
1566             messages::internalError(asyncResp->res);
1567             return;
1568         }
1569         const uint8_t logId = std::atoi(params[0].c_str());
1570         auto getStoredLogCallback = [asyncResp, logId](
1571                                         const boost::system::error_code ec,
1572                                         const std::variant<std::string> &resp) {
1573             if (ec)
1574             {
1575                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1576                 messages::internalError(asyncResp->res);
1577                 return;
1578             }
1579             const std::string *log = std::get_if<std::string>(&resp);
1580             if (log == nullptr)
1581             {
1582                 messages::internalError(asyncResp->res);
1583                 return;
1584             }
1585             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1586             if (j.is_discarded())
1587             {
1588                 messages::internalError(asyncResp->res);
1589                 return;
1590             }
1591             std::string t = getLogCreatedTime(j);
1592             asyncResp->res.jsonValue = {
1593                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1594                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1595                 {"@odata.id",
1596                  "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1597                      std::to_string(logId)},
1598                 {"Name", "CPU Crashdump"},
1599                 {"Id", logId},
1600                 {"EntryType", "Oem"},
1601                 {"OemRecordFormat", "Intel Crashdump"},
1602                 {"Oem", {{"Intel", std::move(j)}}},
1603                 {"Created", std::move(t)}};
1604         };
1605         crow::connections::systemBus->async_method_call(
1606             std::move(getStoredLogCallback), CrashdumpObject,
1607             CrashdumpPath + std::string("/") + std::to_string(logId),
1608             "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface,
1609             "Log");
1610     }
1611 };
1612 
1613 class OnDemandCrashdump : public Node
1614 {
1615   public:
1616     OnDemandCrashdump(CrowApp &app) :
1617         Node(app,
1618              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1619              "Crashdump.OnDemand/")
1620     {
1621         entityPrivileges = {
1622             {boost::beast::http::verb::get, {{"Login"}}},
1623             {boost::beast::http::verb::head, {{"Login"}}},
1624             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1625             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1626             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1627             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1628     }
1629 
1630   private:
1631     void doPost(crow::Response &res, const crow::Request &req,
1632                 const std::vector<std::string> &params) override
1633     {
1634         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1635         static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
1636 
1637         // Only allow one OnDemand Log request at a time
1638         if (onDemandLogMatcher != nullptr)
1639         {
1640             asyncResp->res.addHeader("Retry-After", "30");
1641             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
1642             return;
1643         }
1644         // Make this static so it survives outside this method
1645         static boost::asio::deadline_timer timeout(*req.ioService);
1646 
1647         timeout.expires_from_now(boost::posix_time::seconds(30));
1648         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
1649             onDemandLogMatcher = nullptr;
1650             if (ec)
1651             {
1652                 // operation_aborted is expected if timer is canceled before
1653                 // completion.
1654                 if (ec != boost::asio::error::operation_aborted)
1655                 {
1656                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1657                 }
1658                 return;
1659             }
1660             BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
1661 
1662             messages::internalError(asyncResp->res);
1663         });
1664 
1665         auto onDemandLogMatcherCallback = [asyncResp](
1666                                               sdbusplus::message::message &m) {
1667             BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
1668             boost::system::error_code ec;
1669             timeout.cancel(ec);
1670             if (ec)
1671             {
1672                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1673             }
1674             sdbusplus::message::object_path objPath;
1675             boost::container::flat_map<
1676                 std::string, boost::container::flat_map<
1677                                  std::string, std::variant<std::string>>>
1678                 interfacesAdded;
1679             m.read(objPath, interfacesAdded);
1680             const std::string *log = std::get_if<std::string>(
1681                 &interfacesAdded[CrashdumpInterface]["Log"]);
1682             if (log == nullptr)
1683             {
1684                 messages::internalError(asyncResp->res);
1685                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1686                 // match object inside which this lambda is executing.  Once it
1687                 // is set to nullptr, the match object will be destroyed and the
1688                 // lambda will lose its context, including res, so it needs to
1689                 // be the last thing done.
1690                 onDemandLogMatcher = nullptr;
1691                 return;
1692             }
1693             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1694             if (j.is_discarded())
1695             {
1696                 messages::internalError(asyncResp->res);
1697                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1698                 // match object inside which this lambda is executing.  Once it
1699                 // is set to nullptr, the match object will be destroyed and the
1700                 // lambda will lose its context, including res, so it needs to
1701                 // be the last thing done.
1702                 onDemandLogMatcher = nullptr;
1703                 return;
1704             }
1705             std::string t = getLogCreatedTime(j);
1706             asyncResp->res.jsonValue = {
1707                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1708                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1709                 {"Name", "CPU Crashdump"},
1710                 {"EntryType", "Oem"},
1711                 {"OemRecordFormat", "Intel Crashdump"},
1712                 {"Oem", {{"Intel", std::move(j)}}},
1713                 {"Created", std::move(t)}};
1714             // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1715             // match object inside which this lambda is executing.  Once it is
1716             // set to nullptr, the match object will be destroyed and the lambda
1717             // will lose its context, including res, so it needs to be the last
1718             // thing done.
1719             onDemandLogMatcher = nullptr;
1720         };
1721         onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1722             *crow::connections::systemBus,
1723             sdbusplus::bus::match::rules::interfacesAdded() +
1724                 sdbusplus::bus::match::rules::argNpath(0,
1725                                                        CrashdumpOnDemandPath),
1726             std::move(onDemandLogMatcherCallback));
1727 
1728         auto generateonDemandLogCallback =
1729             [asyncResp](const boost::system::error_code ec,
1730                         const std::string &resp) {
1731                 if (ec)
1732                 {
1733                     if (ec.value() ==
1734                         boost::system::errc::operation_not_supported)
1735                     {
1736                         messages::resourceInStandby(asyncResp->res);
1737                     }
1738                     else
1739                     {
1740                         messages::internalError(asyncResp->res);
1741                     }
1742                     boost::system::error_code timeoutec;
1743                     timeout.cancel(timeoutec);
1744                     if (timeoutec)
1745                     {
1746                         BMCWEB_LOG_ERROR << "error canceling timer "
1747                                          << timeoutec;
1748                     }
1749                     onDemandLogMatcher = nullptr;
1750                     return;
1751                 }
1752             };
1753         crow::connections::systemBus->async_method_call(
1754             std::move(generateonDemandLogCallback), CrashdumpObject,
1755             CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog");
1756     }
1757 };
1758 
1759 class SendRawPECI : public Node
1760 {
1761   public:
1762     SendRawPECI(CrowApp &app) :
1763         Node(app,
1764              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1765              "Crashdump.SendRawPeci/")
1766     {
1767         entityPrivileges = {
1768             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1769             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1770             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1771             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1772             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1773             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1774     }
1775 
1776   private:
1777     void doPost(crow::Response &res, const crow::Request &req,
1778                 const std::vector<std::string> &params) override
1779     {
1780         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1781         uint8_t clientAddress = 0;
1782         uint8_t readLength = 0;
1783         std::vector<uint8_t> peciCommand;
1784         if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1785                                  "ReadLength", readLength, "PECICommand",
1786                                  peciCommand))
1787         {
1788             return;
1789         }
1790 
1791         // Callback to return the Raw PECI response
1792         auto sendRawPECICallback =
1793             [asyncResp](const boost::system::error_code ec,
1794                         const std::vector<uint8_t> &resp) {
1795                 if (ec)
1796                 {
1797                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1798                                      << ec.message();
1799                     messages::internalError(asyncResp->res);
1800                     return;
1801                 }
1802                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1803                                             {"PECIResponse", resp}};
1804             };
1805         // Call the SendRawPECI command with the provided data
1806         crow::connections::systemBus->async_method_call(
1807             std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath,
1808             CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1809             peciCommand);
1810     }
1811 };
1812 
1813 /**
1814  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
1815  */
1816 class DBusLogServiceActionsClear : public Node
1817 {
1818   public:
1819     DBusLogServiceActionsClear(CrowApp &app) :
1820         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1821                   "LogService.Reset")
1822     {
1823         entityPrivileges = {
1824             {boost::beast::http::verb::get, {{"Login"}}},
1825             {boost::beast::http::verb::head, {{"Login"}}},
1826             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1827             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1828             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1829             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1830     }
1831 
1832   private:
1833     /**
1834      * Function handles POST method request.
1835      * The Clear Log actions does not require any parameter.The action deletes
1836      * all entries found in the Entries collection for this Log Service.
1837      */
1838     void doPost(crow::Response &res, const crow::Request &req,
1839                 const std::vector<std::string> &params) override
1840     {
1841         BMCWEB_LOG_DEBUG << "Do delete all entries.";
1842 
1843         auto asyncResp = std::make_shared<AsyncResp>(res);
1844         // Process response from Logging service.
1845         auto resp_handler = [asyncResp](const boost::system::error_code ec) {
1846             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
1847             if (ec)
1848             {
1849                 // TODO Handle for specific error code
1850                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
1851                 asyncResp->res.result(
1852                     boost::beast::http::status::internal_server_error);
1853                 return;
1854             }
1855 
1856             asyncResp->res.result(boost::beast::http::status::no_content);
1857         };
1858 
1859         // Make call to Logging service to request Clear Log
1860         crow::connections::systemBus->async_method_call(
1861             resp_handler, "xyz.openbmc_project.Logging",
1862             "/xyz/openbmc_project/logging",
1863             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1864     }
1865 };
1866 } // namespace redfish
1867