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