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