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