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