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