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