xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision cd225da87857c153e81f3abca6dfcc61c08272ea)
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     }
500 };
501 
502 static int fillEventLogEntryJson(const std::string &logEntryID,
503                                  const std::string logEntry,
504                                  nlohmann::json &logEntryJson)
505 {
506     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
507     // First get the Timestamp
508     size_t space = logEntry.find_first_of(" ");
509     if (space == std::string::npos)
510     {
511         return 1;
512     }
513     std::string timestamp = logEntry.substr(0, space);
514     // Then get the log contents
515     size_t entryStart = logEntry.find_first_not_of(" ", space);
516     if (entryStart == std::string::npos)
517     {
518         return 1;
519     }
520     std::string_view entry(logEntry);
521     entry.remove_prefix(entryStart);
522     // Use split to separate the entry into its fields
523     std::vector<std::string> logEntryFields;
524     boost::split(logEntryFields, entry, boost::is_any_of(","),
525                  boost::token_compress_on);
526     // We need at least a MessageId to be valid
527     if (logEntryFields.size() < 1)
528     {
529         return 1;
530     }
531     std::string &messageID = logEntryFields[0];
532     std::string &messageArgsStart = logEntryFields[1];
533     std::size_t messageArgsSize = logEntryFields.size() - 1;
534 
535     // Get the Message from the MessageRegistry
536     const message_registries::Message *message =
537         message_registries::getMessage(messageID);
538 
539     std::string msg;
540     std::string severity;
541     if (message != nullptr)
542     {
543         msg = message->message;
544         severity = message->severity;
545     }
546 
547     // Get the MessageArgs from the log
548     boost::beast::span messageArgs(&messageArgsStart, messageArgsSize);
549 
550     // Fill the MessageArgs into the Message
551     int i = 0;
552     for (const std::string &messageArg : messageArgs)
553     {
554         std::string argStr = "%" + std::to_string(++i);
555         size_t argPos = msg.find(argStr);
556         if (argPos != std::string::npos)
557         {
558             msg.replace(argPos, argStr.length(), messageArg);
559         }
560     }
561 
562     // Get the Created time from the timestamp. The log timestamp is in RFC3339
563     // format which matches the Redfish format except for the fractional seconds
564     // between the '.' and the '+', so just remove them.
565     std::size_t dot = timestamp.find_first_of(".");
566     std::size_t plus = timestamp.find_first_of("+");
567     if (dot != std::string::npos && plus != std::string::npos)
568     {
569         timestamp.erase(dot, plus - dot);
570     }
571 
572     // Fill in the log entry with the gathered data
573     logEntryJson = {
574         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
575         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
576         {"@odata.id",
577          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/#" +
578              logEntryID},
579         {"Name", "System Event Log Entry"},
580         {"Id", logEntryID},
581         {"Message", std::move(msg)},
582         {"MessageId", std::move(messageID)},
583         {"MessageArgs", std::move(messageArgs)},
584         {"EntryType", "Event"},
585         {"Severity", std::move(severity)},
586         {"Created", std::move(timestamp)}};
587     return 0;
588 }
589 
590 class EventLogEntryCollection : public Node
591 {
592   public:
593     template <typename CrowApp>
594     EventLogEntryCollection(CrowApp &app) :
595         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
596     {
597         entityPrivileges = {
598             {boost::beast::http::verb::get, {{"Login"}}},
599             {boost::beast::http::verb::head, {{"Login"}}},
600             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
601             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
602             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
603             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
604     }
605 
606   private:
607     void doGet(crow::Response &res, const crow::Request &req,
608                const std::vector<std::string> &params) override
609     {
610         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
611         long skip = 0;
612         long top = maxEntriesPerPage; // Show max entries by default
613         if (!getSkipParam(asyncResp->res, req, skip))
614         {
615             return;
616         }
617         if (!getTopParam(asyncResp->res, req, top))
618         {
619             return;
620         }
621         // Collections don't include the static data added by SubRoute because
622         // it has a duplicate entry for members
623         asyncResp->res.jsonValue["@odata.type"] =
624             "#LogEntryCollection.LogEntryCollection";
625         asyncResp->res.jsonValue["@odata.context"] =
626             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
627         asyncResp->res.jsonValue["@odata.id"] =
628             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
629         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
630         asyncResp->res.jsonValue["Description"] =
631             "Collection of System Event Log Entries";
632 
633 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
634         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
635         logEntryArray = nlohmann::json::array();
636         // Go through the log files and create a unique ID for each entry
637         std::vector<std::filesystem::path> redfishLogFiles;
638         getRedfishLogFiles(redfishLogFiles);
639         uint64_t entryCount = 0;
640         std::string logEntry;
641 
642         // Oldest logs are in the last file, so start there and loop backwards
643         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
644              it++)
645         {
646             std::ifstream logStream(*it);
647             if (!logStream.is_open())
648             {
649                 continue;
650             }
651 
652             while (std::getline(logStream, logEntry))
653             {
654                 entryCount++;
655                 // Handle paging using skip (number of entries to skip from the
656                 // start) and top (number of entries to display)
657                 if (entryCount <= skip || entryCount > skip + top)
658                 {
659                     continue;
660                 }
661 
662                 std::string idStr;
663                 if (!getUniqueEntryID(logEntry, idStr))
664                 {
665                     continue;
666                 }
667 
668                 logEntryArray.push_back({});
669                 nlohmann::json &bmcLogEntry = logEntryArray.back();
670                 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
671                 {
672                     messages::internalError(asyncResp->res);
673                     return;
674                 }
675             }
676         }
677         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
678         if (skip + top < entryCount)
679         {
680             asyncResp->res.jsonValue["Members@odata.nextLink"] =
681                 "/redfish/v1/Systems/system/LogServices/EventLog/"
682                 "Entries?$skip=" +
683                 std::to_string(skip + top);
684         }
685 #else
686         // DBus implementation of EventLog/Entries
687         // Make call to Logging Service to find all log entry objects
688         crow::connections::systemBus->async_method_call(
689             [asyncResp](const boost::system::error_code ec,
690                         GetManagedObjectsType &resp) {
691                 if (ec)
692                 {
693                     // TODO Handle for specific error code
694                     BMCWEB_LOG_ERROR
695                         << "getLogEntriesIfaceData resp_handler got error "
696                         << ec;
697                     messages::internalError(asyncResp->res);
698                     return;
699                 }
700                 nlohmann::json &entriesArray =
701                     asyncResp->res.jsonValue["Members"];
702                 entriesArray = nlohmann::json::array();
703                 for (auto &objectPath : resp)
704                 {
705                     for (auto &interfaceMap : objectPath.second)
706                     {
707                         if (interfaceMap.first !=
708                             "xyz.openbmc_project.Logging.Entry")
709                         {
710                             BMCWEB_LOG_DEBUG << "Bailing early on "
711                                              << interfaceMap.first;
712                             continue;
713                         }
714                         entriesArray.push_back({});
715                         nlohmann::json &thisEntry = entriesArray.back();
716                         uint32_t *id;
717                         std::time_t timestamp;
718                         std::string *severity, *message;
719                         bool *resolved;
720                         for (auto &propertyMap : interfaceMap.second)
721                         {
722                             if (propertyMap.first == "Id")
723                             {
724                                 id = sdbusplus::message::variant_ns::get_if<
725                                     uint32_t>(&propertyMap.second);
726                                 if (id == nullptr)
727                                 {
728                                     messages::propertyMissing(asyncResp->res,
729                                                               "Id");
730                                 }
731                             }
732                             else if (propertyMap.first == "Timestamp")
733                             {
734                                 const uint64_t *millisTimeStamp =
735                                     std::get_if<uint64_t>(&propertyMap.second);
736                                 if (millisTimeStamp == nullptr)
737                                 {
738                                     messages::propertyMissing(asyncResp->res,
739                                                               "Timestamp");
740                                 }
741                                 // Retrieve Created property with format:
742                                 // yyyy-mm-ddThh:mm:ss
743                                 std::chrono::milliseconds chronoTimeStamp(
744                                     *millisTimeStamp);
745                                 timestamp =
746                                     std::chrono::duration_cast<
747                                         std::chrono::seconds>(chronoTimeStamp)
748                                         .count();
749                             }
750                             else if (propertyMap.first == "Severity")
751                             {
752                                 severity = std::get_if<std::string>(
753                                     &propertyMap.second);
754                                 if (severity == nullptr)
755                                 {
756                                     messages::propertyMissing(asyncResp->res,
757                                                               "Severity");
758                                 }
759                             }
760                             else if (propertyMap.first == "Message")
761                             {
762                                 message = std::get_if<std::string>(
763                                     &propertyMap.second);
764                                 if (message == nullptr)
765                                 {
766                                     messages::propertyMissing(asyncResp->res,
767                                                               "Message");
768                                 }
769                             }
770                         }
771                         thisEntry = {
772                             {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
773                             {"@odata.context", "/redfish/v1/"
774                                                "$metadata#LogEntry.LogEntry"},
775                             {"@odata.id",
776                              "/redfish/v1/Systems/system/LogServices/EventLog/"
777                              "Entries/" +
778                                  std::to_string(*id)},
779                             {"Name", "System DBus Event Log Entry"},
780                             {"Id", std::to_string(*id)},
781                             {"Message", *message},
782                             {"EntryType", "Event"},
783                             {"Severity",
784                              translateSeverityDbusToRedfish(*severity)},
785                             {"Created", crow::utility::getDateTime(timestamp)}};
786                     }
787                 }
788                 std::sort(entriesArray.begin(), entriesArray.end(),
789                           [](const nlohmann::json &left,
790                              const nlohmann::json &right) {
791                               return (left["Id"] <= right["Id"]);
792                           });
793                 asyncResp->res.jsonValue["Members@odata.count"] =
794                     entriesArray.size();
795             },
796             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
797             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
798 #endif
799     }
800 };
801 
802 class EventLogEntry : public Node
803 {
804   public:
805     EventLogEntry(CrowApp &app) :
806         Node(app,
807              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
808              std::string())
809     {
810         entityPrivileges = {
811             {boost::beast::http::verb::get, {{"Login"}}},
812             {boost::beast::http::verb::head, {{"Login"}}},
813             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
814             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
815             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
816             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
817     }
818 
819   private:
820     void doGet(crow::Response &res, const crow::Request &req,
821                const std::vector<std::string> &params) override
822     {
823         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
824         if (params.size() != 1)
825         {
826             messages::internalError(asyncResp->res);
827             return;
828         }
829         const std::string &entryID = params[0];
830 
831 #ifdef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES
832         // DBus implementation of EventLog/Entries
833         // Make call to Logging Service to find all log entry objects
834         crow::connections::systemBus->async_method_call(
835             [asyncResp, entryID](const boost::system::error_code ec,
836                                  GetManagedPropertyType &resp) {
837                 if (ec)
838                 {
839                     BMCWEB_LOG_ERROR
840                         << "EventLogEntry (DBus) resp_handler got error " << ec;
841                     messages::internalError(asyncResp->res);
842                     return;
843                 }
844                 uint32_t *id;
845                 std::time_t timestamp;
846                 std::string *severity, *message;
847                 bool *resolved;
848                 for (auto &propertyMap : resp)
849                 {
850                     if (propertyMap.first == "Id")
851                     {
852                         id = std::get_if<uint32_t>(&propertyMap.second);
853                         if (id == nullptr)
854                         {
855                             messages::propertyMissing(asyncResp->res, "Id");
856                         }
857                     }
858                     else if (propertyMap.first == "Timestamp")
859                     {
860                         const uint64_t *millisTimeStamp =
861                             std::get_if<uint64_t>(&propertyMap.second);
862                         if (millisTimeStamp == nullptr)
863                         {
864                             messages::propertyMissing(asyncResp->res,
865                                                       "Timestamp");
866                         }
867                         // Retrieve Created property with format:
868                         // yyyy-mm-ddThh:mm:ss
869                         std::chrono::milliseconds chronoTimeStamp(
870                             *millisTimeStamp);
871                         timestamp =
872                             std::chrono::duration_cast<std::chrono::seconds>(
873                                 chronoTimeStamp)
874                                 .count();
875                     }
876                     else if (propertyMap.first == "Severity")
877                     {
878                         severity =
879                             std::get_if<std::string>(&propertyMap.second);
880                         if (severity == nullptr)
881                         {
882                             messages::propertyMissing(asyncResp->res,
883                                                       "Severity");
884                         }
885                     }
886                     else if (propertyMap.first == "Message")
887                     {
888                         message = std::get_if<std::string>(&propertyMap.second);
889                         if (message == nullptr)
890                         {
891                             messages::propertyMissing(asyncResp->res,
892                                                       "Message");
893                         }
894                     }
895                 }
896                 asyncResp->res.jsonValue = {
897                     {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
898                     {"@odata.context", "/redfish/v1/"
899                                        "$metadata#LogEntry.LogEntry"},
900                     {"@odata.id",
901                      "/redfish/v1/Systems/system/LogServices/EventLog/"
902                      "Entries/" +
903                          std::to_string(*id)},
904                     {"Name", "System DBus Event Log Entry"},
905                     {"Id", std::to_string(*id)},
906                     {"Message", *message},
907                     {"EntryType", "Event"},
908                     {"Severity", translateSeverityDbusToRedfish(*severity)},
909                     { "Created",
910                       crow::utility::getDateTime(timestamp) }};
911             },
912             "xyz.openbmc_project.Logging",
913             "/xyz/openbmc_project/logging/entry/" + entryID,
914             "org.freedesktop.DBus.Properties", "GetAll",
915             "xyz.openbmc_project.Logging.Entry");
916 #endif
917     }
918 };
919 
920 class BMCLogServiceCollection : public Node
921 {
922   public:
923     template <typename CrowApp>
924     BMCLogServiceCollection(CrowApp &app) :
925         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
926     {
927         entityPrivileges = {
928             {boost::beast::http::verb::get, {{"Login"}}},
929             {boost::beast::http::verb::head, {{"Login"}}},
930             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
931             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
932             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
933             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
934     }
935 
936   private:
937     /**
938      * Functions triggers appropriate requests on DBus
939      */
940     void doGet(crow::Response &res, const crow::Request &req,
941                const std::vector<std::string> &params) override
942     {
943         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
944         // Collections don't include the static data added by SubRoute because
945         // it has a duplicate entry for members
946         asyncResp->res.jsonValue["@odata.type"] =
947             "#LogServiceCollection.LogServiceCollection";
948         asyncResp->res.jsonValue["@odata.context"] =
949             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
950         asyncResp->res.jsonValue["@odata.id"] =
951             "/redfish/v1/Managers/bmc/LogServices";
952         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
953         asyncResp->res.jsonValue["Description"] =
954             "Collection of LogServices for this Manager";
955         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
956         logServiceArray = nlohmann::json::array();
957 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
958         logServiceArray.push_back(
959             {{ "@odata.id",
960                "/redfish/v1/Managers/bmc/LogServices/Journal" }});
961 #endif
962         asyncResp->res.jsonValue["Members@odata.count"] =
963             logServiceArray.size();
964     }
965 };
966 
967 class BMCJournalLogService : public Node
968 {
969   public:
970     template <typename CrowApp>
971     BMCJournalLogService(CrowApp &app) :
972         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
973     {
974         entityPrivileges = {
975             {boost::beast::http::verb::get, {{"Login"}}},
976             {boost::beast::http::verb::head, {{"Login"}}},
977             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
978             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
979             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
980             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
981     }
982 
983   private:
984     void doGet(crow::Response &res, const crow::Request &req,
985                const std::vector<std::string> &params) override
986     {
987         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
988         asyncResp->res.jsonValue["@odata.type"] =
989             "#LogService.v1_1_0.LogService";
990         asyncResp->res.jsonValue["@odata.id"] =
991             "/redfish/v1/Managers/bmc/LogServices/Journal";
992         asyncResp->res.jsonValue["@odata.context"] =
993             "/redfish/v1/$metadata#LogService.LogService";
994         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
995         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
996         asyncResp->res.jsonValue["Id"] = "BMC Journal";
997         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
998         asyncResp->res.jsonValue["Entries"] = {
999             {"@odata.id",
1000              "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}};
1001     }
1002 };
1003 
1004 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
1005                                       sd_journal *journal,
1006                                       nlohmann::json &bmcJournalLogEntryJson)
1007 {
1008     // Get the Log Entry contents
1009     int ret = 0;
1010 
1011     std::string_view msg;
1012     ret = getJournalMetadata(journal, "MESSAGE", msg);
1013     if (ret < 0)
1014     {
1015         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1016         return 1;
1017     }
1018 
1019     // Get the severity from the PRIORITY field
1020     int severity = 8; // Default to an invalid priority
1021     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
1022     if (ret < 0)
1023     {
1024         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1025         return 1;
1026     }
1027 
1028     // Get the Created time from the timestamp
1029     std::string entryTimeStr;
1030     if (!getEntryTimestamp(journal, entryTimeStr))
1031     {
1032         return 1;
1033     }
1034 
1035     // Fill in the log entry with the gathered data
1036     bmcJournalLogEntryJson = {
1037         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1038         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1039         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1040                           bmcJournalLogEntryID},
1041         {"Name", "BMC Journal Entry"},
1042         {"Id", bmcJournalLogEntryID},
1043         {"Message", msg},
1044         {"EntryType", "Oem"},
1045         {"Severity",
1046          severity <= 2 ? "Critical"
1047                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
1048         {"OemRecordFormat", "Intel BMC Journal Entry"},
1049         {"Created", std::move(entryTimeStr)}};
1050     return 0;
1051 }
1052 
1053 class BMCJournalLogEntryCollection : public Node
1054 {
1055   public:
1056     template <typename CrowApp>
1057     BMCJournalLogEntryCollection(CrowApp &app) :
1058         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
1059     {
1060         entityPrivileges = {
1061             {boost::beast::http::verb::get, {{"Login"}}},
1062             {boost::beast::http::verb::head, {{"Login"}}},
1063             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1064             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1065             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1066             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1067     }
1068 
1069   private:
1070     void doGet(crow::Response &res, const crow::Request &req,
1071                const std::vector<std::string> &params) override
1072     {
1073         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1074         static constexpr const long maxEntriesPerPage = 1000;
1075         long skip = 0;
1076         long top = maxEntriesPerPage; // Show max entries by default
1077         if (!getSkipParam(asyncResp->res, req, skip))
1078         {
1079             return;
1080         }
1081         if (!getTopParam(asyncResp->res, req, top))
1082         {
1083             return;
1084         }
1085         // Collections don't include the static data added by SubRoute because
1086         // it has a duplicate entry for members
1087         asyncResp->res.jsonValue["@odata.type"] =
1088             "#LogEntryCollection.LogEntryCollection";
1089         asyncResp->res.jsonValue["@odata.id"] =
1090             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1091         asyncResp->res.jsonValue["@odata.context"] =
1092             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
1093         asyncResp->res.jsonValue["@odata.id"] =
1094             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1095         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1096         asyncResp->res.jsonValue["Description"] =
1097             "Collection of BMC Journal Entries";
1098         asyncResp->res.jsonValue["@odata.id"] =
1099             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
1100         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1101         logEntryArray = nlohmann::json::array();
1102 
1103         // Go through the journal and use the timestamp to create a unique ID
1104         // for each entry
1105         sd_journal *journalTmp = nullptr;
1106         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1107         if (ret < 0)
1108         {
1109             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1110             messages::internalError(asyncResp->res);
1111             return;
1112         }
1113         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1114             journalTmp, sd_journal_close);
1115         journalTmp = nullptr;
1116         uint64_t entryCount = 0;
1117         SD_JOURNAL_FOREACH(journal.get())
1118         {
1119             entryCount++;
1120             // Handle paging using skip (number of entries to skip from the
1121             // start) and top (number of entries to display)
1122             if (entryCount <= skip || entryCount > skip + top)
1123             {
1124                 continue;
1125             }
1126 
1127             std::string idStr;
1128             if (!getUniqueEntryID(journal.get(), idStr))
1129             {
1130                 continue;
1131             }
1132 
1133             logEntryArray.push_back({});
1134             nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
1135             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1136                                            bmcJournalLogEntry) != 0)
1137             {
1138                 messages::internalError(asyncResp->res);
1139                 return;
1140             }
1141         }
1142         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1143         if (skip + top < entryCount)
1144         {
1145             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1146                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
1147                 std::to_string(skip + top);
1148         }
1149     }
1150 };
1151 
1152 class BMCJournalLogEntry : public Node
1153 {
1154   public:
1155     BMCJournalLogEntry(CrowApp &app) :
1156         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
1157              std::string())
1158     {
1159         entityPrivileges = {
1160             {boost::beast::http::verb::get, {{"Login"}}},
1161             {boost::beast::http::verb::head, {{"Login"}}},
1162             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1163             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1164             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1165             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1166     }
1167 
1168   private:
1169     void doGet(crow::Response &res, const crow::Request &req,
1170                const std::vector<std::string> &params) override
1171     {
1172         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1173         if (params.size() != 1)
1174         {
1175             messages::internalError(asyncResp->res);
1176             return;
1177         }
1178         const std::string &entryID = params[0];
1179         // Convert the unique ID back to a timestamp to find the entry
1180         uint64_t ts = 0;
1181         uint16_t index = 0;
1182         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
1183         {
1184             return;
1185         }
1186 
1187         sd_journal *journalTmp = nullptr;
1188         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1189         if (ret < 0)
1190         {
1191             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1192             messages::internalError(asyncResp->res);
1193             return;
1194         }
1195         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1196             journalTmp, sd_journal_close);
1197         journalTmp = nullptr;
1198         // Go to the timestamp in the log and move to the entry at the index
1199         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1200         for (int i = 0; i <= index; i++)
1201         {
1202             sd_journal_next(journal.get());
1203         }
1204         // Confirm that the entry ID matches what was requested
1205         std::string idStr;
1206         if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
1207         {
1208             messages::resourceMissingAtURI(asyncResp->res, entryID);
1209             return;
1210         }
1211 
1212         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1213                                        asyncResp->res.jsonValue) != 0)
1214         {
1215             messages::internalError(asyncResp->res);
1216             return;
1217         }
1218     }
1219 };
1220 
1221 class CrashdumpService : public Node
1222 {
1223   public:
1224     template <typename CrowApp>
1225     CrashdumpService(CrowApp &app) :
1226         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
1227     {
1228         entityPrivileges = {
1229             {boost::beast::http::verb::get, {{"Login"}}},
1230             {boost::beast::http::verb::head, {{"Login"}}},
1231             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1232             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1233             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1234             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1235     }
1236 
1237   private:
1238     /**
1239      * Functions triggers appropriate requests on DBus
1240      */
1241     void doGet(crow::Response &res, const crow::Request &req,
1242                const std::vector<std::string> &params) override
1243     {
1244         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1245         // Copy over the static data to include the entries added by SubRoute
1246         asyncResp->res.jsonValue["@odata.id"] =
1247             "/redfish/v1/Systems/system/LogServices/Crashdump";
1248         asyncResp->res.jsonValue["@odata.type"] =
1249             "#LogService.v1_1_0.LogService";
1250         asyncResp->res.jsonValue["@odata.context"] =
1251             "/redfish/v1/$metadata#LogService.LogService";
1252         asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service";
1253         asyncResp->res.jsonValue["Description"] = "Crashdump Service";
1254         asyncResp->res.jsonValue["Id"] = "Crashdump";
1255         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1256         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
1257         asyncResp->res.jsonValue["Entries"] = {
1258             {"@odata.id",
1259              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
1260         asyncResp->res.jsonValue["Actions"] = {
1261             {"Oem",
1262              {{"#Crashdump.OnDemand",
1263                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
1264                            "Actions/Oem/Crashdump.OnDemand"}}}}}};
1265 
1266 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
1267         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
1268             {"#Crashdump.SendRawPeci",
1269              { { "target",
1270                  "/redfish/v1/Systems/system/LogServices/Crashdump/"
1271                  "Actions/Oem/Crashdump.SendRawPeci" } }});
1272 #endif
1273     }
1274 };
1275 
1276 class CrashdumpEntryCollection : public Node
1277 {
1278   public:
1279     template <typename CrowApp>
1280     CrashdumpEntryCollection(CrowApp &app) :
1281         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
1282     {
1283         entityPrivileges = {
1284             {boost::beast::http::verb::get, {{"Login"}}},
1285             {boost::beast::http::verb::head, {{"Login"}}},
1286             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1287             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1288             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1289             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1290     }
1291 
1292   private:
1293     /**
1294      * Functions triggers appropriate requests on DBus
1295      */
1296     void doGet(crow::Response &res, const crow::Request &req,
1297                const std::vector<std::string> &params) override
1298     {
1299         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1300         // Collections don't include the static data added by SubRoute because
1301         // it has a duplicate entry for members
1302         auto getLogEntriesCallback = [asyncResp](
1303                                          const boost::system::error_code ec,
1304                                          const std::vector<std::string> &resp) {
1305             if (ec)
1306             {
1307                 if (ec.value() !=
1308                     boost::system::errc::no_such_file_or_directory)
1309                 {
1310                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
1311                                      << ec.message();
1312                     messages::internalError(asyncResp->res);
1313                     return;
1314                 }
1315             }
1316             asyncResp->res.jsonValue["@odata.type"] =
1317                 "#LogEntryCollection.LogEntryCollection";
1318             asyncResp->res.jsonValue["@odata.id"] =
1319                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
1320             asyncResp->res.jsonValue["@odata.context"] =
1321                 "/redfish/v1/"
1322                 "$metadata#LogEntryCollection.LogEntryCollection";
1323             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
1324             asyncResp->res.jsonValue["Description"] =
1325                 "Collection of Crashdump Entries";
1326             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1327             logEntryArray = nlohmann::json::array();
1328             for (const std::string &objpath : resp)
1329             {
1330                 // Don't list the on-demand log
1331                 if (objpath.compare(CrashdumpOnDemandPath) == 0)
1332                 {
1333                     continue;
1334                 }
1335                 std::size_t lastPos = objpath.rfind("/");
1336                 if (lastPos != std::string::npos)
1337                 {
1338                     logEntryArray.push_back(
1339                         {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
1340                                        "Crashdump/Entries/" +
1341                                            objpath.substr(lastPos + 1)}});
1342                 }
1343             }
1344             asyncResp->res.jsonValue["Members@odata.count"] =
1345                 logEntryArray.size();
1346         };
1347         crow::connections::systemBus->async_method_call(
1348             std::move(getLogEntriesCallback),
1349             "xyz.openbmc_project.ObjectMapper",
1350             "/xyz/openbmc_project/object_mapper",
1351             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
1352             std::array<const char *, 1>{CrashdumpInterface});
1353     }
1354 };
1355 
1356 std::string getLogCreatedTime(const nlohmann::json &Crashdump)
1357 {
1358     nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data");
1359     if (cdIt != Crashdump.end())
1360     {
1361         nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1362         if (siIt != cdIt->end())
1363         {
1364             nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1365             if (tsIt != siIt->end())
1366             {
1367                 const std::string *logTime =
1368                     tsIt->get_ptr<const std::string *>();
1369                 if (logTime != nullptr)
1370                 {
1371                     return *logTime;
1372                 }
1373             }
1374         }
1375     }
1376     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1377 
1378     return std::string();
1379 }
1380 
1381 class CrashdumpEntry : public Node
1382 {
1383   public:
1384     CrashdumpEntry(CrowApp &app) :
1385         Node(app,
1386              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
1387              std::string())
1388     {
1389         entityPrivileges = {
1390             {boost::beast::http::verb::get, {{"Login"}}},
1391             {boost::beast::http::verb::head, {{"Login"}}},
1392             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1393             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1394             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1395             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1396     }
1397 
1398   private:
1399     void doGet(crow::Response &res, const crow::Request &req,
1400                const std::vector<std::string> &params) override
1401     {
1402         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1403         if (params.size() != 1)
1404         {
1405             messages::internalError(asyncResp->res);
1406             return;
1407         }
1408         const uint8_t logId = std::atoi(params[0].c_str());
1409         auto getStoredLogCallback = [asyncResp, logId](
1410                                         const boost::system::error_code ec,
1411                                         const std::variant<std::string> &resp) {
1412             if (ec)
1413             {
1414                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1415                 messages::internalError(asyncResp->res);
1416                 return;
1417             }
1418             const std::string *log = std::get_if<std::string>(&resp);
1419             if (log == nullptr)
1420             {
1421                 messages::internalError(asyncResp->res);
1422                 return;
1423             }
1424             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1425             if (j.is_discarded())
1426             {
1427                 messages::internalError(asyncResp->res);
1428                 return;
1429             }
1430             std::string t = getLogCreatedTime(j);
1431             asyncResp->res.jsonValue = {
1432                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1433                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1434                 {"@odata.id",
1435                  "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
1436                      std::to_string(logId)},
1437                 {"Name", "CPU Crashdump"},
1438                 {"Id", logId},
1439                 {"EntryType", "Oem"},
1440                 {"OemRecordFormat", "Intel Crashdump"},
1441                 {"Oem", {{"Intel", std::move(j)}}},
1442                 {"Created", std::move(t)}};
1443         };
1444         crow::connections::systemBus->async_method_call(
1445             std::move(getStoredLogCallback), CrashdumpObject,
1446             CrashdumpPath + std::string("/") + std::to_string(logId),
1447             "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface,
1448             "Log");
1449     }
1450 };
1451 
1452 class OnDemandCrashdump : public Node
1453 {
1454   public:
1455     OnDemandCrashdump(CrowApp &app) :
1456         Node(app,
1457              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1458              "Crashdump.OnDemand/")
1459     {
1460         entityPrivileges = {
1461             {boost::beast::http::verb::get, {{"Login"}}},
1462             {boost::beast::http::verb::head, {{"Login"}}},
1463             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1464             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1465             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1466             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1467     }
1468 
1469   private:
1470     void doPost(crow::Response &res, const crow::Request &req,
1471                 const std::vector<std::string> &params) override
1472     {
1473         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1474         static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher;
1475 
1476         // Only allow one OnDemand Log request at a time
1477         if (onDemandLogMatcher != nullptr)
1478         {
1479             asyncResp->res.addHeader("Retry-After", "30");
1480             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
1481             return;
1482         }
1483         // Make this static so it survives outside this method
1484         static boost::asio::deadline_timer timeout(*req.ioService);
1485 
1486         timeout.expires_from_now(boost::posix_time::seconds(30));
1487         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
1488             onDemandLogMatcher = nullptr;
1489             if (ec)
1490             {
1491                 // operation_aborted is expected if timer is canceled before
1492                 // completion.
1493                 if (ec != boost::asio::error::operation_aborted)
1494                 {
1495                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1496                 }
1497                 return;
1498             }
1499             BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log";
1500 
1501             messages::internalError(asyncResp->res);
1502         });
1503 
1504         auto onDemandLogMatcherCallback = [asyncResp](
1505                                               sdbusplus::message::message &m) {
1506             BMCWEB_LOG_DEBUG << "OnDemand log available match fired";
1507             boost::system::error_code ec;
1508             timeout.cancel(ec);
1509             if (ec)
1510             {
1511                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1512             }
1513             sdbusplus::message::object_path objPath;
1514             boost::container::flat_map<
1515                 std::string, boost::container::flat_map<
1516                                  std::string, std::variant<std::string>>>
1517                 interfacesAdded;
1518             m.read(objPath, interfacesAdded);
1519             const std::string *log = std::get_if<std::string>(
1520                 &interfacesAdded[CrashdumpInterface]["Log"]);
1521             if (log == nullptr)
1522             {
1523                 messages::internalError(asyncResp->res);
1524                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1525                 // match object inside which this lambda is executing.  Once it
1526                 // is set to nullptr, the match object will be destroyed and the
1527                 // lambda will lose its context, including res, so it needs to
1528                 // be the last thing done.
1529                 onDemandLogMatcher = nullptr;
1530                 return;
1531             }
1532             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1533             if (j.is_discarded())
1534             {
1535                 messages::internalError(asyncResp->res);
1536                 // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1537                 // match object inside which this lambda is executing.  Once it
1538                 // is set to nullptr, the match object will be destroyed and the
1539                 // lambda will lose its context, including res, so it needs to
1540                 // be the last thing done.
1541                 onDemandLogMatcher = nullptr;
1542                 return;
1543             }
1544             std::string t = getLogCreatedTime(j);
1545             asyncResp->res.jsonValue = {
1546                 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1547                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1548                 {"Name", "CPU Crashdump"},
1549                 {"EntryType", "Oem"},
1550                 {"OemRecordFormat", "Intel Crashdump"},
1551                 {"Oem", {{"Intel", std::move(j)}}},
1552                 {"Created", std::move(t)}};
1553             // Careful with onDemandLogMatcher.  It is a unique_ptr to the
1554             // match object inside which this lambda is executing.  Once it is
1555             // set to nullptr, the match object will be destroyed and the lambda
1556             // will lose its context, including res, so it needs to be the last
1557             // thing done.
1558             onDemandLogMatcher = nullptr;
1559         };
1560         onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1561             *crow::connections::systemBus,
1562             sdbusplus::bus::match::rules::interfacesAdded() +
1563                 sdbusplus::bus::match::rules::argNpath(0,
1564                                                        CrashdumpOnDemandPath),
1565             std::move(onDemandLogMatcherCallback));
1566 
1567         auto generateonDemandLogCallback =
1568             [asyncResp](const boost::system::error_code ec,
1569                         const std::string &resp) {
1570                 if (ec)
1571                 {
1572                     if (ec.value() ==
1573                         boost::system::errc::operation_not_supported)
1574                     {
1575                         messages::resourceInStandby(asyncResp->res);
1576                     }
1577                     else
1578                     {
1579                         messages::internalError(asyncResp->res);
1580                     }
1581                     boost::system::error_code timeoutec;
1582                     timeout.cancel(timeoutec);
1583                     if (timeoutec)
1584                     {
1585                         BMCWEB_LOG_ERROR << "error canceling timer "
1586                                          << timeoutec;
1587                     }
1588                     onDemandLogMatcher = nullptr;
1589                     return;
1590                 }
1591             };
1592         crow::connections::systemBus->async_method_call(
1593             std::move(generateonDemandLogCallback), CrashdumpObject,
1594             CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog");
1595     }
1596 };
1597 
1598 class SendRawPECI : public Node
1599 {
1600   public:
1601     SendRawPECI(CrowApp &app) :
1602         Node(app,
1603              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
1604              "Crashdump.SendRawPeci/")
1605     {
1606         entityPrivileges = {
1607             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1608             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1609             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1610             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1611             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1612             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1613     }
1614 
1615   private:
1616     void doPost(crow::Response &res, const crow::Request &req,
1617                 const std::vector<std::string> &params) override
1618     {
1619         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1620         uint8_t clientAddress = 0;
1621         uint8_t readLength = 0;
1622         std::vector<uint8_t> peciCommand;
1623         if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1624                                  "ReadLength", readLength, "PECICommand",
1625                                  peciCommand))
1626         {
1627             return;
1628         }
1629 
1630         // Callback to return the Raw PECI response
1631         auto sendRawPECICallback =
1632             [asyncResp](const boost::system::error_code ec,
1633                         const std::vector<uint8_t> &resp) {
1634                 if (ec)
1635                 {
1636                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1637                                      << ec.message();
1638                     messages::internalError(asyncResp->res);
1639                     return;
1640                 }
1641                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1642                                             {"PECIResponse", resp}};
1643             };
1644         // Call the SendRawPECI command with the provided data
1645         crow::connections::systemBus->async_method_call(
1646             std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath,
1647             CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1648             peciCommand);
1649     }
1650 };
1651 
1652 /**
1653  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
1654  */
1655 class DBusLogServiceActionsClear : public Node
1656 {
1657   public:
1658     DBusLogServiceActionsClear(CrowApp &app) :
1659         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1660                   "LogService.Reset")
1661     {
1662         entityPrivileges = {
1663             {boost::beast::http::verb::get, {{"Login"}}},
1664             {boost::beast::http::verb::head, {{"Login"}}},
1665             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1666             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1667             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1668             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1669     }
1670 
1671   private:
1672     /**
1673      * Function handles POST method request.
1674      * The Clear Log actions does not require any parameter.The action deletes
1675      * all entries found in the Entries collection for this Log Service.
1676      */
1677     void doPost(crow::Response &res, const crow::Request &req,
1678                 const std::vector<std::string> &params) override
1679     {
1680         BMCWEB_LOG_DEBUG << "Do delete all entries.";
1681 
1682         auto asyncResp = std::make_shared<AsyncResp>(res);
1683         // Process response from Logging service.
1684         auto resp_handler = [asyncResp](const boost::system::error_code ec) {
1685             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
1686             if (ec)
1687             {
1688                 // TODO Handle for specific error code
1689                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
1690                 asyncResp->res.result(
1691                     boost::beast::http::status::internal_server_error);
1692                 return;
1693             }
1694 
1695             asyncResp->res.result(boost::beast::http::status::no_content);
1696         };
1697 
1698         // Make call to Logging service to request Clear Log
1699         crow::connections::systemBus->async_method_call(
1700             resp_handler, "xyz.openbmc_project.Logging",
1701             "/xyz/openbmc_project/logging",
1702             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1703     }
1704 };
1705 } // namespace redfish
1706