xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision d4342a9202e8781a3634a14c4d5311aaaab23fda)
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         if (ret < 0)
1521         {
1522             BMCWEB_LOG_ERROR << "failed to seek to an entry in journal"
1523                              << strerror(-ret);
1524             messages::internalError(asyncResp->res);
1525             return;
1526         }
1527         for (uint64_t i = 0; i <= index; i++)
1528         {
1529             sd_journal_next(journal.get());
1530             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1531             {
1532                 messages::internalError(asyncResp->res);
1533                 return;
1534             }
1535             if (firstEntry)
1536             {
1537                 firstEntry = false;
1538             }
1539         }
1540         // Confirm that the entry ID matches what was requested
1541         if (idStr != entryID)
1542         {
1543             messages::resourceMissingAtURI(asyncResp->res, entryID);
1544             return;
1545         }
1546 
1547         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1548                                        asyncResp->res.jsonValue) != 0)
1549         {
1550             messages::internalError(asyncResp->res);
1551             return;
1552         }
1553     }
1554 };
1555 
1556 class SystemDumpService : public Node
1557 {
1558   public:
1559     template <typename CrowApp>
1560     SystemDumpService(CrowApp &app) :
1561         Node(app, "/redfish/v1/Systems/system/LogServices/System/")
1562     {
1563         entityPrivileges = {
1564             {boost::beast::http::verb::get, {{"Login"}}},
1565             {boost::beast::http::verb::head, {{"Login"}}},
1566             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1567             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1568             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1569             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1570     }
1571 
1572   private:
1573     void doGet(crow::Response &res, const crow::Request &req,
1574                const std::vector<std::string> &params) override
1575     {
1576         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1577 
1578         asyncResp->res.jsonValue["@odata.id"] =
1579             "/redfish/v1/Systems/system/LogServices/System";
1580         asyncResp->res.jsonValue["@odata.type"] =
1581             "#LogService.v1_1_0.LogService";
1582         asyncResp->res.jsonValue["Name"] = "Dump Log Service";
1583         asyncResp->res.jsonValue["Description"] = "System Dump Log Service";
1584         asyncResp->res.jsonValue["Id"] = "System";
1585         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1586         asyncResp->res.jsonValue["LogEntryTypes"] = "Dump";
1587         asyncResp->res.jsonValue["Oem"]["DumpType"] = "System";
1588 
1589         asyncResp->res.jsonValue["Entries"] = {
1590             {"@odata.id",
1591              "/redfish/v1/Systems/system/LogServices/System/Entries"}};
1592         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
1593             {"target", "/redfish/v1/Systems/system/LogServices/System/"
1594                        "Actions/LogService.ClearLog"}};
1595         asyncResp->res.jsonValue["Actions"]["#LogService.CreateLog"] = {
1596             {"target", "/redfish/v1/Systems/system/LogServices/System/"
1597                        "Actions/LogService.CreateLog"}};
1598     }
1599 };
1600 
1601 class SystemDumpEntryCollection : public Node
1602 {
1603   public:
1604     template <typename CrowApp>
1605     SystemDumpEntryCollection(CrowApp &app) :
1606         Node(app, "/redfish/v1/Systems/system/LogServices/System/Entries/")
1607     {
1608         entityPrivileges = {
1609             {boost::beast::http::verb::get, {{"Login"}}},
1610             {boost::beast::http::verb::head, {{"Login"}}},
1611             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1612             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1613             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1614             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1615     }
1616 
1617   private:
1618     /**
1619      * Functions triggers appropriate requests on DBus
1620      */
1621     void doGet(crow::Response &res, const crow::Request &req,
1622                const std::vector<std::string> &params) override
1623     {
1624         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1625 
1626         asyncResp->res.jsonValue["@odata.type"] =
1627             "#LogEntryCollection.LogEntryCollection";
1628         asyncResp->res.jsonValue["@odata.id"] =
1629             "/redfish/v1/Systems/system/LogServices/System/Entries";
1630         asyncResp->res.jsonValue["Name"] = "System Dump Entries";
1631         asyncResp->res.jsonValue["Description"] =
1632             "Collection of System Dump Entries";
1633 
1634         crow::connections::systemBus->async_method_call(
1635             [asyncResp](const boost::system::error_code ec,
1636                         const crow::openbmc_mapper::GetSubTreeType &resp) {
1637                 if (ec)
1638                 {
1639                     BMCWEB_LOG_ERROR << " resp_handler got error " << ec;
1640                     messages::internalError(asyncResp->res);
1641                     return;
1642                 }
1643 
1644                 nlohmann::json &logArray = asyncResp->res.jsonValue["Members"];
1645                 logArray = nlohmann::json::array();
1646                 for (auto &object : resp)
1647                 {
1648                     const std::string &path =
1649                         static_cast<const std::string &>(object.first);
1650                     std::size_t lastPos = path.rfind("/");
1651                     if (lastPos == std::string::npos)
1652                     {
1653                         continue;
1654                     }
1655                     std::string logID = path.substr(lastPos + 1);
1656                     logArray.push_back(
1657                         {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
1658                                        "System/Entries/" +
1659                                            logID}});
1660                 }
1661                 asyncResp->res.jsonValue["Members@odata.count"] =
1662                     logArray.size();
1663             },
1664             "xyz.openbmc_project.ObjectMapper",
1665             "/xyz/openbmc_project/object_mapper",
1666             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
1667             "/xyz/openbmc_project/dump", 0,
1668             std::array<const char *, 1>{
1669                 "xyz.openbmc_project.Dump.Entry.System"});
1670     }
1671 };
1672 
1673 class SystemDumpEntry : public Node
1674 {
1675   public:
1676     SystemDumpEntry(CrowApp &app) :
1677         Node(app,
1678              "/redfish/v1/Systems/system/LogServices/System/Entries/<str>/",
1679              std::string())
1680     {
1681         entityPrivileges = {
1682             {boost::beast::http::verb::get, {{"Login"}}},
1683             {boost::beast::http::verb::head, {{"Login"}}},
1684             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1685             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1686             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1687             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1688     }
1689 
1690   private:
1691     void doGet(crow::Response &res, const crow::Request &req,
1692                const std::vector<std::string> &params) override
1693     {
1694         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1695         if (params.size() != 1)
1696         {
1697             messages::internalError(asyncResp->res);
1698             return;
1699         }
1700         const std::string &entryID = params[0];
1701         crow::connections::systemBus->async_method_call(
1702             [asyncResp, entryID](const boost::system::error_code ec,
1703                                  GetManagedObjectsType &resp) {
1704                 if (ec)
1705                 {
1706                     BMCWEB_LOG_ERROR
1707                         << "SystemDumpEntry resp_handler got error " << ec;
1708                     messages::internalError(asyncResp->res);
1709                     return;
1710                 }
1711 
1712                 for (auto &objectPath : resp)
1713                 {
1714                     if (objectPath.first.str.find(
1715                             "/xyz/openbmc_project/dump/entry/" + entryID) ==
1716                         std::string::npos)
1717                     {
1718                         continue;
1719                     }
1720 
1721                     bool foundSystemDumpEntry = false;
1722                     for (auto &interfaceMap : objectPath.second)
1723                     {
1724                         if (interfaceMap.first ==
1725                             "xyz.openbmc_project.Dump.Entry.System")
1726                         {
1727                             foundSystemDumpEntry = true;
1728                             break;
1729                         }
1730                     }
1731                     if (foundSystemDumpEntry == false)
1732                     {
1733                         BMCWEB_LOG_DEBUG << "Can't find System Dump Entry";
1734                         messages::internalError(asyncResp->res);
1735                         return;
1736                     }
1737 
1738                     std::string timestamp{};
1739                     uint64_t size = 0;
1740 
1741                     for (auto &interfaceMap : objectPath.second)
1742                     {
1743                         if (interfaceMap.first ==
1744                             "xyz.openbmc_project.Dump.Entry")
1745                         {
1746                             for (auto &propertyMap : interfaceMap.second)
1747                             {
1748                                 if (propertyMap.first == "Size")
1749                                 {
1750                                     auto sizePtr = std::get_if<uint64_t>(
1751                                         &propertyMap.second);
1752                                     if (sizePtr == nullptr)
1753                                     {
1754                                         messages::propertyMissing(
1755                                             asyncResp->res, "Size");
1756                                         break;
1757                                     }
1758                                     size = *sizePtr;
1759                                     break;
1760                                 }
1761                             }
1762                         }
1763                         else if (interfaceMap.first ==
1764                                  "xyz.openbmc_project.Time.EpochTime")
1765                         {
1766                             for (auto &propertyMap : interfaceMap.second)
1767                             {
1768                                 if (propertyMap.first == "Elapsed")
1769                                 {
1770                                     const uint64_t *usecsTimeStamp =
1771                                         std::get_if<uint64_t>(
1772                                             &propertyMap.second);
1773                                     if (usecsTimeStamp == nullptr)
1774                                     {
1775                                         messages::propertyMissing(
1776                                             asyncResp->res, "Elapsed");
1777                                         break;
1778                                     }
1779                                     getTimestampStr(*usecsTimeStamp, timestamp);
1780                                     break;
1781                                 }
1782                             }
1783                         }
1784                     }
1785                     asyncResp->res.jsonValue = {
1786                         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1787                         {"@odata.id",
1788                          "/redfish/v1/Systems/system/LogServices/System/"
1789                          "Entries/" +
1790                              entryID},
1791                         {"Name", "System Dump Entry"},
1792                         {"Id", entryID},
1793                         {"SizeInB", size},
1794                         {"EntryType", "Dump"},
1795                         {"EntryCode", "User generated dump"},
1796                         {"Created", timestamp}};
1797 
1798                     asyncResp->res
1799                         .jsonValue["Actions"]["#LogEntry.DownloadLog"] = {
1800                         {"target",
1801                          "/redfish/v1/Systems/system/LogServices/System/"
1802                          "Entries/" +
1803                              entryID + "/Actions/LogEntry.DownloadLog"}};
1804                 }
1805             },
1806             "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
1807             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1808     }
1809 
1810     void doDelete(crow::Response &res, const crow::Request &req,
1811                   const std::vector<std::string> &params) override
1812     {
1813         BMCWEB_LOG_DEBUG << "Do delete single dump entry";
1814 
1815         auto asyncResp = std::make_shared<AsyncResp>(res);
1816 
1817         if (params.size() != 1)
1818         {
1819             messages::internalError(asyncResp->res);
1820             return;
1821         }
1822         std::string entryID = params[0];
1823 
1824         crow::connections::systemBus->async_method_call(
1825             [asyncResp,
1826              entryID](const boost::system::error_code ec,
1827                       const crow::openbmc_mapper::GetSubTreeType &resp) {
1828                 if (ec)
1829                 {
1830                     BMCWEB_LOG_ERROR << " resp_handler got error " << ec;
1831                     messages::internalError(asyncResp->res);
1832                     return;
1833                 }
1834 
1835                 for (auto &object : resp)
1836                 {
1837                     const std::string &path =
1838                         static_cast<const std::string &>(object.first);
1839 
1840                     std::size_t pos = path.rfind(
1841                         "/xyz/openbmc_project/dump/entry/" + entryID);
1842                     if (pos != std::string::npos)
1843                     {
1844                         deleteSystemDumpEntry(asyncResp->res, entryID);
1845                         return;
1846                     }
1847                 }
1848             },
1849             "xyz.openbmc_project.ObjectMapper",
1850             "/xyz/openbmc_project/object_mapper",
1851             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
1852             "/xyz/openbmc_project/dump", 0,
1853             std::array<const char *, 1>{
1854                 "xyz.openbmc_project.Dump.Entry.System"});
1855     }
1856 };
1857 
1858 class SystemDumpEntryDownload : public Node
1859 {
1860   public:
1861     SystemDumpEntryDownload(CrowApp &app) :
1862         Node(app,
1863              "/redfish/v1/Systems/system/LogServices/System/Entries/<str>/"
1864              "Actions/"
1865              "LogEntry.DownloadLog/",
1866              std::string())
1867     {
1868         entityPrivileges = {
1869             {boost::beast::http::verb::get, {{"Login"}}},
1870             {boost::beast::http::verb::head, {{"Login"}}},
1871             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1872     }
1873 
1874   private:
1875     void doPost(crow::Response &res, const crow::Request &req,
1876                 const std::vector<std::string> &params) override
1877     {
1878         if (params.size() != 1)
1879         {
1880             messages::internalError(res);
1881             return;
1882         }
1883         const std::string &entryID = params[0];
1884         crow::obmc_dump::handleDumpOffloadUrl(req, res, entryID);
1885     }
1886 };
1887 
1888 class SystemDumpClear : public Node
1889 {
1890   public:
1891     SystemDumpClear(CrowApp &app) :
1892         Node(app, "/redfish/v1/Systems/system/LogServices/System/"
1893                   "Actions/"
1894                   "LogService.ClearLog/")
1895     {
1896         entityPrivileges = {
1897             {boost::beast::http::verb::get, {{"Login"}}},
1898             {boost::beast::http::verb::head, {{"Login"}}},
1899             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1900     }
1901 
1902   private:
1903     void doPost(crow::Response &res, const crow::Request &req,
1904                 const std::vector<std::string> &params) override
1905     {
1906 
1907         auto asyncResp = std::make_shared<AsyncResp>(res);
1908         crow::connections::systemBus->async_method_call(
1909             [asyncResp](const boost::system::error_code ec,
1910                         const std::vector<std::string> &dumpList) {
1911                 if (ec)
1912                 {
1913                     messages::internalError(asyncResp->res);
1914                     return;
1915                 }
1916 
1917                 for (const std::string &objectPath : dumpList)
1918                 {
1919                     std::size_t pos = objectPath.rfind("/");
1920                     if (pos != std::string::npos)
1921                     {
1922                         std::string logID = objectPath.substr(pos + 1);
1923                         deleteSystemDumpEntry(asyncResp->res, logID);
1924                     }
1925                 }
1926             },
1927             "xyz.openbmc_project.ObjectMapper",
1928             "/xyz/openbmc_project/object_mapper",
1929             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
1930             "/xyz/openbmc_project/dump", 0,
1931             std::array<const char *, 1>{
1932                 "xyz.openbmc_project.Dump.Entry.System"});
1933     }
1934 };
1935 
1936 class CrashdumpService : public Node
1937 {
1938   public:
1939     template <typename CrowApp>
1940     CrashdumpService(CrowApp &app) :
1941         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
1942     {
1943         // Note: Deviated from redfish privilege registry for GET & HEAD
1944         // method for security reasons.
1945         entityPrivileges = {
1946             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1947             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1948             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1949             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1950             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1951             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1952     }
1953 
1954   private:
1955     /**
1956      * Functions triggers appropriate requests on DBus
1957      */
1958     void doGet(crow::Response &res, const crow::Request &req,
1959                const std::vector<std::string> &params) override
1960     {
1961         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1962         // Copy over the static data to include the entries added by SubRoute
1963         asyncResp->res.jsonValue["@odata.id"] =
1964             "/redfish/v1/Systems/system/LogServices/Crashdump";
1965         asyncResp->res.jsonValue["@odata.type"] =
1966             "#LogService.v1_1_0.LogService";
1967         asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
1968         asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
1969         asyncResp->res.jsonValue["Id"] = "Oem Crashdump";
1970         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1971         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
1972         asyncResp->res.jsonValue["Entries"] = {
1973             {"@odata.id",
1974              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
1975         asyncResp->res.jsonValue["Actions"] = {
1976             {"#LogService.ClearLog",
1977              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1978                          "Actions/LogService.ClearLog"}}},
1979             {"Oem",
1980              {{"#Crashdump.OnDemand",
1981                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1982                            "Actions/Oem/Crashdump.OnDemand"}}}}}};
1983 
1984 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
1985         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
1986             {"#Crashdump.SendRawPeci",
1987              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1988                          "Actions/Oem/Crashdump.SendRawPeci"}}});
1989 #endif
1990     }
1991 };
1992 
1993 class CrashdumpClear : public Node
1994 {
1995   public:
1996     CrashdumpClear(CrowApp &app) :
1997         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/"
1998                   "LogService.ClearLog/")
1999     {
2000         // Note: Deviated from redfish privilege registry for GET & HEAD
2001         // method for security reasons.
2002         entityPrivileges = {
2003             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2004             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2005             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2006             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2007             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2008             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2009     }
2010 
2011   private:
2012     void doPost(crow::Response &res, const crow::Request &req,
2013                 const std::vector<std::string> &params) override
2014     {
2015         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2016 
2017         crow::connections::systemBus->async_method_call(
2018             [asyncResp](const boost::system::error_code ec,
2019                         const std::string &resp) {
2020                 if (ec)
2021                 {
2022                     messages::internalError(asyncResp->res);
2023                     return;
2024                 }
2025                 messages::success(asyncResp->res);
2026             },
2027             crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
2028     }
2029 };
2030 
2031 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp,
2032                               const std::string &logID,
2033                               nlohmann::json &logEntryJson)
2034 {
2035     auto getStoredLogCallback =
2036         [asyncResp, logID, &logEntryJson](
2037             const boost::system::error_code ec,
2038             const std::vector<std::pair<std::string, VariantType>> &params) {
2039             if (ec)
2040             {
2041                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
2042                 if (ec.value() ==
2043                     boost::system::linux_error::bad_request_descriptor)
2044                 {
2045                     messages::resourceNotFound(asyncResp->res, "LogEntry",
2046                                                logID);
2047                 }
2048                 else
2049                 {
2050                     messages::internalError(asyncResp->res);
2051                 }
2052                 return;
2053             }
2054 
2055             std::string timestamp{};
2056             std::string filename{};
2057             std::string logfile{};
2058             ParseCrashdumpParameters(params, filename, timestamp, logfile);
2059 
2060             if (filename.empty() || timestamp.empty())
2061             {
2062                 messages::resourceMissingAtURI(asyncResp->res, logID);
2063                 return;
2064             }
2065 
2066             std::string crashdumpURI =
2067                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
2068                 logID + "/" + filename;
2069             logEntryJson = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
2070                             {"@odata.id", "/redfish/v1/Systems/system/"
2071                                           "LogServices/Crashdump/Entries/" +
2072                                               logID},
2073                             {"Name", "CPU Crashdump"},
2074                             {"Id", logID},
2075                             {"EntryType", "Oem"},
2076                             {"OemRecordFormat", "Crashdump URI"},
2077                             {"Message", std::move(crashdumpURI)},
2078                             {"Created", std::move(timestamp)}};
2079         };
2080     crow::connections::systemBus->async_method_call(
2081         std::move(getStoredLogCallback), crashdumpObject,
2082         crashdumpPath + std::string("/") + logID,
2083         "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
2084 }
2085 
2086 class CrashdumpEntryCollection : public Node
2087 {
2088   public:
2089     template <typename CrowApp>
2090     CrashdumpEntryCollection(CrowApp &app) :
2091         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
2092     {
2093         // Note: Deviated from redfish privilege registry for GET & HEAD
2094         // method for security reasons.
2095         entityPrivileges = {
2096             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2097             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2098             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2099             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2100             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2101             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2102     }
2103 
2104   private:
2105     /**
2106      * Functions triggers appropriate requests on DBus
2107      */
2108     void doGet(crow::Response &res, const crow::Request &req,
2109                const std::vector<std::string> &params) override
2110     {
2111         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2112         // Collections don't include the static data added by SubRoute because
2113         // it has a duplicate entry for members
2114         auto getLogEntriesCallback = [asyncResp](
2115                                          const boost::system::error_code ec,
2116                                          const std::vector<std::string> &resp) {
2117             if (ec)
2118             {
2119                 if (ec.value() !=
2120                     boost::system::errc::no_such_file_or_directory)
2121                 {
2122                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
2123                                      << ec.message();
2124                     messages::internalError(asyncResp->res);
2125                     return;
2126                 }
2127             }
2128             asyncResp->res.jsonValue["@odata.type"] =
2129                 "#LogEntryCollection.LogEntryCollection";
2130             asyncResp->res.jsonValue["@odata.id"] =
2131                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
2132             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
2133             asyncResp->res.jsonValue["Description"] =
2134                 "Collection of Crashdump Entries";
2135             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
2136             logEntryArray = nlohmann::json::array();
2137             std::vector<std::string> logIDs;
2138             // Get the list of log entries and build up an empty array big
2139             // enough to hold them
2140             for (const std::string &objpath : resp)
2141             {
2142                 // Get the log ID
2143                 std::size_t lastPos = objpath.rfind("/");
2144                 if (lastPos == std::string::npos)
2145                 {
2146                     continue;
2147                 }
2148                 logIDs.emplace_back(objpath.substr(lastPos + 1));
2149 
2150                 // Add a space for the log entry to the array
2151                 logEntryArray.push_back({});
2152             }
2153             // Now go through and set up async calls to fill in the entries
2154             size_t index = 0;
2155             for (const std::string &logID : logIDs)
2156             {
2157                 // Add the log entry to the array
2158                 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]);
2159             }
2160             asyncResp->res.jsonValue["Members@odata.count"] =
2161                 logEntryArray.size();
2162         };
2163         crow::connections::systemBus->async_method_call(
2164             std::move(getLogEntriesCallback),
2165             "xyz.openbmc_project.ObjectMapper",
2166             "/xyz/openbmc_project/object_mapper",
2167             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
2168             std::array<const char *, 1>{crashdumpInterface});
2169     }
2170 };
2171 
2172 class CrashdumpEntry : public Node
2173 {
2174   public:
2175     CrashdumpEntry(CrowApp &app) :
2176         Node(app,
2177              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
2178              std::string())
2179     {
2180         // Note: Deviated from redfish privilege registry for GET & HEAD
2181         // method for security reasons.
2182         entityPrivileges = {
2183             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2184             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2185             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2186             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2187             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2188             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2189     }
2190 
2191   private:
2192     void doGet(crow::Response &res, const crow::Request &req,
2193                const std::vector<std::string> &params) override
2194     {
2195         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2196         if (params.size() != 1)
2197         {
2198             messages::internalError(asyncResp->res);
2199             return;
2200         }
2201         const std::string &logID = params[0];
2202         logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
2203     }
2204 };
2205 
2206 class CrashdumpFile : public Node
2207 {
2208   public:
2209     CrashdumpFile(CrowApp &app) :
2210         Node(app,
2211              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/"
2212              "<str>/",
2213              std::string(), std::string())
2214     {
2215         // Note: Deviated from redfish privilege registry for GET & HEAD
2216         // method for security reasons.
2217         entityPrivileges = {
2218             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2219             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2220             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2221             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2222             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2223             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2224     }
2225 
2226   private:
2227     void doGet(crow::Response &res, const crow::Request &req,
2228                const std::vector<std::string> &params) override
2229     {
2230         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2231         if (params.size() != 2)
2232         {
2233             messages::internalError(asyncResp->res);
2234             return;
2235         }
2236         const std::string &logID = params[0];
2237         const std::string &fileName = params[1];
2238 
2239         auto getStoredLogCallback =
2240             [asyncResp, logID, fileName](
2241                 const boost::system::error_code ec,
2242                 const std::vector<std::pair<std::string, VariantType>> &resp) {
2243                 if (ec)
2244                 {
2245                     BMCWEB_LOG_DEBUG << "failed to get log ec: "
2246                                      << ec.message();
2247                     messages::internalError(asyncResp->res);
2248                     return;
2249                 }
2250 
2251                 std::string dbusFilename{};
2252                 std::string dbusTimestamp{};
2253                 std::string dbusFilepath{};
2254 
2255                 ParseCrashdumpParameters(resp, dbusFilename, dbusTimestamp,
2256                                          dbusFilepath);
2257 
2258                 if (dbusFilename.empty() || dbusTimestamp.empty() ||
2259                     dbusFilepath.empty())
2260                 {
2261                     messages::resourceMissingAtURI(asyncResp->res, fileName);
2262                     return;
2263                 }
2264 
2265                 // Verify the file name parameter is correct
2266                 if (fileName != dbusFilename)
2267                 {
2268                     messages::resourceMissingAtURI(asyncResp->res, fileName);
2269                     return;
2270                 }
2271 
2272                 if (!std::filesystem::exists(dbusFilepath))
2273                 {
2274                     messages::resourceMissingAtURI(asyncResp->res, fileName);
2275                     return;
2276                 }
2277                 std::ifstream ifs(dbusFilepath, std::ios::in |
2278                                                     std::ios::binary |
2279                                                     std::ios::ate);
2280                 std::ifstream::pos_type fileSize = ifs.tellg();
2281                 if (fileSize < 0)
2282                 {
2283                     messages::generalError(asyncResp->res);
2284                     return;
2285                 }
2286                 ifs.seekg(0, std::ios::beg);
2287 
2288                 auto crashData = std::make_unique<char[]>(
2289                     static_cast<unsigned int>(fileSize));
2290 
2291                 ifs.read(crashData.get(), static_cast<int>(fileSize));
2292 
2293                 // The cast to std::string is intentional in order to use the
2294                 // assign() that applies move mechanics
2295                 asyncResp->res.body().assign(
2296                     static_cast<std::string>(crashData.get()));
2297 
2298                 // Configure this to be a file download when accessed from
2299                 // a browser
2300                 asyncResp->res.addHeader("Content-Disposition", "attachment");
2301             };
2302         crow::connections::systemBus->async_method_call(
2303             std::move(getStoredLogCallback), crashdumpObject,
2304             crashdumpPath + std::string("/") + logID,
2305             "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
2306     }
2307 };
2308 
2309 class OnDemandCrashdump : public Node
2310 {
2311   public:
2312     OnDemandCrashdump(CrowApp &app) :
2313         Node(app,
2314              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
2315              "Crashdump.OnDemand/")
2316     {
2317         // Note: Deviated from redfish privilege registry for GET & HEAD
2318         // method for security reasons.
2319         entityPrivileges = {
2320             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2321             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2322             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2323             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2324             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2325             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2326     }
2327 
2328   private:
2329     void doPost(crow::Response &res, const crow::Request &req,
2330                 const std::vector<std::string> &params) override
2331     {
2332         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2333 
2334         auto generateonDemandLogCallback = [asyncResp,
2335                                             req](const boost::system::error_code
2336                                                      ec,
2337                                                  const std::string &resp) {
2338             if (ec)
2339             {
2340                 if (ec.value() == boost::system::errc::operation_not_supported)
2341                 {
2342                     messages::resourceInStandby(asyncResp->res);
2343                 }
2344                 else if (ec.value() ==
2345                          boost::system::errc::device_or_resource_busy)
2346                 {
2347                     messages::serviceTemporarilyUnavailable(asyncResp->res,
2348                                                             "60");
2349                 }
2350                 else
2351                 {
2352                     messages::internalError(asyncResp->res);
2353                 }
2354                 return;
2355             }
2356             std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
2357                 [](boost::system::error_code err, sdbusplus::message::message &,
2358                    const std::shared_ptr<task::TaskData> &taskData) {
2359                     if (!err)
2360                     {
2361                         taskData->messages.emplace_back(
2362                             messages::taskCompletedOK(
2363                                 std::to_string(taskData->index)));
2364                         taskData->state = "Completed";
2365                     }
2366                     return task::completed;
2367                 },
2368                 "type='signal',interface='org.freedesktop.DBus.Properties',"
2369                 "member='PropertiesChanged',arg0namespace='com.intel."
2370                 "crashdump'");
2371             task->startTimer(std::chrono::minutes(5));
2372             task->populateResp(asyncResp->res);
2373             task->payload.emplace(req);
2374         };
2375         crow::connections::systemBus->async_method_call(
2376             std::move(generateonDemandLogCallback), crashdumpObject,
2377             crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog");
2378     }
2379 };
2380 
2381 class SendRawPECI : public Node
2382 {
2383   public:
2384     SendRawPECI(CrowApp &app) :
2385         Node(app,
2386              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
2387              "Crashdump.SendRawPeci/")
2388     {
2389         // Note: Deviated from redfish privilege registry for GET & HEAD
2390         // method for security reasons.
2391         entityPrivileges = {
2392             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2393             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2394             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2395             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2396             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2397             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2398     }
2399 
2400   private:
2401     void doPost(crow::Response &res, const crow::Request &req,
2402                 const std::vector<std::string> &params) override
2403     {
2404         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2405         std::vector<std::vector<uint8_t>> peciCommands;
2406 
2407         nlohmann::json reqJson =
2408             nlohmann::json::parse(req.body, nullptr, false);
2409         if (reqJson.find("PECICommands") != reqJson.end())
2410         {
2411             if (!json_util::readJson(req, res, "PECICommands", peciCommands))
2412             {
2413                 return;
2414             }
2415             uint32_t idx = 0;
2416             for (auto const &cmd : peciCommands)
2417             {
2418                 if (cmd.size() < 3)
2419                 {
2420                     std::string s("[");
2421                     for (auto const &val : cmd)
2422                     {
2423                         if (val != *cmd.begin())
2424                         {
2425                             s += ",";
2426                         }
2427                         s += std::to_string(val);
2428                     }
2429                     s += "]";
2430                     messages::actionParameterValueFormatError(
2431                         res, s, "PECICommands[" + std::to_string(idx) + "]",
2432                         "SendRawPeci");
2433                     return;
2434                 }
2435                 idx++;
2436             }
2437         }
2438         else
2439         {
2440             /* This interface is deprecated */
2441             uint8_t clientAddress = 0;
2442             uint8_t readLength = 0;
2443             std::vector<uint8_t> peciCommand;
2444             if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
2445                                      "ReadLength", readLength, "PECICommand",
2446                                      peciCommand))
2447             {
2448                 return;
2449             }
2450             peciCommands.push_back({clientAddress, 0, readLength});
2451             peciCommands[0].insert(peciCommands[0].end(), peciCommand.begin(),
2452                                    peciCommand.end());
2453         }
2454         // Callback to return the Raw PECI response
2455         auto sendRawPECICallback =
2456             [asyncResp](const boost::system::error_code ec,
2457                         const std::vector<std::vector<uint8_t>> &resp) {
2458                 if (ec)
2459                 {
2460                     BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: "
2461                                      << ec.message();
2462                     messages::internalError(asyncResp->res);
2463                     return;
2464                 }
2465                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
2466                                             {"PECIResponse", resp}};
2467             };
2468         // Call the SendRawPECI command with the provided data
2469         crow::connections::systemBus->async_method_call(
2470             std::move(sendRawPECICallback), crashdumpObject, crashdumpPath,
2471             crashdumpRawPECIInterface, "SendRawPeci", peciCommands);
2472     }
2473 };
2474 
2475 /**
2476  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
2477  */
2478 class DBusLogServiceActionsClear : public Node
2479 {
2480   public:
2481     DBusLogServiceActionsClear(CrowApp &app) :
2482         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
2483                   "LogService.ClearLog/")
2484     {
2485         entityPrivileges = {
2486             {boost::beast::http::verb::get, {{"Login"}}},
2487             {boost::beast::http::verb::head, {{"Login"}}},
2488             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2489             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2490             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2491             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2492     }
2493 
2494   private:
2495     /**
2496      * Function handles POST method request.
2497      * The Clear Log actions does not require any parameter.The action deletes
2498      * all entries found in the Entries collection for this Log Service.
2499      */
2500     void doPost(crow::Response &res, const crow::Request &req,
2501                 const std::vector<std::string> &params) override
2502     {
2503         BMCWEB_LOG_DEBUG << "Do delete all entries.";
2504 
2505         auto asyncResp = std::make_shared<AsyncResp>(res);
2506         // Process response from Logging service.
2507         auto resp_handler = [asyncResp](const boost::system::error_code ec) {
2508             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
2509             if (ec)
2510             {
2511                 // TODO Handle for specific error code
2512                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
2513                 asyncResp->res.result(
2514                     boost::beast::http::status::internal_server_error);
2515                 return;
2516             }
2517 
2518             asyncResp->res.result(boost::beast::http::status::no_content);
2519         };
2520 
2521         // Make call to Logging service to request Clear Log
2522         crow::connections::systemBus->async_method_call(
2523             resp_handler, "xyz.openbmc_project.Logging",
2524             "/xyz/openbmc_project/logging",
2525             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
2526     }
2527 };
2528 
2529 /****************************************************
2530  * Redfish PostCode interfaces
2531  * using DBUS interface: getPostCodesTS
2532  ******************************************************/
2533 class PostCodesLogService : public Node
2534 {
2535   public:
2536     PostCodesLogService(CrowApp &app) :
2537         Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/")
2538     {
2539         entityPrivileges = {
2540             {boost::beast::http::verb::get, {{"Login"}}},
2541             {boost::beast::http::verb::head, {{"Login"}}},
2542             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2543             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2544             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2545             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2546     }
2547 
2548   private:
2549     void doGet(crow::Response &res, const crow::Request &req,
2550                const std::vector<std::string> &params) override
2551     {
2552         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2553 
2554         asyncResp->res.jsonValue = {
2555             {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"},
2556             {"@odata.type", "#LogService.v1_1_0.LogService"},
2557             {"@odata.context", "/redfish/v1/$metadata#LogService.LogService"},
2558             {"Name", "POST Code Log Service"},
2559             {"Description", "POST Code Log Service"},
2560             {"Id", "BIOS POST Code Log"},
2561             {"OverWritePolicy", "WrapsWhenFull"},
2562             {"Entries",
2563              {{"@odata.id",
2564                "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}};
2565         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
2566             {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/"
2567                        "Actions/LogService.ClearLog"}};
2568     }
2569 };
2570 
2571 class PostCodesClear : public Node
2572 {
2573   public:
2574     PostCodesClear(CrowApp &app) :
2575         Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/"
2576                   "LogService.ClearLog/")
2577     {
2578         entityPrivileges = {
2579             {boost::beast::http::verb::get, {{"Login"}}},
2580             {boost::beast::http::verb::head, {{"Login"}}},
2581             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2582             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2583             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2584             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2585     }
2586 
2587   private:
2588     void doPost(crow::Response &res, const crow::Request &req,
2589                 const std::vector<std::string> &params) override
2590     {
2591         BMCWEB_LOG_DEBUG << "Do delete all postcodes entries.";
2592 
2593         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2594         // Make call to post-code service to request clear all
2595         crow::connections::systemBus->async_method_call(
2596             [asyncResp](const boost::system::error_code ec) {
2597                 if (ec)
2598                 {
2599                     // TODO Handle for specific error code
2600                     BMCWEB_LOG_ERROR
2601                         << "doClearPostCodes resp_handler got error " << ec;
2602                     asyncResp->res.result(
2603                         boost::beast::http::status::internal_server_error);
2604                     messages::internalError(asyncResp->res);
2605                     return;
2606                 }
2607             },
2608             "xyz.openbmc_project.State.Boot.PostCode",
2609             "/xyz/openbmc_project/State/Boot/PostCode",
2610             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
2611     }
2612 };
2613 
2614 static void fillPostCodeEntry(
2615     std::shared_ptr<AsyncResp> aResp,
2616     const boost::container::flat_map<uint64_t, uint64_t> &postcode,
2617     const uint16_t bootIndex, const uint64_t codeIndex = 0,
2618     const uint64_t skip = 0, const uint64_t top = 0)
2619 {
2620     // Get the Message from the MessageRegistry
2621     const message_registries::Message *message =
2622         message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode");
2623 
2624     uint64_t currentCodeIndex = 0;
2625     nlohmann::json &logEntryArray = aResp->res.jsonValue["Members"];
2626 
2627     uint64_t firstCodeTimeUs = 0;
2628     for (const std::pair<uint64_t, uint64_t> &code : postcode)
2629     {
2630         currentCodeIndex++;
2631         std::string postcodeEntryID =
2632             "B" + std::to_string(bootIndex) + "-" +
2633             std::to_string(currentCodeIndex); // 1 based index in EntryID string
2634 
2635         uint64_t usecSinceEpoch = code.first;
2636         uint64_t usTimeOffset = 0;
2637 
2638         if (1 == currentCodeIndex)
2639         { // already incremented
2640             firstCodeTimeUs = code.first;
2641         }
2642         else
2643         {
2644             usTimeOffset = code.first - firstCodeTimeUs;
2645         }
2646 
2647         // skip if no specific codeIndex is specified and currentCodeIndex does
2648         // not fall between top and skip
2649         if ((codeIndex == 0) &&
2650             (currentCodeIndex <= skip || currentCodeIndex > top))
2651         {
2652             continue;
2653         }
2654 
2655         // skip if a sepcific codeIndex is specified and does not match the
2656         // currentIndex
2657         if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
2658         {
2659             // This is done for simplicity. 1st entry is needed to calculate
2660             // time offset. To improve efficiency, one can get to the entry
2661             // directly (possibly with flatmap's nth method)
2662             continue;
2663         }
2664 
2665         // currentCodeIndex is within top and skip or equal to specified code
2666         // index
2667 
2668         // Get the Created time from the timestamp
2669         std::string entryTimeStr;
2670         if (!getTimestampStr(usecSinceEpoch, entryTimeStr))
2671         {
2672             continue;
2673         }
2674 
2675         // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
2676         std::ostringstream hexCode;
2677         hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
2678                 << code.second;
2679         std::ostringstream timeOffsetStr;
2680         // Set Fixed -Point Notation
2681         timeOffsetStr << std::fixed;
2682         // Set precision to 4 digits
2683         timeOffsetStr << std::setprecision(4);
2684         // Add double to stream
2685         timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
2686         std::vector<std::string> messageArgs = {
2687             std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()};
2688 
2689         // Get MessageArgs template from message registry
2690         std::string msg;
2691         if (message != nullptr)
2692         {
2693             msg = message->message;
2694 
2695             // fill in this post code value
2696             int i = 0;
2697             for (const std::string &messageArg : messageArgs)
2698             {
2699                 std::string argStr = "%" + std::to_string(++i);
2700                 size_t argPos = msg.find(argStr);
2701                 if (argPos != std::string::npos)
2702                 {
2703                     msg.replace(argPos, argStr.length(), messageArg);
2704                 }
2705             }
2706         }
2707 
2708         // Get Severity template from message registry
2709         std::string severity;
2710         if (message != nullptr)
2711         {
2712             severity = message->severity;
2713         }
2714 
2715         // add to AsyncResp
2716         logEntryArray.push_back({});
2717         nlohmann::json &bmcLogEntry = logEntryArray.back();
2718         bmcLogEntry = {
2719             {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
2720             {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
2721             {"@odata.id", "/redfish/v1/Systems/system/LogServices/"
2722                           "PostCodes/Entries/" +
2723                               postcodeEntryID},
2724             {"Name", "POST Code Log Entry"},
2725             {"Id", postcodeEntryID},
2726             {"Message", std::move(msg)},
2727             {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"},
2728             {"MessageArgs", std::move(messageArgs)},
2729             {"EntryType", "Event"},
2730             {"Severity", std::move(severity)},
2731             {"Created", std::move(entryTimeStr)}};
2732     }
2733 }
2734 
2735 static void getPostCodeForEntry(std::shared_ptr<AsyncResp> aResp,
2736                                 const uint16_t bootIndex,
2737                                 const uint64_t codeIndex)
2738 {
2739     crow::connections::systemBus->async_method_call(
2740         [aResp, bootIndex, codeIndex](
2741             const boost::system::error_code ec,
2742             const boost::container::flat_map<uint64_t, uint64_t> &postcode) {
2743             if (ec)
2744             {
2745                 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
2746                 messages::internalError(aResp->res);
2747                 return;
2748             }
2749 
2750             // skip the empty postcode boots
2751             if (postcode.empty())
2752             {
2753                 return;
2754             }
2755 
2756             fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex);
2757 
2758             aResp->res.jsonValue["Members@odata.count"] =
2759                 aResp->res.jsonValue["Members"].size();
2760         },
2761         "xyz.openbmc_project.State.Boot.PostCode",
2762         "/xyz/openbmc_project/State/Boot/PostCode",
2763         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
2764         bootIndex);
2765 }
2766 
2767 static void getPostCodeForBoot(std::shared_ptr<AsyncResp> aResp,
2768                                const uint16_t bootIndex,
2769                                const uint16_t bootCount,
2770                                const uint64_t entryCount, const uint64_t skip,
2771                                const uint64_t top)
2772 {
2773     crow::connections::systemBus->async_method_call(
2774         [aResp, bootIndex, bootCount, entryCount, skip,
2775          top](const boost::system::error_code ec,
2776               const boost::container::flat_map<uint64_t, uint64_t> &postcode) {
2777             if (ec)
2778             {
2779                 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
2780                 messages::internalError(aResp->res);
2781                 return;
2782             }
2783 
2784             uint64_t endCount = entryCount;
2785             if (!postcode.empty())
2786             {
2787                 endCount = entryCount + postcode.size();
2788 
2789                 if ((skip < endCount) && ((top + skip) > entryCount))
2790                 {
2791                     uint64_t thisBootSkip =
2792                         std::max(skip, entryCount) - entryCount;
2793                     uint64_t thisBootTop =
2794                         std::min(top + skip, endCount) - entryCount;
2795 
2796                     fillPostCodeEntry(aResp, postcode, bootIndex, 0,
2797                                       thisBootSkip, thisBootTop);
2798                 }
2799                 aResp->res.jsonValue["Members@odata.count"] = endCount;
2800             }
2801 
2802             // continue to previous bootIndex
2803             if (bootIndex < bootCount)
2804             {
2805                 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1),
2806                                    bootCount, endCount, skip, top);
2807             }
2808             else
2809             {
2810                 aResp->res.jsonValue["Members@odata.nextLink"] =
2811                     "/redfish/v1/Systems/system/LogServices/PostCodes/"
2812                     "Entries?$skip=" +
2813                     std::to_string(skip + top);
2814             }
2815         },
2816         "xyz.openbmc_project.State.Boot.PostCode",
2817         "/xyz/openbmc_project/State/Boot/PostCode",
2818         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
2819         bootIndex);
2820 }
2821 
2822 static void getCurrentBootNumber(std::shared_ptr<AsyncResp> aResp,
2823                                  const uint64_t skip, const uint64_t top)
2824 {
2825     uint64_t entryCount = 0;
2826     crow::connections::systemBus->async_method_call(
2827         [aResp, entryCount, skip,
2828          top](const boost::system::error_code ec,
2829               const std::variant<uint16_t> &bootCount) {
2830             if (ec)
2831             {
2832                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
2833                 messages::internalError(aResp->res);
2834                 return;
2835             }
2836             auto pVal = std::get_if<uint16_t>(&bootCount);
2837             if (pVal)
2838             {
2839                 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top);
2840             }
2841             else
2842             {
2843                 BMCWEB_LOG_DEBUG << "Post code boot index failed.";
2844             }
2845         },
2846         "xyz.openbmc_project.State.Boot.PostCode",
2847         "/xyz/openbmc_project/State/Boot/PostCode",
2848         "org.freedesktop.DBus.Properties", "Get",
2849         "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount");
2850 }
2851 
2852 class PostCodesEntryCollection : public Node
2853 {
2854   public:
2855     template <typename CrowApp>
2856     PostCodesEntryCollection(CrowApp &app) :
2857         Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/")
2858     {
2859         entityPrivileges = {
2860             {boost::beast::http::verb::get, {{"Login"}}},
2861             {boost::beast::http::verb::head, {{"Login"}}},
2862             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2863             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2864             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2865             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2866     }
2867 
2868   private:
2869     void doGet(crow::Response &res, const crow::Request &req,
2870                const std::vector<std::string> &params) override
2871     {
2872         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2873 
2874         asyncResp->res.jsonValue["@odata.type"] =
2875             "#LogEntryCollection.LogEntryCollection";
2876         asyncResp->res.jsonValue["@odata.context"] =
2877             "/redfish/v1/"
2878             "$metadata#LogEntryCollection.LogEntryCollection";
2879         asyncResp->res.jsonValue["@odata.id"] =
2880             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
2881         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
2882         asyncResp->res.jsonValue["Description"] =
2883             "Collection of POST Code Log Entries";
2884         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
2885         asyncResp->res.jsonValue["Members@odata.count"] = 0;
2886 
2887         uint64_t skip = 0;
2888         uint64_t top = maxEntriesPerPage; // Show max entries by default
2889         if (!getSkipParam(asyncResp->res, req, skip))
2890         {
2891             return;
2892         }
2893         if (!getTopParam(asyncResp->res, req, top))
2894         {
2895             return;
2896         }
2897         getCurrentBootNumber(asyncResp, skip, top);
2898     }
2899 };
2900 
2901 class PostCodesEntry : public Node
2902 {
2903   public:
2904     PostCodesEntry(CrowApp &app) :
2905         Node(app,
2906              "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/",
2907              std::string())
2908     {
2909         entityPrivileges = {
2910             {boost::beast::http::verb::get, {{"Login"}}},
2911             {boost::beast::http::verb::head, {{"Login"}}},
2912             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2913             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2914             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2915             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2916     }
2917 
2918   private:
2919     void doGet(crow::Response &res, const crow::Request &req,
2920                const std::vector<std::string> &params) override
2921     {
2922         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2923         if (params.size() != 1)
2924         {
2925             messages::internalError(asyncResp->res);
2926             return;
2927         }
2928 
2929         const std::string &targetID = params[0];
2930 
2931         size_t bootPos = targetID.find('B');
2932         if (bootPos == std::string::npos)
2933         {
2934             // Requested ID was not found
2935             messages::resourceMissingAtURI(asyncResp->res, targetID);
2936             return;
2937         }
2938         std::string_view bootIndexStr(targetID);
2939         bootIndexStr.remove_prefix(bootPos + 1);
2940         uint16_t bootIndex = 0;
2941         uint64_t codeIndex = 0;
2942         size_t dashPos = bootIndexStr.find('-');
2943 
2944         if (dashPos == std::string::npos)
2945         {
2946             return;
2947         }
2948         std::string_view codeIndexStr(bootIndexStr);
2949         bootIndexStr.remove_suffix(dashPos);
2950         codeIndexStr.remove_prefix(dashPos + 1);
2951 
2952         bootIndex = static_cast<uint16_t>(
2953             strtoul(std::string(bootIndexStr).c_str(), NULL, 0));
2954         codeIndex = strtoul(std::string(codeIndexStr).c_str(), NULL, 0);
2955         if (bootIndex == 0 || codeIndex == 0)
2956         {
2957             BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string "
2958                              << params[0];
2959         }
2960 
2961         asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry";
2962         asyncResp->res.jsonValue["@odata.context"] =
2963             "/redfish/v1/$metadata#LogEntry.LogEntry";
2964         asyncResp->res.jsonValue["@odata.id"] =
2965             "/redfish/v1/Systems/system/LogServices/PostCodes/"
2966             "Entries";
2967         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
2968         asyncResp->res.jsonValue["Description"] =
2969             "Collection of POST Code Log Entries";
2970         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
2971         asyncResp->res.jsonValue["Members@odata.count"] = 0;
2972 
2973         getPostCodeForEntry(asyncResp, bootIndex, codeIndex);
2974     }
2975 };
2976 
2977 } // namespace redfish
2978