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