xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision c4d00437907d8f3054399487f344892c8413aea5)
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
590         // field
591         boost::string_view messageID;
592         ret =
593             getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
594         if (ret < 0)
595         {
596             messages::resourceNotFound(asyncResp->res, "LogEntry", "system");
597             return;
598         }
599 
600         if (fillEventLogEntryJson(entryID, messageID, journal.get(),
601                                   asyncResp->res.jsonValue) != 0)
602         {
603             messages::internalError(asyncResp->res);
604             return;
605         }
606     }
607 };
608 
609 class BMCLogServiceCollection : public Node
610 {
611   public:
612     template <typename CrowApp>
613     BMCLogServiceCollection(CrowApp &app) :
614         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
615     {
616         entityPrivileges = {
617             {boost::beast::http::verb::get, {{"Login"}}},
618             {boost::beast::http::verb::head, {{"Login"}}},
619             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
620             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
621             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
622             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
623     }
624 
625   private:
626     /**
627      * Functions triggers appropriate requests on DBus
628      */
629     void doGet(crow::Response &res, const crow::Request &req,
630                const std::vector<std::string> &params) override
631     {
632         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
633         // Collections don't include the static data added by SubRoute because
634         // it has a duplicate entry for members
635         asyncResp->res.jsonValue["@odata.type"] =
636             "#LogServiceCollection.LogServiceCollection";
637         asyncResp->res.jsonValue["@odata.context"] =
638             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
639         asyncResp->res.jsonValue["@odata.id"] =
640             "/redfish/v1/Managers/bmc/LogServices";
641         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
642         asyncResp->res.jsonValue["Description"] =
643             "Collection of LogServices for this Manager";
644         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
645         logServiceArray = nlohmann::json::array();
646 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
647         logServiceArray.push_back(
648             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
649 #endif
650 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
651         logServiceArray.push_back(
652             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
653 #endif
654         asyncResp->res.jsonValue["Members@odata.count"] =
655             logServiceArray.size();
656     }
657 };
658 
659 class BMCJournalLogService : public Node
660 {
661   public:
662     template <typename CrowApp>
663     BMCJournalLogService(CrowApp &app) :
664         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
665     {
666         entityPrivileges = {
667             {boost::beast::http::verb::get, {{"Login"}}},
668             {boost::beast::http::verb::head, {{"Login"}}},
669             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
670             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
671             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
672             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
673     }
674 
675   private:
676     void doGet(crow::Response &res, const crow::Request &req,
677                const std::vector<std::string> &params) override
678     {
679         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
680         asyncResp->res.jsonValue["@odata.type"] =
681             "#LogService.v1_1_0.LogService";
682         asyncResp->res.jsonValue["@odata.id"] =
683             "/redfish/v1/Managers/bmc/LogServices/Journal";
684         asyncResp->res.jsonValue["@odata.context"] =
685             "/redfish/v1/$metadata#LogService.LogService";
686         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
687         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
688         asyncResp->res.jsonValue["Id"] = "BMC Journal";
689         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
690     }
691 };
692 
693 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
694                                       sd_journal *journal,
695                                       nlohmann::json &bmcJournalLogEntryJson)
696 {
697     // Get the Log Entry contents
698     int ret = 0;
699 
700     boost::string_view msg;
701     ret = getJournalMetadata(journal, "MESSAGE", msg);
702     if (ret < 0)
703     {
704         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
705         return 1;
706     }
707 
708     // Get the severity from the PRIORITY field
709     int severity = 8; // Default to an invalid priority
710     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
711     if (ret < 0)
712     {
713         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
714         return 1;
715     }
716 
717     // Get the Created time from the timestamp
718     std::string entryTimeStr;
719     if (!getEntryTimestamp(journal, entryTimeStr))
720     {
721         return 1;
722     }
723 
724     // Fill in the log entry with the gathered data
725     bmcJournalLogEntryJson = {
726         {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
727         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
728         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
729                           bmcJournalLogEntryID},
730         {"Name", "BMC Journal Entry"},
731         {"Id", bmcJournalLogEntryID},
732         {"Message", msg},
733         {"EntryType", "Oem"},
734         {"Severity",
735          severity <= 2 ? "Critical"
736                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
737         {"OemRecordFormat", "Intel BMC Journal Entry"},
738         {"Created", std::move(entryTimeStr)}};
739     return 0;
740 }
741 
742 class BMCJournalLogEntryCollection : public Node
743 {
744   public:
745     template <typename CrowApp>
746     BMCJournalLogEntryCollection(CrowApp &app) :
747         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
748     {
749         entityPrivileges = {
750             {boost::beast::http::verb::get, {{"Login"}}},
751             {boost::beast::http::verb::head, {{"Login"}}},
752             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
753             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
754             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
755             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
756     }
757 
758   private:
759     void doGet(crow::Response &res, const crow::Request &req,
760                const std::vector<std::string> &params) override
761     {
762         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
763         static constexpr const long maxEntriesPerPage = 1000;
764         long skip = 0;
765         long top = maxEntriesPerPage; // Show max entries by default
766         if (!getSkipParam(asyncResp->res, req, skip))
767         {
768             return;
769         }
770         if (!getTopParam(asyncResp->res, req, top))
771         {
772             return;
773         }
774         // Collections don't include the static data added by SubRoute because
775         // it has a duplicate entry for members
776         asyncResp->res.jsonValue["@odata.type"] =
777             "#LogEntryCollection.LogEntryCollection";
778         asyncResp->res.jsonValue["@odata.id"] =
779             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
780         asyncResp->res.jsonValue["@odata.context"] =
781             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
782         asyncResp->res.jsonValue["@odata.id"] =
783             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
784         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
785         asyncResp->res.jsonValue["Description"] =
786             "Collection of BMC Journal Entries";
787         asyncResp->res.jsonValue["@odata.id"] =
788             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
789         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
790         logEntryArray = nlohmann::json::array();
791 
792         // Go through the journal and use the timestamp to create a unique ID
793         // for each entry
794         sd_journal *journalTmp = nullptr;
795         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
796         if (ret < 0)
797         {
798             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
799             messages::internalError(asyncResp->res);
800             return;
801         }
802         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
803             journalTmp, sd_journal_close);
804         journalTmp = nullptr;
805         uint64_t entryCount = 0;
806         SD_JOURNAL_FOREACH(journal.get())
807         {
808             entryCount++;
809             // Handle paging using skip (number of entries to skip from the
810             // start) and top (number of entries to display)
811             if (entryCount <= skip || entryCount > skip + top)
812             {
813                 continue;
814             }
815 
816             std::string idStr;
817             if (!getUniqueEntryID(journal.get(), idStr))
818             {
819                 continue;
820             }
821 
822             logEntryArray.push_back({});
823             nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
824             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
825                                            bmcJournalLogEntry) != 0)
826             {
827                 messages::internalError(asyncResp->res);
828                 return;
829             }
830         }
831         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
832         if (skip + top < entryCount)
833         {
834             asyncResp->res.jsonValue["Members@odata.nextLink"] =
835                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
836                 std::to_string(skip + top);
837         }
838     }
839 };
840 
841 class BMCJournalLogEntry : public Node
842 {
843   public:
844     BMCJournalLogEntry(CrowApp &app) :
845         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
846              std::string())
847     {
848         entityPrivileges = {
849             {boost::beast::http::verb::get, {{"Login"}}},
850             {boost::beast::http::verb::head, {{"Login"}}},
851             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
852             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
853             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
854             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
855     }
856 
857   private:
858     void doGet(crow::Response &res, const crow::Request &req,
859                const std::vector<std::string> &params) override
860     {
861         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
862         if (params.size() != 1)
863         {
864             messages::internalError(asyncResp->res);
865             return;
866         }
867         const std::string &entryID = params[0];
868         // Convert the unique ID back to a timestamp to find the entry
869         uint64_t ts = 0;
870         uint16_t index = 0;
871         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
872         {
873             return;
874         }
875 
876         sd_journal *journalTmp = nullptr;
877         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
878         if (ret < 0)
879         {
880             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
881             messages::internalError(asyncResp->res);
882             return;
883         }
884         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
885             journalTmp, sd_journal_close);
886         journalTmp = nullptr;
887         // Go to the timestamp in the log and move to the entry at the index
888         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
889         for (int i = 0; i <= index; i++)
890         {
891             sd_journal_next(journal.get());
892         }
893         // Confirm that the entry ID matches what was requested
894         std::string idStr;
895         if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
896         {
897             messages::resourceMissingAtURI(asyncResp->res, entryID);
898             return;
899         }
900 
901         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
902                                        asyncResp->res.jsonValue) != 0)
903         {
904             messages::internalError(asyncResp->res);
905             return;
906         }
907     }
908 };
909 
910 class CPULogService : public Node
911 {
912   public:
913     template <typename CrowApp>
914     CPULogService(CrowApp &app) :
915         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
916     {
917         entityPrivileges = {
918             {boost::beast::http::verb::get, {{"Login"}}},
919             {boost::beast::http::verb::head, {{"Login"}}},
920             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
921             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
922             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
923             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
924     }
925 
926   private:
927     /**
928      * Functions triggers appropriate requests on DBus
929      */
930     void doGet(crow::Response &res, const crow::Request &req,
931                const std::vector<std::string> &params) override
932     {
933         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
934         // Copy over the static data to include the entries added by SubRoute
935         asyncResp->res.jsonValue["@odata.id"] =
936             "/redfish/v1/Managers/bmc/LogServices/CpuLog";
937         asyncResp->res.jsonValue["@odata.type"] =
938             "#LogService.v1_1_0.LogService";
939         asyncResp->res.jsonValue["@odata.context"] =
940             "/redfish/v1/$metadata#LogService.LogService";
941         asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
942         asyncResp->res.jsonValue["Description"] = "CPU Log Service";
943         asyncResp->res.jsonValue["Id"] = "CPU Log";
944         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
945         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
946         asyncResp->res.jsonValue["Actions"] = {
947             {"Oem",
948              {{"#CpuLog.Immediate",
949                {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/"
950                            "Actions/Oem/CpuLog.Immediate"}}}}}};
951 
952 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
953         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
954             {"#CpuLog.SendRawPeci",
955              {{"target", "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/"
956                          "Oem/CpuLog.SendRawPeci"}}});
957 #endif
958     }
959 };
960 
961 class CPULogEntryCollection : public Node
962 {
963   public:
964     template <typename CrowApp>
965     CPULogEntryCollection(CrowApp &app) :
966         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
967     {
968         entityPrivileges = {
969             {boost::beast::http::verb::get, {{"Login"}}},
970             {boost::beast::http::verb::head, {{"Login"}}},
971             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
972             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
973             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
974             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
975     }
976 
977   private:
978     /**
979      * Functions triggers appropriate requests on DBus
980      */
981     void doGet(crow::Response &res, const crow::Request &req,
982                const std::vector<std::string> &params) override
983     {
984         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
985         // Collections don't include the static data added by SubRoute because
986         // it has a duplicate entry for members
987         auto getLogEntriesCallback = [asyncResp](
988                                          const boost::system::error_code ec,
989                                          const std::vector<std::string> &resp) {
990             if (ec)
991             {
992                 if (ec.value() !=
993                     boost::system::errc::no_such_file_or_directory)
994                 {
995                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
996                                      << ec.message();
997                     messages::internalError(asyncResp->res);
998                     return;
999                 }
1000             }
1001             asyncResp->res.jsonValue["@odata.type"] =
1002                 "#LogEntryCollection.LogEntryCollection";
1003             asyncResp->res.jsonValue["@odata.id"] =
1004                 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
1005             asyncResp->res.jsonValue["@odata.context"] =
1006                 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
1007             asyncResp->res.jsonValue["@odata.id"] =
1008                 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
1009             asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1010             asyncResp->res.jsonValue["Description"] =
1011                 "Collection of CPU Log Entries";
1012             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1013             logEntryArray = nlohmann::json::array();
1014             for (const std::string &objpath : resp)
1015             {
1016                 // Don't list the immediate log
1017                 if (objpath.compare(cpuLogImmediatePath) == 0)
1018                 {
1019                     continue;
1020                 }
1021                 std::size_t lastPos = objpath.rfind("/");
1022                 if (lastPos != std::string::npos)
1023                 {
1024                     logEntryArray.push_back(
1025                         {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
1026                                        "CpuLog/Entries/" +
1027                                            objpath.substr(lastPos + 1)}});
1028                 }
1029             }
1030             asyncResp->res.jsonValue["Members@odata.count"] =
1031                 logEntryArray.size();
1032         };
1033         crow::connections::systemBus->async_method_call(
1034             std::move(getLogEntriesCallback),
1035             "xyz.openbmc_project.ObjectMapper",
1036             "/xyz/openbmc_project/object_mapper",
1037             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
1038             std::array<const char *, 1>{cpuLogInterface});
1039     }
1040 };
1041 
1042 std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1043 {
1044     nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data");
1045     if (cdIt != cpuLog.end())
1046     {
1047         nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1048         if (siIt != cdIt->end())
1049         {
1050             nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1051             if (tsIt != siIt->end())
1052             {
1053                 const std::string *logTime =
1054                     tsIt->get_ptr<const std::string *>();
1055                 if (logTime != nullptr)
1056                 {
1057                     return *logTime;
1058                 }
1059             }
1060         }
1061     }
1062     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1063 
1064     return std::string();
1065 }
1066 
1067 class CPULogEntry : public Node
1068 {
1069   public:
1070     CPULogEntry(CrowApp &app) :
1071         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
1072              std::string())
1073     {
1074         entityPrivileges = {
1075             {boost::beast::http::verb::get, {{"Login"}}},
1076             {boost::beast::http::verb::head, {{"Login"}}},
1077             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1078             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1079             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1080             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1081     }
1082 
1083   private:
1084     void doGet(crow::Response &res, const crow::Request &req,
1085                const std::vector<std::string> &params) override
1086     {
1087         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1088         if (params.size() != 1)
1089         {
1090             messages::internalError(asyncResp->res);
1091             return;
1092         }
1093         const uint8_t logId = std::atoi(params[0].c_str());
1094         auto getStoredLogCallback = [asyncResp, logId](
1095                                         const boost::system::error_code ec,
1096                                         const std::variant<std::string> &resp) {
1097             if (ec)
1098             {
1099                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1100                 messages::internalError(asyncResp->res);
1101                 return;
1102             }
1103             const std::string *log = std::get_if<std::string>(&resp);
1104             if (log == nullptr)
1105             {
1106                 messages::internalError(asyncResp->res);
1107                 return;
1108             }
1109             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1110             if (j.is_discarded())
1111             {
1112                 messages::internalError(asyncResp->res);
1113                 return;
1114             }
1115             std::string t = getLogCreatedTime(j);
1116             asyncResp->res.jsonValue = {
1117                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1118                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1119                 {"@odata.id",
1120                  "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
1121                      std::to_string(logId)},
1122                 {"Name", "CPU Debug Log"},
1123                 {"Id", logId},
1124                 {"EntryType", "Oem"},
1125                 {"OemRecordFormat", "Intel CPU Log"},
1126                 {"Oem", {{"Intel", std::move(j)}}},
1127                 {"Created", std::move(t)}};
1128         };
1129         crow::connections::systemBus->async_method_call(
1130             std::move(getStoredLogCallback), cpuLogObject,
1131             cpuLogPath + std::string("/") + std::to_string(logId),
1132             "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
1133     }
1134 };
1135 
1136 class ImmediateCPULog : public Node
1137 {
1138   public:
1139     ImmediateCPULog(CrowApp &app) :
1140         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
1141                   "CpuLog.Immediate/")
1142     {
1143         entityPrivileges = {
1144             {boost::beast::http::verb::get, {{"Login"}}},
1145             {boost::beast::http::verb::head, {{"Login"}}},
1146             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1147             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1148             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1149             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1150     }
1151 
1152   private:
1153     void doPost(crow::Response &res, const crow::Request &req,
1154                 const std::vector<std::string> &params) override
1155     {
1156         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1157         static std::unique_ptr<sdbusplus::bus::match::match>
1158             immediateLogMatcher;
1159 
1160         // Only allow one Immediate Log request at a time
1161         if (immediateLogMatcher != nullptr)
1162         {
1163             asyncResp->res.addHeader("Retry-After", "30");
1164             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
1165             return;
1166         }
1167         // Make this static so it survives outside this method
1168         static boost::asio::deadline_timer timeout(*req.ioService);
1169 
1170         timeout.expires_from_now(boost::posix_time::seconds(30));
1171         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
1172             immediateLogMatcher = nullptr;
1173             if (ec)
1174             {
1175                 // operation_aborted is expected if timer is canceled before
1176                 // completion.
1177                 if (ec != boost::asio::error::operation_aborted)
1178                 {
1179                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1180                 }
1181                 return;
1182             }
1183             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1184 
1185             messages::internalError(asyncResp->res);
1186         });
1187 
1188         auto immediateLogMatcherCallback = [asyncResp](
1189                                                sdbusplus::message::message &m) {
1190             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1191             boost::system::error_code ec;
1192             timeout.cancel(ec);
1193             if (ec)
1194             {
1195                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1196             }
1197             sdbusplus::message::object_path objPath;
1198             boost::container::flat_map<
1199                 std::string, boost::container::flat_map<
1200                                  std::string, std::variant<std::string>>>
1201                 interfacesAdded;
1202             m.read(objPath, interfacesAdded);
1203             const std::string *log = std::get_if<std::string>(
1204                 &interfacesAdded[cpuLogInterface]["Log"]);
1205             if (log == nullptr)
1206             {
1207                 messages::internalError(asyncResp->res);
1208                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
1209                 // match object inside which this lambda is executing.  Once it
1210                 // is set to nullptr, the match object will be destroyed and the
1211                 // lambda will lose its context, including res, so it needs to
1212                 // be the last thing done.
1213                 immediateLogMatcher = nullptr;
1214                 return;
1215             }
1216             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1217             if (j.is_discarded())
1218             {
1219                 messages::internalError(asyncResp->res);
1220                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
1221                 // match object inside which this lambda is executing.  Once it
1222                 // is set to nullptr, the match object will be destroyed and the
1223                 // lambda will lose its context, including res, so it needs to
1224                 // be the last thing done.
1225                 immediateLogMatcher = nullptr;
1226                 return;
1227             }
1228             std::string t = getLogCreatedTime(j);
1229             asyncResp->res.jsonValue = {
1230                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1231                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1232                 {"Name", "CPU Debug Log"},
1233                 {"EntryType", "Oem"},
1234                 {"OemRecordFormat", "Intel CPU Log"},
1235                 {"Oem", {{"Intel", std::move(j)}}},
1236                 {"Created", std::move(t)}};
1237             // Careful with immediateLogMatcher.  It is a unique_ptr to the
1238             // match object inside which this lambda is executing.  Once it is
1239             // set to nullptr, the match object will be destroyed and the lambda
1240             // will lose its context, including res, so it needs to be the last
1241             // thing done.
1242             immediateLogMatcher = nullptr;
1243         };
1244         immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1245             *crow::connections::systemBus,
1246             sdbusplus::bus::match::rules::interfacesAdded() +
1247                 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
1248             std::move(immediateLogMatcherCallback));
1249 
1250         auto generateImmediateLogCallback =
1251             [asyncResp](const boost::system::error_code ec,
1252                         const std::string &resp) {
1253                 if (ec)
1254                 {
1255                     if (ec.value() ==
1256                         boost::system::errc::operation_not_supported)
1257                     {
1258                         messages::resourceInStandby(asyncResp->res);
1259                     }
1260                     else
1261                     {
1262                         messages::internalError(asyncResp->res);
1263                     }
1264                     boost::system::error_code timeoutec;
1265                     timeout.cancel(timeoutec);
1266                     if (timeoutec)
1267                     {
1268                         BMCWEB_LOG_ERROR << "error canceling timer "
1269                                          << timeoutec;
1270                     }
1271                     immediateLogMatcher = nullptr;
1272                     return;
1273                 }
1274             };
1275         crow::connections::systemBus->async_method_call(
1276             std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1277             cpuLogImmediateInterface, "GenerateImmediateLog");
1278     }
1279 };
1280 
1281 class SendRawPECI : public Node
1282 {
1283   public:
1284     SendRawPECI(CrowApp &app) :
1285         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
1286                   "CpuLog.SendRawPeci/")
1287     {
1288         entityPrivileges = {
1289             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1290             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1291             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1292             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1293             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1294             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1295     }
1296 
1297   private:
1298     void doPost(crow::Response &res, const crow::Request &req,
1299                 const std::vector<std::string> &params) override
1300     {
1301         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1302         uint8_t clientAddress = 0;
1303         uint8_t readLength = 0;
1304         std::vector<uint8_t> peciCommand;
1305         if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1306                                  "ReadLength", readLength, "PECICommand",
1307                                  peciCommand))
1308         {
1309             return;
1310         }
1311 
1312         // Callback to return the Raw PECI response
1313         auto sendRawPECICallback =
1314             [asyncResp](const boost::system::error_code ec,
1315                         const std::vector<uint8_t> &resp) {
1316                 if (ec)
1317                 {
1318                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1319                                      << ec.message();
1320                     messages::internalError(asyncResp->res);
1321                     return;
1322                 }
1323                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1324                                             {"PECIResponse", resp}};
1325             };
1326         // Call the SendRawPECI command with the provided data
1327         crow::connections::systemBus->async_method_call(
1328             std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1329             cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1330             peciCommand);
1331     }
1332 };
1333 
1334 } // namespace redfish
1335