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