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