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