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