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