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