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