xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision 336e96c667adf8401c8ed772d681a17c35b94b44)
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 
23 #include <systemd/sd-journal.h>
24 
25 #include <boost/algorithm/string/split.hpp>
26 #include <boost/beast/core/span.hpp>
27 #include <boost/container/flat_map.hpp>
28 #include <error_messages.hpp>
29 #include <filesystem>
30 #include <string_view>
31 #include <variant>
32 
33 namespace redfish
34 {
35 
36 constexpr char const *CrashdumpObject = "com.intel.crashdump";
37 constexpr char const *CrashdumpPath = "/com/intel/crashdump";
38 constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand";
39 constexpr char const *CrashdumpInterface = "com.intel.crashdump";
40 constexpr char const *CrashdumpOnDemandInterface =
41     "com.intel.crashdump.OnDemand";
42 constexpr char const *CrashdumpRawPECIInterface =
43     "com.intel.crashdump.SendRawPeci";
44 
45 namespace message_registries
46 {
47 static const Message *getMessageFromRegistry(
48     const std::string &messageKey,
49     const boost::beast::span<const MessageEntry> registry)
50 {
51     boost::beast::span<const MessageEntry>::const_iterator messageIt =
52         std::find_if(registry.cbegin(), registry.cend(),
53                      [&messageKey](const MessageEntry &messageEntry) {
54                          return !std::strcmp(messageEntry.first,
55                                              messageKey.c_str());
56                      });
57     if (messageIt != registry.cend())
58     {
59         return &messageIt->second;
60     }
61 
62     return nullptr;
63 }
64 
65 static const Message *getMessage(const std::string_view &messageID)
66 {
67     // Redfish MessageIds are in the form
68     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
69     // the right Message
70     std::vector<std::string> fields;
71     fields.reserve(4);
72     boost::split(fields, messageID, boost::is_any_of("."));
73     std::string &registryName = fields[0];
74     std::string &messageKey = fields[3];
75 
76     // Find the right registry and check it for the MessageKey
77     if (std::string(base::header.registryPrefix) == registryName)
78     {
79         return getMessageFromRegistry(
80             messageKey, boost::beast::span<const MessageEntry>(base::registry));
81     }
82     if (std::string(openbmc::header.registryPrefix) == registryName)
83     {
84         return getMessageFromRegistry(
85             messageKey,
86             boost::beast::span<const MessageEntry>(openbmc::registry));
87     }
88     return nullptr;
89 }
90 } // namespace message_registries
91 
92 namespace fs = std::filesystem;
93 
94 using GetManagedPropertyType = boost::container::flat_map<
95     std::string,
96     sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
97                                 int32_t, uint32_t, int64_t, uint64_t, double>>;
98 
99 using GetManagedObjectsType = boost::container::flat_map<
100     sdbusplus::message::object_path,
101     boost::container::flat_map<std::string, GetManagedPropertyType>>;
102 
103 inline std::string translateSeverityDbusToRedfish(const std::string &s)
104 {
105     if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
106     {
107         return "Critical";
108     }
109     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
110     {
111         return "Critical";
112     }
113     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
114     {
115         return "OK";
116     }
117     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
118     {
119         return "Critical";
120     }
121     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
122     {
123         return "Critical";
124     }
125     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
126     {
127         return "OK";
128     }
129     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
130     {
131         return "OK";
132     }
133     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
134     {
135         return "Warning";
136     }
137     return "";
138 }
139 
140 static int getJournalMetadata(sd_journal *journal,
141                               const std::string_view &field,
142                               std::string_view &contents)
143 {
144     const char *data = nullptr;
145     size_t length = 0;
146     int ret = 0;
147     // Get the metadata from the requested field of the journal entry
148     ret = sd_journal_get_data(journal, field.data(), (const void **)&data,
149                               &length);
150     if (ret < 0)
151     {
152         return ret;
153     }
154     contents = std::string_view(data, length);
155     // Only use the content after the "=" character.
156     contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
157     return ret;
158 }
159 
160 static int getJournalMetadata(sd_journal *journal,
161                               const std::string_view &field, const int &base,
162                               int &contents)
163 {
164     int ret = 0;
165     std::string_view metadata;
166     // Get the metadata from the requested field of the journal entry
167     ret = getJournalMetadata(journal, field, metadata);
168     if (ret < 0)
169     {
170         return ret;
171     }
172     contents = strtol(metadata.data(), nullptr, base);
173     return ret;
174 }
175 
176 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp)
177 {
178     int ret = 0;
179     uint64_t timestamp = 0;
180     ret = sd_journal_get_realtime_usec(journal, &timestamp);
181     if (ret < 0)
182     {
183         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
184                          << strerror(-ret);
185         return false;
186     }
187     time_t t =
188         static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
189     struct tm *loctime = localtime(&t);
190     char entryTime[64] = {};
191     if (NULL != loctime)
192     {
193         strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
194     }
195     // Insert the ':' into the timezone
196     std::string_view t1(entryTime);
197     std::string_view t2(entryTime);
198     if (t1.size() > 2 && t2.size() > 2)
199     {
200         t1.remove_suffix(2);
201         t2.remove_prefix(t2.size() - 2);
202     }
203     entryTimestamp = std::string(t1) + ":" + std::string(t2);
204     return true;
205 }
206 
207 static bool getSkipParam(crow::Response &res, const crow::Request &req,
208                          long &skip)
209 {
210     char *skipParam = req.urlParams.get("$skip");
211     if (skipParam != nullptr)
212     {
213         char *ptr = nullptr;
214         skip = std::strtol(skipParam, &ptr, 10);
215         if (*skipParam == '\0' || *ptr != '\0')
216         {
217 
218             messages::queryParameterValueTypeError(res, std::string(skipParam),
219                                                    "$skip");
220             return false;
221         }
222         if (skip < 0)
223         {
224 
225             messages::queryParameterOutOfRange(res, std::to_string(skip),
226                                                "$skip", "greater than 0");
227             return false;
228         }
229     }
230     return true;
231 }
232 
233 static constexpr const long maxEntriesPerPage = 1000;
234 static bool getTopParam(crow::Response &res, const crow::Request &req,
235                         long &top)
236 {
237     char *topParam = req.urlParams.get("$top");
238     if (topParam != nullptr)
239     {
240         char *ptr = nullptr;
241         top = std::strtol(topParam, &ptr, 10);
242         if (*topParam == '\0' || *ptr != '\0')
243         {
244             messages::queryParameterValueTypeError(res, std::string(topParam),
245                                                    "$top");
246             return false;
247         }
248         if (top < 1 || top > maxEntriesPerPage)
249         {
250 
251             messages::queryParameterOutOfRange(
252                 res, std::to_string(top), "$top",
253                 "1-" + std::to_string(maxEntriesPerPage));
254             return false;
255         }
256     }
257     return true;
258 }
259 
260 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID)
261 {
262     int ret = 0;
263     static uint64_t prevTs = 0;
264     static int index = 0;
265     // Get the entry timestamp
266     uint64_t curTs = 0;
267     ret = sd_journal_get_realtime_usec(journal, &curTs);
268     if (ret < 0)
269     {
270         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
271                          << strerror(-ret);
272         return false;
273     }
274     // If the timestamp isn't unique, increment the index
275     if (curTs == prevTs)
276     {
277         index++;
278     }
279     else
280     {
281         // Otherwise, reset it
282         index = 0;
283     }
284     // Save the timestamp
285     prevTs = curTs;
286 
287     entryID = std::to_string(curTs);
288     if (index > 0)
289     {
290         entryID += "_" + std::to_string(index);
291     }
292     return true;
293 }
294 
295 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID)
296 {
297     static uint64_t prevTs = 0;
298     static int index = 0;
299     // Get the entry timestamp
300     uint64_t curTs = 0;
301     std::tm timeStruct = {};
302     std::istringstream entryStream(logEntry);
303     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
304     {
305         curTs = std::mktime(&timeStruct);
306     }
307     // If the timestamp isn't unique, increment the index
308     if (curTs == prevTs)
309     {
310         index++;
311     }
312     else
313     {
314         // Otherwise, reset it
315         index = 0;
316     }
317     // Save the timestamp
318     prevTs = curTs;
319 
320     entryID = std::to_string(curTs);
321     if (index > 0)
322     {
323         entryID += "_" + std::to_string(index);
324     }
325     return true;
326 }
327 
328 static bool getTimestampFromID(crow::Response &res, const std::string &entryID,
329                                uint64_t &timestamp, uint16_t &index)
330 {
331     if (entryID.empty())
332     {
333         return false;
334     }
335     // Convert the unique ID back to a timestamp to find the entry
336     std::string_view tsStr(entryID);
337 
338     auto underscorePos = tsStr.find("_");
339     if (underscorePos != tsStr.npos)
340     {
341         // Timestamp has an index
342         tsStr.remove_suffix(tsStr.size() - underscorePos);
343         std::string_view indexStr(entryID);
344         indexStr.remove_prefix(underscorePos + 1);
345         std::size_t pos;
346         try
347         {
348             index = std::stoul(std::string(indexStr), &pos);
349         }
350         catch (std::invalid_argument)
351         {
352             messages::resourceMissingAtURI(res, entryID);
353             return false;
354         }
355         catch (std::out_of_range)
356         {
357             messages::resourceMissingAtURI(res, entryID);
358             return false;
359         }
360         if (pos != indexStr.size())
361         {
362             messages::resourceMissingAtURI(res, entryID);
363             return false;
364         }
365     }
366     // Timestamp has no index
367     std::size_t pos;
368     try
369     {
370         timestamp = std::stoull(std::string(tsStr), &pos);
371     }
372     catch (std::invalid_argument)
373     {
374         messages::resourceMissingAtURI(res, entryID);
375         return false;
376     }
377     catch (std::out_of_range)
378     {
379         messages::resourceMissingAtURI(res, entryID);
380         return false;
381     }
382     if (pos != tsStr.size())
383     {
384         messages::resourceMissingAtURI(res, entryID);
385         return false;
386     }
387     return true;
388 }
389 
390 static bool
391     getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles)
392 {
393     static const std::filesystem::path redfishLogDir = "/var/log";
394     static const std::string redfishLogFilename = "redfish";
395 
396     // Loop through the directory looking for redfish log files
397     for (const std::filesystem::directory_entry &dirEnt :
398          std::filesystem::directory_iterator(redfishLogDir))
399     {
400         // If we find a redfish log file, save the path
401         std::string filename = dirEnt.path().filename();
402         if (boost::starts_with(filename, redfishLogFilename))
403         {
404             redfishLogFiles.emplace_back(redfishLogDir / filename);
405         }
406     }
407     // As the log files rotate, they are appended with a ".#" that is higher for
408     // the older logs. Since we don't expect more than 10 log files, we
409     // can just sort the list to get them in order from newest to oldest
410     std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
411 
412     return !redfishLogFiles.empty();
413 }
414 
415 class SystemLogServiceCollection : public Node
416 {
417   public:
418     template <typename CrowApp>
419     SystemLogServiceCollection(CrowApp &app) :
420         Node(app, "/redfish/v1/Systems/system/LogServices/")
421     {
422         entityPrivileges = {
423             {boost::beast::http::verb::get, {{"Login"}}},
424             {boost::beast::http::verb::head, {{"Login"}}},
425             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
426             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
427             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
428             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
429     }
430 
431   private:
432     /**
433      * Functions triggers appropriate requests on DBus
434      */
435     void doGet(crow::Response &res, const crow::Request &req,
436                const std::vector<std::string> &params) override
437     {
438         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
439         // Collections don't include the static data added by SubRoute because
440         // it has a duplicate entry for members
441         asyncResp->res.jsonValue["@odata.type"] =
442             "#LogServiceCollection.LogServiceCollection";
443         asyncResp->res.jsonValue["@odata.context"] =
444             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
445         asyncResp->res.jsonValue["@odata.id"] =
446             "/redfish/v1/Systems/system/LogServices";
447         asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
448         asyncResp->res.jsonValue["Description"] =
449             "Collection of LogServices for this Computer System";
450         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
451         logServiceArray = nlohmann::json::array();
452         logServiceArray.push_back(
453             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
454 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
455         logServiceArray.push_back(
456             {{"@odata.id",
457               "/redfish/v1/Systems/system/LogServices/Crashdump"}});
458 #endif
459         asyncResp->res.jsonValue["Members@odata.count"] =
460             logServiceArray.size();
461     }
462 };
463 
464 class EventLogService : public Node
465 {
466   public:
467     template <typename CrowApp>
468     EventLogService(CrowApp &app) :
469         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
470     {
471         entityPrivileges = {
472             {boost::beast::http::verb::get, {{"Login"}}},
473             {boost::beast::http::verb::head, {{"Login"}}},
474             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
475             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
476             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
477             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
478     }
479 
480   private:
481     void doGet(crow::Response &res, const crow::Request &req,
482                const std::vector<std::string> &params) override
483     {
484         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
485 
486         asyncResp->res.jsonValue["@odata.id"] =
487             "/redfish/v1/Systems/system/LogServices/EventLog";
488         asyncResp->res.jsonValue["@odata.type"] =
489             "#LogService.v1_1_0.LogService";
490         asyncResp->res.jsonValue["@odata.context"] =
491             "/redfish/v1/$metadata#LogService.LogService";
492         asyncResp->res.jsonValue["Name"] = "Event Log Service";
493         asyncResp->res.jsonValue["Description"] = "System Event Log Service";
494         asyncResp->res.jsonValue["Id"] = "Event Log";
495         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
496         asyncResp->res.jsonValue["Entries"] = {
497             {"@odata.id",
498              "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
499         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
500 
501             {"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
502                        "Actions/LogService.ClearLog"}};
503     }
504 };
505 
506 class EventLogClear : public Node
507 {
508   public:
509     EventLogClear(CrowApp &app) :
510         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
511                   "LogService.ClearLog/")
512     {
513         entityPrivileges = {
514             {boost::beast::http::verb::get, {{"Login"}}},
515             {boost::beast::http::verb::head, {{"Login"}}},
516             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
517             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
518             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
519             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
520     }
521 
522   private:
523     void doPost(crow::Response &res, const crow::Request &req,
524                 const std::vector<std::string> &params) override
525     {
526         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
527 
528         // Clear the EventLog by deleting the log files
529         std::vector<std::filesystem::path> redfishLogFiles;
530         if (getRedfishLogFiles(redfishLogFiles))
531         {
532             for (const std::filesystem::path &file : redfishLogFiles)
533             {
534                 std::error_code ec;
535                 std::filesystem::remove(file, ec);
536             }
537         }
538 
539         // Reload rsyslog so it knows to start new log files
540         crow::connections::systemBus->async_method_call(
541             [asyncResp](const boost::system::error_code ec) {
542                 if (ec)
543                 {
544                     BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
545                     messages::internalError(asyncResp->res);
546                     return;
547                 }
548 
549                 messages::success(asyncResp->res);
550             },
551             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
552             "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
553             "replace");
554     }
555 };
556 
557 static int fillEventLogEntryJson(const std::string &logEntryID,
558                                  const std::string logEntry,
559                                  nlohmann::json &logEntryJson)
560 {
561     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
562     // First get the Timestamp
563     size_t space = logEntry.find_first_of(" ");
564     if (space == std::string::npos)
565     {
566         return 1;
567     }
568     std::string timestamp = logEntry.substr(0, space);
569     // Then get the log contents
570     size_t entryStart = logEntry.find_first_not_of(" ", space);
571     if (entryStart == std::string::npos)
572     {
573         return 1;
574     }
575     std::string_view entry(logEntry);
576     entry.remove_prefix(entryStart);
577     // Use split to separate the entry into its fields
578     std::vector<std::string> logEntryFields;
579     boost::split(logEntryFields, entry, boost::is_any_of(","),
580                  boost::token_compress_on);
581     // We need at least a MessageId to be valid
582     if (logEntryFields.size() < 1)
583     {
584         return 1;
585     }
586     std::string &messageID = logEntryFields[0];
587 
588     // Get the Message from the MessageRegistry
589     const message_registries::Message *message =
590         message_registries::getMessage(messageID);
591 
592     std::string msg;
593     std::string severity;
594     if (message != nullptr)
595     {
596         msg = message->message;
597         severity = message->severity;
598     }
599 
600     // Get the MessageArgs from the log if there are any
601     boost::beast::span<std::string> messageArgs;
602     if (logEntryFields.size() > 1)
603     {
604         std::string &messageArgsStart = logEntryFields[1];
605         // If the first string is empty, assume there are no MessageArgs
606         std::size_t messageArgsSize = 0;
607         if (!messageArgsStart.empty())
608         {
609             messageArgsSize = logEntryFields.size() - 1;
610         }
611 
612         messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize);
613 
614         // Fill the MessageArgs into the Message
615         int i = 0;
616         for (const std::string &messageArg : messageArgs)
617         {
618             std::string argStr = "%" + std::to_string(++i);
619             size_t argPos = msg.find(argStr);
620             if (argPos != std::string::npos)
621             {
622                 msg.replace(argPos, argStr.length(), messageArg);
623             }
624         }
625     }
626 
627     // Get the Created time from the timestamp. The log timestamp is in RFC3339
628     // format which matches the Redfish format except for the fractional seconds
629     // between the '.' and the '+', so just remove them.
630     std::size_t dot = timestamp.find_first_of(".");
631     std::size_t plus = timestamp.find_first_of("+");
632     if (dot != std::string::npos && plus != std::string::npos)
633     {
634         timestamp.erase(dot, plus - dot);
635     }
636 
637     // Fill in the log entry with the gathered data
638     logEntryJson = {
639         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
640         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
641         {"@odata.id",
642          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/#" +
643              logEntryID},
644         {"Name", "System Event Log Entry"},
645         {"Id", logEntryID},
646         {"Message", std::move(msg)},
647         {"MessageId", std::move(messageID)},
648         {"MessageArgs", std::move(messageArgs)},
649         {"EntryType", "Event"},
650         {"Severity", std::move(severity)},
651         {"Created", std::move(timestamp)}};
652     return 0;
653 }
654 
655 class JournalEventLogEntryCollection : public Node
656 {
657   public:
658     template <typename CrowApp>
659     JournalEventLogEntryCollection(CrowApp &app) :
660         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
661     {
662         entityPrivileges = {
663             {boost::beast::http::verb::get, {{"Login"}}},
664             {boost::beast::http::verb::head, {{"Login"}}},
665             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
666             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
667             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
668             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
669     }
670 
671   private:
672     void doGet(crow::Response &res, const crow::Request &req,
673                const std::vector<std::string> &params) override
674     {
675         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
676         long skip = 0;
677         long top = maxEntriesPerPage; // Show max entries by default
678         if (!getSkipParam(asyncResp->res, req, skip))
679         {
680             return;
681         }
682         if (!getTopParam(asyncResp->res, req, top))
683         {
684             return;
685         }
686         // Collections don't include the static data added by SubRoute because
687         // it has a duplicate entry for members
688         asyncResp->res.jsonValue["@odata.type"] =
689             "#LogEntryCollection.LogEntryCollection";
690         asyncResp->res.jsonValue["@odata.context"] =
691             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
692         asyncResp->res.jsonValue["@odata.id"] =
693             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
694         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
695         asyncResp->res.jsonValue["Description"] =
696             "Collection of System Event Log Entries";
697 
698         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
699         logEntryArray = nlohmann::json::array();
700         // Go through the log files and create a unique ID for each entry
701         std::vector<std::filesystem::path> redfishLogFiles;
702         getRedfishLogFiles(redfishLogFiles);
703         uint64_t entryCount = 0;
704         std::string logEntry;
705 
706         // Oldest logs are in the last file, so start there and loop backwards
707         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
708              it++)
709         {
710             std::ifstream logStream(*it);
711             if (!logStream.is_open())
712             {
713                 continue;
714             }
715 
716             while (std::getline(logStream, logEntry))
717             {
718                 entryCount++;
719                 // Handle paging using skip (number of entries to skip from the
720                 // start) and top (number of entries to display)
721                 if (entryCount <= skip || entryCount > skip + top)
722                 {
723                     continue;
724                 }
725 
726                 std::string idStr;
727                 if (!getUniqueEntryID(logEntry, idStr))
728                 {
729                     continue;
730                 }
731 
732                 logEntryArray.push_back({});
733                 nlohmann::json &bmcLogEntry = logEntryArray.back();
734                 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
735                 {
736                     messages::internalError(asyncResp->res);
737                     return;
738                 }
739             }
740         }
741         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
742         if (skip + top < entryCount)
743         {
744             asyncResp->res.jsonValue["Members@odata.nextLink"] =
745                 "/redfish/v1/Systems/system/LogServices/EventLog/"
746                 "Entries?$skip=" +
747                 std::to_string(skip + top);
748         }
749     }
750 };
751 
752 class DBusEventLogEntryCollection : public Node
753 {
754   public:
755     template <typename CrowApp>
756     DBusEventLogEntryCollection(CrowApp &app) :
757         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
758     {
759         entityPrivileges = {
760             {boost::beast::http::verb::get, {{"Login"}}},
761             {boost::beast::http::verb::head, {{"Login"}}},
762             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
763             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
764             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
765             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
766     }
767 
768   private:
769     void doGet(crow::Response &res, const crow::Request &req,
770                const std::vector<std::string> &params) override
771     {
772         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
773 
774         // Collections don't include the static data added by SubRoute because
775         // it has a duplicate entry for members
776         asyncResp->res.jsonValue["@odata.type"] =
777             "#LogEntryCollection.LogEntryCollection";
778         asyncResp->res.jsonValue["@odata.context"] =
779             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
780         asyncResp->res.jsonValue["@odata.id"] =
781             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
782         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
783         asyncResp->res.jsonValue["Description"] =
784             "Collection of System Event Log Entries";
785 
786         // DBus implementation of EventLog/Entries
787         // Make call to Logging Service to find all log entry objects
788         crow::connections::systemBus->async_method_call(
789             [asyncResp](const boost::system::error_code ec,
790                         GetManagedObjectsType &resp) {
791                 if (ec)
792                 {
793                     // TODO Handle for specific error code
794                     BMCWEB_LOG_ERROR
795                         << "getLogEntriesIfaceData resp_handler got error "
796                         << ec;
797                     messages::internalError(asyncResp->res);
798                     return;
799                 }
800                 nlohmann::json &entriesArray =
801                     asyncResp->res.jsonValue["Members"];
802                 entriesArray = nlohmann::json::array();
803                 for (auto &objectPath : resp)
804                 {
805                     for (auto &interfaceMap : objectPath.second)
806                     {
807                         if (interfaceMap.first !=
808                             "xyz.openbmc_project.Logging.Entry")
809                         {
810                             BMCWEB_LOG_DEBUG << "Bailing early on "
811                                              << interfaceMap.first;
812                             continue;
813                         }
814                         entriesArray.push_back({});
815                         nlohmann::json &thisEntry = entriesArray.back();
816                         uint32_t *id;
817                         std::time_t timestamp;
818                         std::string *severity, *message;
819                         bool *resolved;
820                         for (auto &propertyMap : interfaceMap.second)
821                         {
822                             if (propertyMap.first == "Id")
823                             {
824                                 id = sdbusplus::message::variant_ns::get_if<
825                                     uint32_t>(&propertyMap.second);
826                                 if (id == nullptr)
827                                 {
828                                     messages::propertyMissing(asyncResp->res,
829                                                               "Id");
830                                 }
831                             }
832                             else if (propertyMap.first == "Timestamp")
833                             {
834                                 const uint64_t *millisTimeStamp =
835                                     std::get_if<uint64_t>(&propertyMap.second);
836                                 if (millisTimeStamp == nullptr)
837                                 {
838                                     messages::propertyMissing(asyncResp->res,
839                                                               "Timestamp");
840                                 }
841                                 // Retrieve Created property with format:
842                                 // yyyy-mm-ddThh:mm:ss
843                                 std::chrono::milliseconds chronoTimeStamp(
844                                     *millisTimeStamp);
845                                 timestamp =
846                                     std::chrono::duration_cast<
847                                         std::chrono::seconds>(chronoTimeStamp)
848                                         .count();
849                             }
850                             else if (propertyMap.first == "Severity")
851                             {
852                                 severity = std::get_if<std::string>(
853                                     &propertyMap.second);
854                                 if (severity == nullptr)
855                                 {
856                                     messages::propertyMissing(asyncResp->res,
857                                                               "Severity");
858                                 }
859                             }
860                             else if (propertyMap.first == "Message")
861                             {
862                                 message = std::get_if<std::string>(
863                                     &propertyMap.second);
864                                 if (message == nullptr)
865                                 {
866                                     messages::propertyMissing(asyncResp->res,
867                                                               "Message");
868                                 }
869                             }
870                         }
871                         thisEntry = {
872                             {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
873                             {"@odata.context", "/redfish/v1/"
874                                                "$metadata#LogEntry.LogEntry"},
875                             {"@odata.id",
876                              "/redfish/v1/Systems/system/LogServices/EventLog/"
877                              "Entries/" +
878                                  std::to_string(*id)},
879                             {"Name", "System Event Log Entry"},
880                             {"Id", std::to_string(*id)},
881                             {"Message", *message},
882                             {"EntryType", "Event"},
883                             {"Severity",
884                              translateSeverityDbusToRedfish(*severity)},
885                             {"Created", crow::utility::getDateTime(timestamp)}};
886                     }
887                 }
888                 std::sort(entriesArray.begin(), entriesArray.end(),
889                           [](const nlohmann::json &left,
890                              const nlohmann::json &right) {
891                               return (left["Id"] <= right["Id"]);
892                           });
893                 asyncResp->res.jsonValue["Members@odata.count"] =
894                     entriesArray.size();
895             },
896             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
897             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
898     }
899 };
900 
901 class DBusEventLogEntry : public Node
902 {
903   public:
904     DBusEventLogEntry(CrowApp &app) :
905         Node(app,
906              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
907              std::string())
908     {
909         entityPrivileges = {
910             {boost::beast::http::verb::get, {{"Login"}}},
911             {boost::beast::http::verb::head, {{"Login"}}},
912             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
913             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
914             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
915             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
916     }
917 
918   private:
919     void doGet(crow::Response &res, const crow::Request &req,
920                const std::vector<std::string> &params) override
921     {
922         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
923         if (params.size() != 1)
924         {
925             messages::internalError(asyncResp->res);
926             return;
927         }
928         const std::string &entryID = params[0];
929 
930         // DBus implementation of EventLog/Entries
931         // Make call to Logging Service to find all log entry objects
932         crow::connections::systemBus->async_method_call(
933             [asyncResp, entryID](const boost::system::error_code ec,
934                                  GetManagedPropertyType &resp) {
935                 if (ec)
936                 {
937                     BMCWEB_LOG_ERROR
938                         << "EventLogEntry (DBus) resp_handler got error " << ec;
939                     messages::internalError(asyncResp->res);
940                     return;
941                 }
942                 uint32_t *id;
943                 std::time_t timestamp;
944                 std::string *severity, *message;
945                 bool *resolved;
946                 for (auto &propertyMap : resp)
947                 {
948                     if (propertyMap.first == "Id")
949                     {
950                         id = std::get_if<uint32_t>(&propertyMap.second);
951                         if (id == nullptr)
952                         {
953                             messages::propertyMissing(asyncResp->res, "Id");
954                         }
955                     }
956                     else if (propertyMap.first == "Timestamp")
957                     {
958                         const uint64_t *millisTimeStamp =
959                             std::get_if<uint64_t>(&propertyMap.second);
960                         if (millisTimeStamp == nullptr)
961                         {
962                             messages::propertyMissing(asyncResp->res,
963                                                       "Timestamp");
964                         }
965                         // Retrieve Created property with format:
966                         // yyyy-mm-ddThh:mm:ss
967                         std::chrono::milliseconds chronoTimeStamp(
968                             *millisTimeStamp);
969                         timestamp =
970                             std::chrono::duration_cast<std::chrono::seconds>(
971                                 chronoTimeStamp)
972                                 .count();
973                     }
974                     else if (propertyMap.first == "Severity")
975                     {
976                         severity =
977                             std::get_if<std::string>(&propertyMap.second);
978                         if (severity == nullptr)
979                         {
980                             messages::propertyMissing(asyncResp->res,
981                                                       "Severity");
982                         }
983                     }
984                     else if (propertyMap.first == "Message")
985                     {
986                         message = std::get_if<std::string>(&propertyMap.second);
987                         if (message == nullptr)
988                         {
989                             messages::propertyMissing(asyncResp->res,
990                                                       "Message");
991                         }
992                     }
993                 }
994                 asyncResp->res.jsonValue = {
995                     {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
996                     {"@odata.context", "/redfish/v1/"
997                                        "$metadata#LogEntry.LogEntry"},
998                     {"@odata.id",
999                      "/redfish/v1/Systems/system/LogServices/EventLog/"
1000                      "Entries/" +
1001                          std::to_string(*id)},
1002                     {"Name", "System Event Log Entry"},
1003                     {"Id", std::to_string(*id)},
1004                     {"Message", *message},
1005                     {"EntryType", "Event"},
1006                     {"Severity", translateSeverityDbusToRedfish(*severity)},
1007                     {"Created", crow::utility::getDateTime(timestamp)}};
1008             },
1009             "xyz.openbmc_project.Logging",
1010             "/xyz/openbmc_project/logging/entry/" + entryID,
1011             "org.freedesktop.DBus.Properties", "GetAll",
1012             "xyz.openbmc_project.Logging.Entry");
1013     }
1014 
1015     void doDelete(crow::Response &res, const crow::Request &req,
1016                   const std::vector<std::string> &params) override
1017     {
1018 
1019         BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1020 
1021         auto asyncResp = std::make_shared<AsyncResp>(res);
1022 
1023         if (params.size() != 1)
1024         {
1025             messages::internalError(asyncResp->res);
1026             return;
1027         }
1028         std::string entryID = params[0];
1029 
1030         dbus::utility::escapePathForDbus(entryID);
1031 
1032         // Process response from Logging service.
1033         auto respHandler = [asyncResp](const boost::system::error_code ec) {
1034             BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1035             if (ec)
1036             {
1037                 // TODO Handle for specific error code
1038                 BMCWEB_LOG_ERROR
1039                     << "EventLogEntry (DBus) doDelete respHandler got error "
1040                     << ec;
1041                 asyncResp->res.result(
1042                     boost::beast::http::status::internal_server_error);
1043                 return;
1044             }
1045 
1046             asyncResp->res.result(boost::beast::http::status::ok);
1047         };
1048 
1049         // Make call to Logging service to request Delete Log
1050         crow::connections::systemBus->async_method_call(
1051             respHandler, "xyz.openbmc_project.Logging",
1052             "/xyz/openbmc_project/logging/entry/" + entryID,
1053             "xyz.openbmc_project.Object.Delete", "Delete");
1054     }
1055 };
1056 
1057 class BMCLogServiceCollection : public Node
1058 {
1059   public:
1060     template <typename CrowApp>
1061     BMCLogServiceCollection(CrowApp &app) :
1062         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
1063     {
1064         entityPrivileges = {
1065             {boost::beast::http::verb::get, {{"Login"}}},
1066             {boost::beast::http::verb::head, {{"Login"}}},
1067             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1068             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1069             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1070             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1071     }
1072 
1073   private:
1074     /**
1075      * Functions triggers appropriate requests on DBus
1076      */
1077     void doGet(crow::Response &res, const crow::Request &req,
1078                const std::vector<std::string> &params) override
1079     {
1080         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1081         // Collections don't include the static data added by SubRoute because
1082         // it has a duplicate entry for members
1083         asyncResp->res.jsonValue["@odata.type"] =
1084             "#LogServiceCollection.LogServiceCollection";
1085         asyncResp->res.jsonValue["@odata.context"] =
1086             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
1087         asyncResp->res.jsonValue["@odata.id"] =
1088             "/redfish/v1/Managers/bmc/LogServices";
1089         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
1090         asyncResp->res.jsonValue["Description"] =
1091             "Collection of LogServices for this Manager";
1092         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
1093         logServiceArray = nlohmann::json::array();
1094 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
1095         logServiceArray.push_back(
1096             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
1097 #endif
1098         asyncResp->res.jsonValue["Members@odata.count"] =
1099             logServiceArray.size();
1100     }
1101 };
1102 
1103 class BMCJournalLogService : public Node
1104 {
1105   public:
1106     template <typename CrowApp>
1107     BMCJournalLogService(CrowApp &app) :
1108         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
1109     {
1110         entityPrivileges = {
1111             {boost::beast::http::verb::get, {{"Login"}}},
1112             {boost::beast::http::verb::head, {{"Login"}}},
1113             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1114             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1115             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1116             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1117     }
1118 
1119   private:
1120     void doGet(crow::Response &res, const crow::Request &req,
1121                const std::vector<std::string> &params) override
1122     {
1123         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1124         asyncResp->res.jsonValue["@odata.type"] =
1125             "#LogService.v1_1_0.LogService";
1126         asyncResp->res.jsonValue["@odata.id"] =
1127             "/redfish/v1/Managers/bmc/LogServices/Journal";
1128         asyncResp->res.jsonValue["@odata.context"] =
1129             "/redfish/v1/$metadata#LogService.LogService";
1130         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
1131         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
1132         asyncResp->res.jsonValue["Id"] = "BMC Journal";
1133         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1134         asyncResp->res.jsonValue["Entries"] = {
1135             {"@odata.id",
1136              "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
1137     }
1138 };
1139 
1140 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1141                                       sd_journal *journal,
1142                                       nlohmann::json &bmcJournalLogEntryJson)
1143 {
1144     // Get the Log Entry contents
1145     int ret = 0;
1146 
1147     std::string_view msg;
1148     ret = getJournalMetadata(journal, "MESSAGE", msg);
1149     if (ret < 0)
1150     {
1151         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1152         return 1;
1153     }
1154 
1155     // Get the severity from the PRIORITY field
1156     int severity = 8; // Default to an invalid priority
1157     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
1158     if (ret < 0)
1159     {
1160         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1161         return 1;
1162     }
1163 
1164     // Get the Created time from the timestamp
1165     std::string entryTimeStr;
1166     if (!getEntryTimestamp(journal, entryTimeStr))
1167     {
1168         return 1;
1169     }
1170 
1171     // Fill in the log entry with the gathered data
1172     bmcJournalLogEntryJson = {
1173         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1174         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1175         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1176                           bmcJournalLogEntryID},
1177         {"Name", "BMC Journal Entry"},
1178         {"Id", bmcJournalLogEntryID},
1179         {"Message", msg},
1180         {"EntryType", "Oem"},
1181         {"Severity",
1182          severity <= 2 ? "Critical"
1183                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
1184         {"OemRecordFormat", "BMC Journal Entry"},
1185         {"Created", std::move(entryTimeStr)}};
1186     return 0;
1187 }
1188 
1189 class BMCJournalLogEntryCollection : public Node
1190 {
1191   public:
1192     template <typename CrowApp>
1193     BMCJournalLogEntryCollection(CrowApp &app) :
1194         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
1195     {
1196         entityPrivileges = {
1197             {boost::beast::http::verb::get, {{"Login"}}},
1198             {boost::beast::http::verb::head, {{"Login"}}},
1199             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1200             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1201             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1202             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1203     }
1204 
1205   private:
1206     void doGet(crow::Response &res, const crow::Request &req,
1207                const std::vector<std::string> &params) override
1208     {
1209         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1210         static constexpr const long maxEntriesPerPage = 1000;
1211         long skip = 0;
1212         long top = maxEntriesPerPage; // Show max entries by default
1213         if (!getSkipParam(asyncResp->res, req, skip))
1214         {
1215             return;
1216         }
1217         if (!getTopParam(asyncResp->res, req, top))
1218         {
1219             return;
1220         }
1221         // Collections don't include the static data added by SubRoute because
1222         // it has a duplicate entry for members
1223         asyncResp->res.jsonValue["@odata.type"] =
1224             "#LogEntryCollection.LogEntryCollection";
1225         asyncResp->res.jsonValue["@odata.id"] =
1226             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1227         asyncResp->res.jsonValue["@odata.context"] =
1228             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
1229         asyncResp->res.jsonValue["@odata.id"] =
1230             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1231         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1232         asyncResp->res.jsonValue["Description"] =
1233             "Collection of BMC Journal Entries";
1234         asyncResp->res.jsonValue["@odata.id"] =
1235             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
1236         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1237         logEntryArray = nlohmann::json::array();
1238 
1239         // Go through the journal and use the timestamp to create a unique ID
1240         // for each entry
1241         sd_journal *journalTmp = nullptr;
1242         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1243         if (ret < 0)
1244         {
1245             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1246             messages::internalError(asyncResp->res);
1247             return;
1248         }
1249         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1250             journalTmp, sd_journal_close);
1251         journalTmp = nullptr;
1252         uint64_t entryCount = 0;
1253         SD_JOURNAL_FOREACH(journal.get())
1254         {
1255             entryCount++;
1256             // Handle paging using skip (number of entries to skip from the
1257             // start) and top (number of entries to display)
1258             if (entryCount <= skip || entryCount > skip + top)
1259             {
1260                 continue;
1261             }
1262 
1263             std::string idStr;
1264             if (!getUniqueEntryID(journal.get(), idStr))
1265             {
1266                 continue;
1267             }
1268 
1269             logEntryArray.push_back({});
1270             nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1271             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1272                                            bmcJournalLogEntry) != 0)
1273             {
1274                 messages::internalError(asyncResp->res);
1275                 return;
1276             }
1277         }
1278         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1279         if (skip + top < entryCount)
1280         {
1281             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1282                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
1283                 std::to_string(skip + top);
1284         }
1285     }
1286 };
1287 
1288 class BMCJournalLogEntry : public Node
1289 {
1290   public:
1291     BMCJournalLogEntry(CrowApp &app) :
1292         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
1293              std::string())
1294     {
1295         entityPrivileges = {
1296             {boost::beast::http::verb::get, {{"Login"}}},
1297             {boost::beast::http::verb::head, {{"Login"}}},
1298             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1299             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1300             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1301             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1302     }
1303 
1304   private:
1305     void doGet(crow::Response &res, const crow::Request &req,
1306                const std::vector<std::string> &params) override
1307     {
1308         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1309         if (params.size() != 1)
1310         {
1311             messages::internalError(asyncResp->res);
1312             return;
1313         }
1314         const std::string &entryID = params[0];
1315         // Convert the unique ID back to a timestamp to find the entry
1316         uint64_t ts = 0;
1317         uint16_t index = 0;
1318         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
1319         {
1320             return;
1321         }
1322 
1323         sd_journal *journalTmp = nullptr;
1324         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1325         if (ret < 0)
1326         {
1327             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1328             messages::internalError(asyncResp->res);
1329             return;
1330         }
1331         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1332             journalTmp, sd_journal_close);
1333         journalTmp = nullptr;
1334         // Go to the timestamp in the log and move to the entry at the index
1335         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1336         for (int i = 0; i <= index; i++)
1337         {
1338             sd_journal_next(journal.get());
1339         }
1340         // Confirm that the entry ID matches what was requested
1341         std::string idStr;
1342         if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
1343         {
1344             messages::resourceMissingAtURI(asyncResp->res, entryID);
1345             return;
1346         }
1347 
1348         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1349                                        asyncResp->res.jsonValue) != 0)
1350         {
1351             messages::internalError(asyncResp->res);
1352             return;
1353         }
1354     }
1355 };
1356 
1357 class CrashdumpService : public Node
1358 {
1359   public:
1360     template <typename CrowApp>
1361     CrashdumpService(CrowApp &app) :
1362         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
1363     {
1364         entityPrivileges = {
1365             {boost::beast::http::verb::get, {{"Login"}}},
1366             {boost::beast::http::verb::head, {{"Login"}}},
1367             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1368             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1369             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1370             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1371     }
1372 
1373   private:
1374     /**
1375      * Functions triggers appropriate requests on DBus
1376      */
1377     void doGet(crow::Response &res, const crow::Request &req,
1378                const std::vector<std::string> &params) override
1379     {
1380         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1381         // Copy over the static data to include the entries added by SubRoute
1382         asyncResp->res.jsonValue["@odata.id"] =
1383             "/redfish/v1/Systems/system/LogServices/Crashdump";
1384         asyncResp->res.jsonValue["@odata.type"] =
1385             "#LogService.v1_1_0.LogService";
1386         asyncResp->res.jsonValue["@odata.context"] =
1387             "/redfish/v1/$metadata#LogService.LogService";
1388         asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1389         asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1390         asyncResp->res.jsonValue["Id"] = "Crashdump";
1391         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1392         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
1393         asyncResp->res.jsonValue["Entries"] = {
1394             {"@odata.id",
1395              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
1396         asyncResp->res.jsonValue["Actions"] = {
1397             {"Oem",
1398              {{"#Crashdump.OnDemand",
1399                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1400                            "Actions/Oem/Crashdump.OnDemand"}}}}}};
1401 
1402 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
1403         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
1404             {"#Crashdump.SendRawPeci",
1405              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1406                          "Actions/Oem/Crashdump.SendRawPeci"}}});
1407 #endif
1408     }
1409 };
1410 
1411 class CrashdumpEntryCollection : public Node
1412 {
1413   public:
1414     template <typename CrowApp>
1415     CrashdumpEntryCollection(CrowApp &app) :
1416         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
1417     {
1418         entityPrivileges = {
1419             {boost::beast::http::verb::get, {{"Login"}}},
1420             {boost::beast::http::verb::head, {{"Login"}}},
1421             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1422             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1423             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1424             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1425     }
1426 
1427   private:
1428     /**
1429      * Functions triggers appropriate requests on DBus
1430      */
1431     void doGet(crow::Response &res, const crow::Request &req,
1432                const std::vector<std::string> &params) override
1433     {
1434         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1435         // Collections don't include the static data added by SubRoute because
1436         // it has a duplicate entry for members
1437         auto getLogEntriesCallback = [asyncResp](
1438                                          const boost::system::error_code ec,
1439                                          const std::vector<std::string> &resp) {
1440             if (ec)
1441             {
1442                 if (ec.value() !=
1443                     boost::system::errc::no_such_file_or_directory)
1444                 {
1445                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1446                                      << ec.message();
1447                     messages::internalError(asyncResp->res);
1448                     return;
1449                 }
1450             }
1451             asyncResp->res.jsonValue["@odata.type"] =
1452                 "#LogEntryCollection.LogEntryCollection";
1453             asyncResp->res.jsonValue["@odata.id"] =
1454                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
1455             asyncResp->res.jsonValue["@odata.context"] =
1456                 "/redfish/v1/"
1457                 "$metadata#LogEntryCollection.LogEntryCollection";
1458             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
1459             asyncResp->res.jsonValue["Description"] =
1460                 "Collection of Crashdump Entries";
1461             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1462             logEntryArray = nlohmann::json::array();
1463             for (const std::string &objpath : resp)
1464             {
1465                 // Don't list the on-demand log
1466                 if (objpath.compare(CrashdumpOnDemandPath) == 0)
1467                 {
1468                     continue;
1469                 }
1470                 std::size_t lastPos = objpath.rfind("/");
1471                 if (lastPos != std::string::npos)
1472                 {
1473                     logEntryArray.push_back(
1474                         {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
1475                                        "Crashdump/Entries/" +
1476                                            objpath.substr(lastPos + 1)}});
1477                 }
1478             }
1479             asyncResp->res.jsonValue["Members@odata.count"] =
1480                 logEntryArray.size();
1481         };
1482         crow::connections::systemBus->async_method_call(
1483             std::move(getLogEntriesCallback),
1484             "xyz.openbmc_project.ObjectMapper",
1485             "/xyz/openbmc_project/object_mapper",
1486             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
1487             std::array<const char *, 1>{CrashdumpInterface});
1488     }
1489 };
1490 
1491 std::string getLogCreatedTime(const nlohmann::json &Crashdump)
1492 {
1493     nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data");
1494     if (cdIt != Crashdump.end())
1495     {
1496         nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1497         if (siIt != cdIt->end())
1498         {
1499             nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1500             if (tsIt != siIt->end())
1501             {
1502                 const std::string *logTime =
1503                     tsIt->get_ptr<const std::string *>();
1504                 if (logTime != nullptr)
1505                 {
1506                     return *logTime;
1507                 }
1508             }
1509         }
1510     }
1511     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1512 
1513     return std::string();
1514 }
1515 
1516 class CrashdumpEntry : public Node
1517 {
1518   public:
1519     CrashdumpEntry(CrowApp &app) :
1520         Node(app,
1521              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
1522              std::string())
1523     {
1524         entityPrivileges = {
1525             {boost::beast::http::verb::get, {{"Login"}}},
1526             {boost::beast::http::verb::head, {{"Login"}}},
1527             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1528             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1529             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1530             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1531     }
1532 
1533   private:
1534     void doGet(crow::Response &res, const crow::Request &req,
1535                const std::vector<std::string> &params) override
1536     {
1537         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1538         if (params.size() != 1)
1539         {
1540             messages::internalError(asyncResp->res);
1541             return;
1542         }
1543         const uint8_t logId = std::atoi(params[0].c_str());
1544         auto getStoredLogCallback = [asyncResp, logId](
1545                                         const boost::system::error_code ec,
1546                                         const std::variant<std::string> &resp) {
1547             if (ec)
1548             {
1549                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1550                 messages::internalError(asyncResp->res);
1551                 return;
1552             }
1553             const std::string *log = std::get_if<std::string>(&resp);
1554             if (log == nullptr)
1555             {
1556                 messages::internalError(asyncResp->res);
1557                 return;
1558             }
1559             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1560             if (j.is_discarded())
1561             {
1562                 messages::internalError(asyncResp->res);
1563                 return;
1564             }
1565             std::string t = getLogCreatedTime(j);
1566             asyncResp->res.jsonValue = {
1567                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1568                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1569                 {"@odata.id",
1570                  "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1571                      std::to_string(logId)},
1572                 {"Name", "CPU Crashdump"},
1573                 {"Id", logId},
1574                 {"EntryType", "Oem"},
1575                 {"OemRecordFormat", "Intel Crashdump"},
1576                 {"Oem", {{"Intel", std::move(j)}}},
1577                 {"Created", std::move(t)}};
1578         };
1579         crow::connections::systemBus->async_method_call(
1580             std::move(getStoredLogCallback), CrashdumpObject,
1581             CrashdumpPath + std::string("/") + std::to_string(logId),
1582             "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface,
1583             "Log");
1584     }
1585 };
1586 
1587 class OnDemandCrashdump : public Node
1588 {
1589   public:
1590     OnDemandCrashdump(CrowApp &app) :
1591         Node(app,
1592              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1593              "Crashdump.OnDemand/")
1594     {
1595         entityPrivileges = {
1596             {boost::beast::http::verb::get, {{"Login"}}},
1597             {boost::beast::http::verb::head, {{"Login"}}},
1598             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1599             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1600             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1601             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1602     }
1603 
1604   private:
1605     void doPost(crow::Response &res, const crow::Request &req,
1606                 const std::vector<std::string> &params) override
1607     {
1608         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1609         static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
1610 
1611         // Only allow one OnDemand Log request at a time
1612         if (onDemandLogMatcher != nullptr)
1613         {
1614             asyncResp->res.addHeader("Retry-After", "30");
1615             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
1616             return;
1617         }
1618         // Make this static so it survives outside this method
1619         static boost::asio::deadline_timer timeout(*req.ioService);
1620 
1621         timeout.expires_from_now(boost::posix_time::seconds(30));
1622         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
1623             onDemandLogMatcher = nullptr;
1624             if (ec)
1625             {
1626                 // operation_aborted is expected if timer is canceled before
1627                 // completion.
1628                 if (ec != boost::asio::error::operation_aborted)
1629                 {
1630                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1631                 }
1632                 return;
1633             }
1634             BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
1635 
1636             messages::internalError(asyncResp->res);
1637         });
1638 
1639         auto onDemandLogMatcherCallback = [asyncResp](
1640                                               sdbusplus::message::message &m) {
1641             BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
1642             boost::system::error_code ec;
1643             timeout.cancel(ec);
1644             if (ec)
1645             {
1646                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1647             }
1648             sdbusplus::message::object_path objPath;
1649             boost::container::flat_map<
1650                 std::string, boost::container::flat_map<
1651                                  std::string, std::variant<std::string>>>
1652                 interfacesAdded;
1653             m.read(objPath, interfacesAdded);
1654             const std::string *log = std::get_if<std::string>(
1655                 &interfacesAdded[CrashdumpInterface]["Log"]);
1656             if (log == nullptr)
1657             {
1658                 messages::internalError(asyncResp->res);
1659                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1660                 // match object inside which this lambda is executing.  Once it
1661                 // is set to nullptr, the match object will be destroyed and the
1662                 // lambda will lose its context, including res, so it needs to
1663                 // be the last thing done.
1664                 onDemandLogMatcher = nullptr;
1665                 return;
1666             }
1667             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1668             if (j.is_discarded())
1669             {
1670                 messages::internalError(asyncResp->res);
1671                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1672                 // match object inside which this lambda is executing.  Once it
1673                 // is set to nullptr, the match object will be destroyed and the
1674                 // lambda will lose its context, including res, so it needs to
1675                 // be the last thing done.
1676                 onDemandLogMatcher = nullptr;
1677                 return;
1678             }
1679             std::string t = getLogCreatedTime(j);
1680             asyncResp->res.jsonValue = {
1681                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1682                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1683                 {"Name", "CPU Crashdump"},
1684                 {"EntryType", "Oem"},
1685                 {"OemRecordFormat", "Intel Crashdump"},
1686                 {"Oem", {{"Intel", std::move(j)}}},
1687                 {"Created", std::move(t)}};
1688             // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1689             // match object inside which this lambda is executing.  Once it is
1690             // set to nullptr, the match object will be destroyed and the lambda
1691             // will lose its context, including res, so it needs to be the last
1692             // thing done.
1693             onDemandLogMatcher = nullptr;
1694         };
1695         onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1696             *crow::connections::systemBus,
1697             sdbusplus::bus::match::rules::interfacesAdded() +
1698                 sdbusplus::bus::match::rules::argNpath(0,
1699                                                        CrashdumpOnDemandPath),
1700             std::move(onDemandLogMatcherCallback));
1701 
1702         auto generateonDemandLogCallback =
1703             [asyncResp](const boost::system::error_code ec,
1704                         const std::string &resp) {
1705                 if (ec)
1706                 {
1707                     if (ec.value() ==
1708                         boost::system::errc::operation_not_supported)
1709                     {
1710                         messages::resourceInStandby(asyncResp->res);
1711                     }
1712                     else
1713                     {
1714                         messages::internalError(asyncResp->res);
1715                     }
1716                     boost::system::error_code timeoutec;
1717                     timeout.cancel(timeoutec);
1718                     if (timeoutec)
1719                     {
1720                         BMCWEB_LOG_ERROR << "error canceling timer "
1721                                          << timeoutec;
1722                     }
1723                     onDemandLogMatcher = nullptr;
1724                     return;
1725                 }
1726             };
1727         crow::connections::systemBus->async_method_call(
1728             std::move(generateonDemandLogCallback), CrashdumpObject,
1729             CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog");
1730     }
1731 };
1732 
1733 class SendRawPECI : public Node
1734 {
1735   public:
1736     SendRawPECI(CrowApp &app) :
1737         Node(app,
1738              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1739              "Crashdump.SendRawPeci/")
1740     {
1741         entityPrivileges = {
1742             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1743             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1744             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1745             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1746             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1747             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1748     }
1749 
1750   private:
1751     void doPost(crow::Response &res, const crow::Request &req,
1752                 const std::vector<std::string> &params) override
1753     {
1754         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1755         uint8_t clientAddress = 0;
1756         uint8_t readLength = 0;
1757         std::vector<uint8_t> peciCommand;
1758         if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1759                                  "ReadLength", readLength, "PECICommand",
1760                                  peciCommand))
1761         {
1762             return;
1763         }
1764 
1765         // Callback to return the Raw PECI response
1766         auto sendRawPECICallback =
1767             [asyncResp](const boost::system::error_code ec,
1768                         const std::vector<uint8_t> &resp) {
1769                 if (ec)
1770                 {
1771                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1772                                      << ec.message();
1773                     messages::internalError(asyncResp->res);
1774                     return;
1775                 }
1776                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1777                                             {"PECIResponse", resp}};
1778             };
1779         // Call the SendRawPECI command with the provided data
1780         crow::connections::systemBus->async_method_call(
1781             std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath,
1782             CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1783             peciCommand);
1784     }
1785 };
1786 
1787 /**
1788  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
1789  */
1790 class DBusLogServiceActionsClear : public Node
1791 {
1792   public:
1793     DBusLogServiceActionsClear(CrowApp &app) :
1794         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1795                   "LogService.Reset")
1796     {
1797         entityPrivileges = {
1798             {boost::beast::http::verb::get, {{"Login"}}},
1799             {boost::beast::http::verb::head, {{"Login"}}},
1800             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1801             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1802             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1803             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1804     }
1805 
1806   private:
1807     /**
1808      * Function handles POST method request.
1809      * The Clear Log actions does not require any parameter.The action deletes
1810      * all entries found in the Entries collection for this Log Service.
1811      */
1812     void doPost(crow::Response &res, const crow::Request &req,
1813                 const std::vector<std::string> &params) override
1814     {
1815         BMCWEB_LOG_DEBUG << "Do delete all entries.";
1816 
1817         auto asyncResp = std::make_shared<AsyncResp>(res);
1818         // Process response from Logging service.
1819         auto resp_handler = [asyncResp](const boost::system::error_code ec) {
1820             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
1821             if (ec)
1822             {
1823                 // TODO Handle for specific error code
1824                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
1825                 asyncResp->res.result(
1826                     boost::beast::http::status::internal_server_error);
1827                 return;
1828             }
1829 
1830             asyncResp->res.result(boost::beast::http::status::no_content);
1831         };
1832 
1833         // Make call to Logging service to request Clear Log
1834         crow::connections::systemBus->async_method_call(
1835             resp_handler, "xyz.openbmc_project.Logging",
1836             "/xyz/openbmc_project/logging",
1837             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1838     }
1839 };
1840 } // namespace redfish
1841