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