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