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