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