xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision b01bf2991955ef267ce2be8e7a18eac984990de8)
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 std::string_view &field,
43                               std::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 = std::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 std::string_view &field, const int &base,
63                               int &contents)
64 {
65     int ret = 0;
66     std::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     std::string_view t1(entryTime);
98     std::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 = std::string(t1) + ":" + std::string(t2);
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     std::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         std::string_view indexStr(entryID);
212         indexStr.remove_prefix(underscorePos + 1);
213         std::size_t pos;
214         try
215         {
216             index = std::stoul(std::string(indexStr), &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(std::string(tsStr), &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 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
298         logServiceArray.push_back(
299             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/CpuLog"}});
300 #endif
301         asyncResp->res.jsonValue["Members@odata.count"] =
302             logServiceArray.size();
303     }
304 };
305 
306 class EventLogService : public Node
307 {
308   public:
309     template <typename CrowApp>
310     EventLogService(CrowApp &app) :
311         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
312     {
313         entityPrivileges = {
314             {boost::beast::http::verb::get, {{"Login"}}},
315             {boost::beast::http::verb::head, {{"Login"}}},
316             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
317             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
318             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
319             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
320     }
321 
322   private:
323     void doGet(crow::Response &res, const crow::Request &req,
324                const std::vector<std::string> &params) override
325     {
326         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
327 
328         asyncResp->res.jsonValue["@odata.id"] =
329             "/redfish/v1/Systems/system/LogServices/EventLog";
330         asyncResp->res.jsonValue["@odata.type"] =
331             "#LogService.v1_1_0.LogService";
332         asyncResp->res.jsonValue["@odata.context"] =
333             "/redfish/v1/$metadata#LogService.LogService";
334         asyncResp->res.jsonValue["Name"] = "Event Log Service";
335         asyncResp->res.jsonValue["Description"] = "System Event Log Service";
336         asyncResp->res.jsonValue["Id"] = "Event Log";
337         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
338         asyncResp->res.jsonValue["Entries"] = {
339             {"@odata.id",
340              "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
341     }
342 };
343 
344 static int fillEventLogEntryJson(const std::string &bmcLogEntryID,
345                                  const std::string_view &messageID,
346                                  sd_journal *journal,
347                                  nlohmann::json &bmcLogEntryJson)
348 {
349     // Get the Log Entry contents
350     int ret = 0;
351 
352     std::string_view msg;
353     ret = getJournalMetadata(journal, "MESSAGE", msg);
354     if (ret < 0)
355     {
356         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
357         return 1;
358     }
359 
360     // Get the severity from the PRIORITY field
361     int severity = 8; // Default to an invalid priority
362     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
363     if (ret < 0)
364     {
365         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
366         return 1;
367     }
368 
369     // Get the MessageArgs from the journal entry by finding all of the
370     // REDFISH_MESSAGE_ARG_x fields
371     const void *data;
372     size_t length;
373     std::vector<std::string> messageArgs;
374     SD_JOURNAL_FOREACH_DATA(journal, data, length)
375     {
376         std::string_view field(static_cast<const char *>(data), length);
377         if (boost::starts_with(field, "REDFISH_MESSAGE_ARG_"))
378         {
379             // Get the Arg number from the field name
380             field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1);
381             if (field.empty())
382             {
383                 continue;
384             }
385             int argNum = std::strtoul(field.data(), nullptr, 10);
386             if (argNum == 0)
387             {
388                 continue;
389             }
390             // Get the Arg value after the "=" character.
391             field.remove_prefix(std::min(field.find("=") + 1, field.size()));
392             // Make sure we have enough space in messageArgs
393             if (argNum > messageArgs.size())
394             {
395                 messageArgs.resize(argNum);
396             }
397             messageArgs[argNum - 1] = std::string(field);
398         }
399     }
400 
401     // Get the Created time from the timestamp
402     std::string entryTimeStr;
403     if (!getEntryTimestamp(journal, entryTimeStr))
404     {
405         return 1;
406     }
407 
408     // Fill in the log entry with the gathered data
409     bmcLogEntryJson = {
410         {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
411         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
412         {"@odata.id",
413          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
414              bmcLogEntryID},
415         {"Name", "System Event Log Entry"},
416         {"Id", bmcLogEntryID},
417         {"Message", msg},
418         {"MessageId", messageID},
419         {"MessageArgs", std::move(messageArgs)},
420         {"EntryType", "Event"},
421         {"Severity",
422          severity <= 2 ? "Critical"
423                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
424         {"Created", std::move(entryTimeStr)}};
425     return 0;
426 }
427 
428 class EventLogEntryCollection : public Node
429 {
430   public:
431     template <typename CrowApp>
432     EventLogEntryCollection(CrowApp &app) :
433         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
434     {
435         entityPrivileges = {
436             {boost::beast::http::verb::get, {{"Login"}}},
437             {boost::beast::http::verb::head, {{"Login"}}},
438             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
439             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
440             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
441             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
442     }
443 
444   private:
445     void doGet(crow::Response &res, const crow::Request &req,
446                const std::vector<std::string> &params) override
447     {
448         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
449         long skip = 0;
450         long top = maxEntriesPerPage; // Show max entries by default
451         if (!getSkipParam(asyncResp->res, req, skip))
452         {
453             return;
454         }
455         if (!getTopParam(asyncResp->res, req, top))
456         {
457             return;
458         }
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/system/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             std::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(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/system/LogServices/EventLog/Entries/<str>/",
537              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() != 1)
554         {
555             messages::internalError(asyncResp->res);
556             return;
557         }
558         const std::string &entryID = params[0];
559         // Convert the unique ID back to a timestamp to find the entry
560         uint64_t ts = 0;
561         uint16_t index = 0;
562         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
563         {
564             return;
565         }
566 
567         sd_journal *journalTmp = nullptr;
568         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
569         if (ret < 0)
570         {
571             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
572             messages::internalError(asyncResp->res);
573             return;
574         }
575         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
576             journalTmp, sd_journal_close);
577         journalTmp = nullptr;
578         // Go to the timestamp in the log and move to the entry at the index
579         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
580         for (int i = 0; i <= index; i++)
581         {
582             sd_journal_next(journal.get());
583         }
584         // Confirm that the entry ID matches what was requested
585         std::string idStr;
586         if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
587         {
588             messages::resourceMissingAtURI(asyncResp->res, entryID);
589             return;
590         }
591 
592         // only use journal entries that contain a REDFISH_MESSAGE_ID field
593         std::string_view messageID;
594         ret =
595             getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID);
596         if (ret < 0)
597         {
598             messages::resourceNotFound(asyncResp->res, "LogEntry", "system");
599             return;
600         }
601 
602         if (fillEventLogEntryJson(entryID, messageID, journal.get(),
603                                   asyncResp->res.jsonValue) != 0)
604         {
605             messages::internalError(asyncResp->res);
606             return;
607         }
608     }
609 };
610 
611 class BMCLogServiceCollection : public Node
612 {
613   public:
614     template <typename CrowApp>
615     BMCLogServiceCollection(CrowApp &app) :
616         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
617     {
618         entityPrivileges = {
619             {boost::beast::http::verb::get, {{"Login"}}},
620             {boost::beast::http::verb::head, {{"Login"}}},
621             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
622             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
623             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
624             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
625     }
626 
627   private:
628     /**
629      * Functions triggers appropriate requests on DBus
630      */
631     void doGet(crow::Response &res, const crow::Request &req,
632                const std::vector<std::string> &params) override
633     {
634         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
635         // Collections don't include the static data added by SubRoute because
636         // it has a duplicate entry for members
637         asyncResp->res.jsonValue["@odata.type"] =
638             "#LogServiceCollection.LogServiceCollection";
639         asyncResp->res.jsonValue["@odata.context"] =
640             "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection";
641         asyncResp->res.jsonValue["@odata.id"] =
642             "/redfish/v1/Managers/bmc/LogServices";
643         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
644         asyncResp->res.jsonValue["Description"] =
645             "Collection of LogServices for this Manager";
646         nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"];
647         logServiceArray = nlohmann::json::array();
648 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
649         logServiceArray.push_back(
650             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
651 #endif
652         asyncResp->res.jsonValue["Members@odata.count"] =
653             logServiceArray.size();
654     }
655 };
656 
657 class BMCJournalLogService : public Node
658 {
659   public:
660     template <typename CrowApp>
661     BMCJournalLogService(CrowApp &app) :
662         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
663     {
664         entityPrivileges = {
665             {boost::beast::http::verb::get, {{"Login"}}},
666             {boost::beast::http::verb::head, {{"Login"}}},
667             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
668             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
669             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
670             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
671     }
672 
673   private:
674     void doGet(crow::Response &res, const crow::Request &req,
675                const std::vector<std::string> &params) override
676     {
677         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
678         asyncResp->res.jsonValue["@odata.type"] =
679             "#LogService.v1_1_0.LogService";
680         asyncResp->res.jsonValue["@odata.id"] =
681             "/redfish/v1/Managers/bmc/LogServices/Journal";
682         asyncResp->res.jsonValue["@odata.context"] =
683             "/redfish/v1/$metadata#LogService.LogService";
684         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
685         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
686         asyncResp->res.jsonValue["Id"] = "BMC Journal";
687         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
688         asyncResp->res.jsonValue["Entries"] = {
689             {"@odata.id",
690              "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}};
691     }
692 };
693 
694 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID,
695                                       sd_journal *journal,
696                                       nlohmann::json &bmcJournalLogEntryJson)
697 {
698     // Get the Log Entry contents
699     int ret = 0;
700 
701     std::string_view msg;
702     ret = getJournalMetadata(journal, "MESSAGE", msg);
703     if (ret < 0)
704     {
705         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
706         return 1;
707     }
708 
709     // Get the severity from the PRIORITY field
710     int severity = 8; // Default to an invalid priority
711     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
712     if (ret < 0)
713     {
714         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
715         return 1;
716     }
717 
718     // Get the Created time from the timestamp
719     std::string entryTimeStr;
720     if (!getEntryTimestamp(journal, entryTimeStr))
721     {
722         return 1;
723     }
724 
725     // Fill in the log entry with the gathered data
726     bmcJournalLogEntryJson = {
727         {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
728         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
729         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
730                           bmcJournalLogEntryID},
731         {"Name", "BMC Journal Entry"},
732         {"Id", bmcJournalLogEntryID},
733         {"Message", msg},
734         {"EntryType", "Oem"},
735         {"Severity",
736          severity <= 2 ? "Critical"
737                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
738         {"OemRecordFormat", "Intel BMC Journal Entry"},
739         {"Created", std::move(entryTimeStr)}};
740     return 0;
741 }
742 
743 class BMCJournalLogEntryCollection : public Node
744 {
745   public:
746     template <typename CrowApp>
747     BMCJournalLogEntryCollection(CrowApp &app) :
748         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
749     {
750         entityPrivileges = {
751             {boost::beast::http::verb::get, {{"Login"}}},
752             {boost::beast::http::verb::head, {{"Login"}}},
753             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
754             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
755             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
756             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
757     }
758 
759   private:
760     void doGet(crow::Response &res, const crow::Request &req,
761                const std::vector<std::string> &params) override
762     {
763         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
764         static constexpr const long maxEntriesPerPage = 1000;
765         long skip = 0;
766         long top = maxEntriesPerPage; // Show max entries by default
767         if (!getSkipParam(asyncResp->res, req, skip))
768         {
769             return;
770         }
771         if (!getTopParam(asyncResp->res, req, top))
772         {
773             return;
774         }
775         // Collections don't include the static data added by SubRoute because
776         // it has a duplicate entry for members
777         asyncResp->res.jsonValue["@odata.type"] =
778             "#LogEntryCollection.LogEntryCollection";
779         asyncResp->res.jsonValue["@odata.id"] =
780             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
781         asyncResp->res.jsonValue["@odata.context"] =
782             "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection";
783         asyncResp->res.jsonValue["@odata.id"] =
784             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
785         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
786         asyncResp->res.jsonValue["Description"] =
787             "Collection of BMC Journal Entries";
788         asyncResp->res.jsonValue["@odata.id"] =
789             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
790         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
791         logEntryArray = nlohmann::json::array();
792 
793         // Go through the journal and use the timestamp to create a unique ID
794         // for each entry
795         sd_journal *journalTmp = nullptr;
796         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
797         if (ret < 0)
798         {
799             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
800             messages::internalError(asyncResp->res);
801             return;
802         }
803         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
804             journalTmp, sd_journal_close);
805         journalTmp = nullptr;
806         uint64_t entryCount = 0;
807         SD_JOURNAL_FOREACH(journal.get())
808         {
809             entryCount++;
810             // Handle paging using skip (number of entries to skip from the
811             // start) and top (number of entries to display)
812             if (entryCount <= skip || entryCount > skip + top)
813             {
814                 continue;
815             }
816 
817             std::string idStr;
818             if (!getUniqueEntryID(journal.get(), idStr))
819             {
820                 continue;
821             }
822 
823             logEntryArray.push_back({});
824             nlohmann::json &bmcJournalLogEntry = logEntryArray.back();
825             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
826                                            bmcJournalLogEntry) != 0)
827             {
828                 messages::internalError(asyncResp->res);
829                 return;
830             }
831         }
832         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
833         if (skip + top < entryCount)
834         {
835             asyncResp->res.jsonValue["Members@odata.nextLink"] =
836                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
837                 std::to_string(skip + top);
838         }
839     }
840 };
841 
842 class BMCJournalLogEntry : public Node
843 {
844   public:
845     BMCJournalLogEntry(CrowApp &app) :
846         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
847              std::string())
848     {
849         entityPrivileges = {
850             {boost::beast::http::verb::get, {{"Login"}}},
851             {boost::beast::http::verb::head, {{"Login"}}},
852             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
853             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
854             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
855             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
856     }
857 
858   private:
859     void doGet(crow::Response &res, const crow::Request &req,
860                const std::vector<std::string> &params) override
861     {
862         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
863         if (params.size() != 1)
864         {
865             messages::internalError(asyncResp->res);
866             return;
867         }
868         const std::string &entryID = params[0];
869         // Convert the unique ID back to a timestamp to find the entry
870         uint64_t ts = 0;
871         uint16_t index = 0;
872         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
873         {
874             return;
875         }
876 
877         sd_journal *journalTmp = nullptr;
878         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
879         if (ret < 0)
880         {
881             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
882             messages::internalError(asyncResp->res);
883             return;
884         }
885         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
886             journalTmp, sd_journal_close);
887         journalTmp = nullptr;
888         // Go to the timestamp in the log and move to the entry at the index
889         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
890         for (int i = 0; i <= index; i++)
891         {
892             sd_journal_next(journal.get());
893         }
894         // Confirm that the entry ID matches what was requested
895         std::string idStr;
896         if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID)
897         {
898             messages::resourceMissingAtURI(asyncResp->res, entryID);
899             return;
900         }
901 
902         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
903                                        asyncResp->res.jsonValue) != 0)
904         {
905             messages::internalError(asyncResp->res);
906             return;
907         }
908     }
909 };
910 
911 class CPULogService : public Node
912 {
913   public:
914     template <typename CrowApp>
915     CPULogService(CrowApp &app) :
916         Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/")
917     {
918         entityPrivileges = {
919             {boost::beast::http::verb::get, {{"Login"}}},
920             {boost::beast::http::verb::head, {{"Login"}}},
921             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
922             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
923             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
924             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
925     }
926 
927   private:
928     /**
929      * Functions triggers appropriate requests on DBus
930      */
931     void doGet(crow::Response &res, const crow::Request &req,
932                const std::vector<std::string> &params) override
933     {
934         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
935         // Copy over the static data to include the entries added by SubRoute
936         asyncResp->res.jsonValue["@odata.id"] =
937             "/redfish/v1/Systems/system/LogServices/CpuLog";
938         asyncResp->res.jsonValue["@odata.type"] =
939             "#LogService.v1_1_0.LogService";
940         asyncResp->res.jsonValue["@odata.context"] =
941             "/redfish/v1/$metadata#LogService.LogService";
942         asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
943         asyncResp->res.jsonValue["Description"] = "CPU Log Service";
944         asyncResp->res.jsonValue["Id"] = "CPU Log";
945         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
946         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
947         asyncResp->res.jsonValue["Entries"] = {
948             {"@odata.id",
949              "/redfish/v1/Systems/system/LogServices/CpuLog/Entries"}};
950         asyncResp->res.jsonValue["Actions"] = {
951             {"Oem",
952              {{"#CpuLog.Immediate",
953                {{"target", "/redfish/v1/Systems/system/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/Systems/system/LogServices/CpuLog/"
960                          "Actions/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/Systems/system/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/Systems/system/LogServices/CpuLog/Entries";
1009             asyncResp->res.jsonValue["@odata.context"] =
1010                 "/redfish/v1/"
1011                 "$metadata#LogEntryCollection.LogEntryCollection";
1012             asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
1013             asyncResp->res.jsonValue["Description"] =
1014                 "Collection of CPU Log Entries";
1015             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
1016             logEntryArray = nlohmann::json::array();
1017             for (const std::string &objpath : resp)
1018             {
1019                 // Don't list the immediate log
1020                 if (objpath.compare(cpuLogImmediatePath) == 0)
1021                 {
1022                     continue;
1023                 }
1024                 std::size_t lastPos = objpath.rfind("/");
1025                 if (lastPos != std::string::npos)
1026                 {
1027                     logEntryArray.push_back(
1028                         {{"@odata.id", "/redfish/v1/Systems/system/LogServices/"
1029                                        "CpuLog/Entries/" +
1030                                            objpath.substr(lastPos + 1)}});
1031                 }
1032             }
1033             asyncResp->res.jsonValue["Members@odata.count"] =
1034                 logEntryArray.size();
1035         };
1036         crow::connections::systemBus->async_method_call(
1037             std::move(getLogEntriesCallback),
1038             "xyz.openbmc_project.ObjectMapper",
1039             "/xyz/openbmc_project/object_mapper",
1040             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
1041             std::array<const char *, 1>{cpuLogInterface});
1042     }
1043 };
1044 
1045 std::string getLogCreatedTime(const nlohmann::json &cpuLog)
1046 {
1047     nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data");
1048     if (cdIt != cpuLog.end())
1049     {
1050         nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO");
1051         if (siIt != cdIt->end())
1052         {
1053             nlohmann::json::const_iterator tsIt = siIt->find("timestamp");
1054             if (tsIt != siIt->end())
1055             {
1056                 const std::string *logTime =
1057                     tsIt->get_ptr<const std::string *>();
1058                 if (logTime != nullptr)
1059                 {
1060                     return *logTime;
1061                 }
1062             }
1063         }
1064     }
1065     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
1066 
1067     return std::string();
1068 }
1069 
1070 class CPULogEntry : public Node
1071 {
1072   public:
1073     CPULogEntry(CrowApp &app) :
1074         Node(app,
1075              "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/<str>/",
1076              std::string())
1077     {
1078         entityPrivileges = {
1079             {boost::beast::http::verb::get, {{"Login"}}},
1080             {boost::beast::http::verb::head, {{"Login"}}},
1081             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1082             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1083             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1084             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1085     }
1086 
1087   private:
1088     void doGet(crow::Response &res, const crow::Request &req,
1089                const std::vector<std::string> &params) override
1090     {
1091         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1092         if (params.size() != 1)
1093         {
1094             messages::internalError(asyncResp->res);
1095             return;
1096         }
1097         const uint8_t logId = std::atoi(params[0].c_str());
1098         auto getStoredLogCallback = [asyncResp, logId](
1099                                         const boost::system::error_code ec,
1100                                         const std::variant<std::string> &resp) {
1101             if (ec)
1102             {
1103                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
1104                 messages::internalError(asyncResp->res);
1105                 return;
1106             }
1107             const std::string *log = std::get_if<std::string>(&resp);
1108             if (log == nullptr)
1109             {
1110                 messages::internalError(asyncResp->res);
1111                 return;
1112             }
1113             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1114             if (j.is_discarded())
1115             {
1116                 messages::internalError(asyncResp->res);
1117                 return;
1118             }
1119             std::string t = getLogCreatedTime(j);
1120             asyncResp->res.jsonValue = {
1121                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1122                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1123                 {"@odata.id",
1124                  "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/" +
1125                      std::to_string(logId)},
1126                 {"Name", "CPU Debug Log"},
1127                 {"Id", logId},
1128                 {"EntryType", "Oem"},
1129                 {"OemRecordFormat", "Intel CPU Log"},
1130                 {"Oem", {{"Intel", std::move(j)}}},
1131                 {"Created", std::move(t)}};
1132         };
1133         crow::connections::systemBus->async_method_call(
1134             std::move(getStoredLogCallback), cpuLogObject,
1135             cpuLogPath + std::string("/") + std::to_string(logId),
1136             "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
1137     }
1138 };
1139 
1140 class ImmediateCPULog : public Node
1141 {
1142   public:
1143     ImmediateCPULog(CrowApp &app) :
1144         Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/"
1145                   "CpuLog.Immediate/")
1146     {
1147         entityPrivileges = {
1148             {boost::beast::http::verb::get, {{"Login"}}},
1149             {boost::beast::http::verb::head, {{"Login"}}},
1150             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1151             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1152             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1153             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1154     }
1155 
1156   private:
1157     void doPost(crow::Response &res, const crow::Request &req,
1158                 const std::vector<std::string> &params) override
1159     {
1160         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1161         static std::unique_ptr<sdbusplus::bus::match::match>
1162             immediateLogMatcher;
1163 
1164         // Only allow one Immediate Log request at a time
1165         if (immediateLogMatcher != nullptr)
1166         {
1167             asyncResp->res.addHeader("Retry-After", "30");
1168             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
1169             return;
1170         }
1171         // Make this static so it survives outside this method
1172         static boost::asio::deadline_timer timeout(*req.ioService);
1173 
1174         timeout.expires_from_now(boost::posix_time::seconds(30));
1175         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
1176             immediateLogMatcher = nullptr;
1177             if (ec)
1178             {
1179                 // operation_aborted is expected if timer is canceled before
1180                 // completion.
1181                 if (ec != boost::asio::error::operation_aborted)
1182                 {
1183                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
1184                 }
1185                 return;
1186             }
1187             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
1188 
1189             messages::internalError(asyncResp->res);
1190         });
1191 
1192         auto immediateLogMatcherCallback = [asyncResp](
1193                                                sdbusplus::message::message &m) {
1194             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
1195             boost::system::error_code ec;
1196             timeout.cancel(ec);
1197             if (ec)
1198             {
1199                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
1200             }
1201             sdbusplus::message::object_path objPath;
1202             boost::container::flat_map<
1203                 std::string, boost::container::flat_map<
1204                                  std::string, std::variant<std::string>>>
1205                 interfacesAdded;
1206             m.read(objPath, interfacesAdded);
1207             const std::string *log = std::get_if<std::string>(
1208                 &interfacesAdded[cpuLogInterface]["Log"]);
1209             if (log == nullptr)
1210             {
1211                 messages::internalError(asyncResp->res);
1212                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
1213                 // match object inside which this lambda is executing.  Once it
1214                 // is set to nullptr, the match object will be destroyed and the
1215                 // lambda will lose its context, including res, so it needs to
1216                 // be the last thing done.
1217                 immediateLogMatcher = nullptr;
1218                 return;
1219             }
1220             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
1221             if (j.is_discarded())
1222             {
1223                 messages::internalError(asyncResp->res);
1224                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
1225                 // match object inside which this lambda is executing.  Once it
1226                 // is set to nullptr, the match object will be destroyed and the
1227                 // lambda will lose its context, including res, so it needs to
1228                 // be the last thing done.
1229                 immediateLogMatcher = nullptr;
1230                 return;
1231             }
1232             std::string t = getLogCreatedTime(j);
1233             asyncResp->res.jsonValue = {
1234                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
1235                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
1236                 {"Name", "CPU Debug Log"},
1237                 {"EntryType", "Oem"},
1238                 {"OemRecordFormat", "Intel CPU Log"},
1239                 {"Oem", {{"Intel", std::move(j)}}},
1240                 {"Created", std::move(t)}};
1241             // Careful with immediateLogMatcher.  It is a unique_ptr to the
1242             // match object inside which this lambda is executing.  Once it is
1243             // set to nullptr, the match object will be destroyed and the lambda
1244             // will lose its context, including res, so it needs to be the last
1245             // thing done.
1246             immediateLogMatcher = nullptr;
1247         };
1248         immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
1249             *crow::connections::systemBus,
1250             sdbusplus::bus::match::rules::interfacesAdded() +
1251                 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
1252             std::move(immediateLogMatcherCallback));
1253 
1254         auto generateImmediateLogCallback =
1255             [asyncResp](const boost::system::error_code ec,
1256                         const std::string &resp) {
1257                 if (ec)
1258                 {
1259                     if (ec.value() ==
1260                         boost::system::errc::operation_not_supported)
1261                     {
1262                         messages::resourceInStandby(asyncResp->res);
1263                     }
1264                     else
1265                     {
1266                         messages::internalError(asyncResp->res);
1267                     }
1268                     boost::system::error_code timeoutec;
1269                     timeout.cancel(timeoutec);
1270                     if (timeoutec)
1271                     {
1272                         BMCWEB_LOG_ERROR << "error canceling timer "
1273                                          << timeoutec;
1274                     }
1275                     immediateLogMatcher = nullptr;
1276                     return;
1277                 }
1278             };
1279         crow::connections::systemBus->async_method_call(
1280             std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
1281             cpuLogImmediateInterface, "GenerateImmediateLog");
1282     }
1283 };
1284 
1285 class SendRawPECI : public Node
1286 {
1287   public:
1288     SendRawPECI(CrowApp &app) :
1289         Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/"
1290                   "CpuLog.SendRawPeci/")
1291     {
1292         entityPrivileges = {
1293             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
1294             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
1295             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1296             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1297             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1298             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1299     }
1300 
1301   private:
1302     void doPost(crow::Response &res, const crow::Request &req,
1303                 const std::vector<std::string> &params) override
1304     {
1305         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1306         uint8_t clientAddress = 0;
1307         uint8_t readLength = 0;
1308         std::vector<uint8_t> peciCommand;
1309         if (!json_util::readJson(req, res, "ClientAddress", clientAddress,
1310                                  "ReadLength", readLength, "PECICommand",
1311                                  peciCommand))
1312         {
1313             return;
1314         }
1315 
1316         // Callback to return the Raw PECI response
1317         auto sendRawPECICallback =
1318             [asyncResp](const boost::system::error_code ec,
1319                         const std::vector<uint8_t> &resp) {
1320                 if (ec)
1321                 {
1322                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1323                                      << ec.message();
1324                     messages::internalError(asyncResp->res);
1325                     return;
1326                 }
1327                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1328                                             {"PECIResponse", resp}};
1329             };
1330         // Call the SendRawPECI command with the provided data
1331         crow::connections::systemBus->async_method_call(
1332             std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1333             cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1334             peciCommand);
1335     }
1336 };
1337 
1338 } // namespace redfish
1339