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