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