xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision 16428a1a312ac17209d71593298e14f833a03c2c)
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 LogServiceCollection : public Node
258 {
259   public:
260     template <typename CrowApp>
261     LogServiceCollection(CrowApp &app) :
262         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
263     {
264         // Collections use static ID for SubRoute to add to its parent, but only
265         // load dynamic data so the duplicate static members don't get displayed
266         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
267         entityPrivileges = {
268             {boost::beast::http::verb::get, {{"Login"}}},
269             {boost::beast::http::verb::head, {{"Login"}}},
270             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
271             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
272             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
273             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
274     }
275 
276   private:
277     /**
278      * Functions triggers appropriate requests on DBus
279      */
280     void doGet(crow::Response &res, const crow::Request &req,
281                const std::vector<std::string> &params) override
282     {
283         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
284         // Collections don't include the static data added by SubRoute because
285         // it has a duplicate entry for members
286         asyncResp->res.jsonValue["@odata.type"] =
287             "#LogServiceCollection.LogServiceCollection";
288         asyncResp->res.jsonValue["@odata.context"] =
289             "/redfish/v1/"
290             "$metadata#LogServiceCollection.LogServiceCollection";
291         asyncResp->res.jsonValue["@odata.id"] =
292             "/redfish/v1/Managers/bmc/LogServices";
293         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
294         asyncResp->res.jsonValue["Description"] =
295             "Collection of LogServices for this Manager";
296         nlohmann::json &logserviceArray = asyncResp->res.jsonValue["Members"];
297         logserviceArray = nlohmann::json::array();
298         logserviceArray.push_back(
299             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog"}});
300 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
301         logserviceArray.push_back(
302             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
303 #endif
304         asyncResp->res.jsonValue["Members@odata.count"] =
305             logserviceArray.size();
306     }
307 };
308 
309 class BMCLogService : public Node
310 {
311   public:
312     template <typename CrowApp>
313     BMCLogService(CrowApp &app) :
314         Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/")
315     {
316         // Set the id for SubRoute
317         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog";
318         entityPrivileges = {
319             {boost::beast::http::verb::get, {{"Login"}}},
320             {boost::beast::http::verb::head, {{"Login"}}},
321             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
322             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
323             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
324             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
325     }
326 
327   private:
328     void doGet(crow::Response &res, const crow::Request &req,
329                const std::vector<std::string> &params) override
330     {
331         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
332         // Copy over the static data to include the entries added by SubRoute
333         asyncResp->res.jsonValue = Node::json;
334         asyncResp->res.jsonValue["@odata.type"] =
335             "#LogService.v1_1_0.LogService";
336         asyncResp->res.jsonValue["@odata.context"] =
337             "/redfish/v1/$metadata#LogService.LogService";
338         asyncResp->res.jsonValue["Name"] = "Open BMC Log Service";
339         asyncResp->res.jsonValue["Description"] = "BMC Log Service";
340         asyncResp->res.jsonValue["Id"] = "BMC Log";
341         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
342     }
343 };
344 
345 static int fillBMCLogEntryJson(const std::string &bmcLogEntryID,
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 Created time from the timestamp
370     std::string entryTimeStr;
371     if (!getEntryTimestamp(journal, entryTimeStr))
372     {
373         return 1;
374     }
375 
376     // Fill in the log entry with the gathered data
377     bmcLogEntryJson = {
378         {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
379         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
380         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/" +
381                           bmcLogEntryID},
382         {"Name", "BMC Journal Entry"},
383         {"Id", bmcLogEntryID},
384         {"Message", msg},
385         {"EntryType", "Oem"},
386         {"Severity",
387          severity <= 2 ? "Critical"
388                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
389         {"OemRecordFormat", "Intel BMC Journal Entry"},
390         {"Created", std::move(entryTimeStr)}};
391     return 0;
392 }
393 
394 class BMCLogEntryCollection : public Node
395 {
396   public:
397     template <typename CrowApp>
398     BMCLogEntryCollection(CrowApp &app) :
399         Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/")
400     {
401         // Collections use static ID for SubRoute to add to its parent, but only
402         // load dynamic data so the duplicate static members don't get displayed
403         Node::json["@odata.id"] =
404             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
405         entityPrivileges = {
406             {boost::beast::http::verb::get, {{"Login"}}},
407             {boost::beast::http::verb::head, {{"Login"}}},
408             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
409             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
410             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
411             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
412     }
413 
414   private:
415     void doGet(crow::Response &res, const crow::Request &req,
416                const std::vector<std::string> &params) override
417     {
418         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
419         static constexpr const long maxEntriesPerPage = 1000;
420         long skip = 0;
421         long top = maxEntriesPerPage; // Show max entries by default
422         if (!getSkipParam(asyncResp->res, req, skip))
423         {
424             return;
425         }
426         if (!getTopParam(asyncResp->res, req, top))
427         {
428             return;
429         }
430         // Collections don't include the static data added by SubRoute because
431         // it has a duplicate entry for members
432         asyncResp->res.jsonValue["@odata.type"] =
433             "#LogEntryCollection.LogEntryCollection";
434         asyncResp->res.jsonValue["@odata.context"] =
435             "/redfish/v1/"
436             "$metadata#LogEntryCollection.LogEntryCollection";
437         asyncResp->res.jsonValue["@odata.id"] =
438             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
439         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
440         asyncResp->res.jsonValue["Description"] =
441             "Collection of BMC Journal Entries";
442         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
443         logEntryArray = nlohmann::json::array();
444 
445         // Go through the journal and use the timestamp to create a unique ID
446         // for each entry
447         sd_journal *journalTmp = nullptr;
448         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
449         if (ret < 0)
450         {
451             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
452             messages::internalError(asyncResp->res);
453             return;
454         }
455         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
456             journalTmp, sd_journal_close);
457         journalTmp = nullptr;
458         uint64_t entryCount = 0;
459         SD_JOURNAL_FOREACH(journal.get())
460         {
461             entryCount++;
462             // Handle paging using skip (number of entries to skip from the
463             // start) and top (number of entries to display)
464             if (entryCount <= skip || entryCount > skip + top)
465             {
466                 continue;
467             }
468 
469             std::string idStr;
470             if (!getUniqueEntryID(journal.get(), idStr))
471             {
472                 continue;
473             }
474 
475             logEntryArray.push_back({});
476             nlohmann::json &bmcLogEntry = logEntryArray.back();
477             if (fillBMCLogEntryJson(idStr, journal.get(), bmcLogEntry) != 0)
478             {
479                 messages::internalError(asyncResp->res);
480                 return;
481             }
482         }
483         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
484         if (skip + top < entryCount)
485         {
486             asyncResp->res.jsonValue["Members@odata.nextLink"] =
487                 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
488                 std::to_string(skip + top);
489         }
490     }
491 };
492 
493 class BMCLogEntry : public Node
494 {
495   public:
496     BMCLogEntry(CrowApp &app) :
497         Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/<str>/",
498              std::string())
499     {
500         entityPrivileges = {
501             {boost::beast::http::verb::get, {{"Login"}}},
502             {boost::beast::http::verb::head, {{"Login"}}},
503             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
504             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
505             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
506             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
507     }
508 
509   private:
510     void doGet(crow::Response &res, const crow::Request &req,
511                const std::vector<std::string> &params) override
512     {
513         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
514         if (params.size() != 1)
515         {
516             messages::internalError(asyncResp->res);
517             return;
518         }
519         const std::string &entryID = params[0];
520         // Convert the unique ID back to a timestamp to find the entry
521         uint64_t ts = 0;
522         uint16_t index = 0;
523         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
524         {
525             return;
526         }
527 
528         sd_journal *journalTmp = nullptr;
529         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
530         if (ret < 0)
531         {
532             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
533             messages::internalError(asyncResp->res);
534             return;
535         }
536         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
537             journalTmp, sd_journal_close);
538         journalTmp = nullptr;
539         // Go to the timestamp in the log and move to the entry at the index
540         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
541         for (int i = 0; i <= index; i++)
542         {
543             sd_journal_next(journal.get());
544         }
545         if (fillBMCLogEntryJson(params[0], journal.get(),
546                                 asyncResp->res.jsonValue) != 0)
547         {
548             messages::internalError(asyncResp->res);
549             return;
550         }
551     }
552 };
553 
554 class CPULogService : public Node
555 {
556   public:
557     template <typename CrowApp>
558     CPULogService(CrowApp &app) :
559         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
560     {
561         // Set the id for SubRoute
562         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog";
563         entityPrivileges = {
564             {boost::beast::http::verb::get, {{"Login"}}},
565             {boost::beast::http::verb::head, {{"Login"}}},
566             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
567             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
568             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
569             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
570     }
571 
572   private:
573     /**
574      * Functions triggers appropriate requests on DBus
575      */
576     void doGet(crow::Response &res, const crow::Request &req,
577                const std::vector<std::string> &params) override
578     {
579         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
580         // Copy over the static data to include the entries added by SubRoute
581         asyncResp->res.jsonValue = Node::json;
582         asyncResp->res.jsonValue["@odata.type"] =
583             "#LogService.v1_1_0.LogService";
584         asyncResp->res.jsonValue["@odata.context"] =
585             "/redfish/v1/"
586             "$metadata#LogService.LogService";
587         asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
588         asyncResp->res.jsonValue["Description"] = "CPU Log Service";
589         asyncResp->res.jsonValue["Id"] = "CPU Log";
590         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
591         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
592         asyncResp->res.jsonValue["Actions"] = {
593             {"Oem",
594              {{"#CpuLog.Immediate",
595                {{"target",
596                  "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
597                  "CpuLog.Immediate"}}}}}};
598 
599 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
600         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
601             {"#CpuLog.SendRawPeci",
602              {{"target",
603                "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
604                "CpuLog.SendRawPeci"}}});
605 #endif
606     }
607 };
608 
609 class CPULogEntryCollection : public Node
610 {
611   public:
612     template <typename CrowApp>
613     CPULogEntryCollection(CrowApp &app) :
614         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
615     {
616         // Collections use static ID for SubRoute to add to its parent, but only
617         // load dynamic data so the duplicate static members don't get displayed
618         Node::json["@odata.id"] =
619             "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
620         entityPrivileges = {
621             {boost::beast::http::verb::get, {{"Login"}}},
622             {boost::beast::http::verb::head, {{"Login"}}},
623             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
624             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
625             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
626             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
627     }
628 
629   private:
630     /**
631      * Functions triggers appropriate requests on DBus
632      */
633     void doGet(crow::Response &res, const crow::Request &req,
634                const std::vector<std::string> &params) override
635     {
636         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
637         // Collections don't include the static data added by SubRoute because
638         // it has a duplicate entry for members
639         auto getLogEntriesCallback = [asyncResp](
640                                          const boost::system::error_code ec,
641                                          const std::vector<std::string> &resp) {
642             if (ec)
643             {
644                 if (ec.value() !=
645                     boost::system::errc::no_such_file_or_directory)
646                 {
647                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
648                                      << ec.message();
649                     messages::internalError(asyncResp->res);
650                     return;
651                 }
652             }
653             asyncResp->res.jsonValue["@odata.type"] =
654                 "#LogEntryCollection.LogEntryCollection";
655             asyncResp->res.jsonValue["@odata.context"] =
656                 "/redfish/v1/"
657                 "$metadata#LogEntryCollection.LogEntryCollection";
658             asyncResp->res.jsonValue["@odata.id"] =
659                 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
660             asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
661             asyncResp->res.jsonValue["Description"] =
662                 "Collection of CPU Log Entries";
663             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
664             logEntryArray = nlohmann::json::array();
665             for (const std::string &objpath : resp)
666             {
667                 // Don't list the immediate log
668                 if (objpath.compare(cpuLogImmediatePath) == 0)
669                 {
670                     continue;
671                 }
672                 std::size_t lastPos = objpath.rfind("/");
673                 if (lastPos != std::string::npos)
674                 {
675                     logEntryArray.push_back(
676                         {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
677                                        "CpuLog/Entries/" +
678                                            objpath.substr(lastPos + 1)}});
679                 }
680             }
681             asyncResp->res.jsonValue["Members@odata.count"] =
682                 logEntryArray.size();
683         };
684         crow::connections::systemBus->async_method_call(
685             std::move(getLogEntriesCallback),
686             "xyz.openbmc_project.ObjectMapper",
687             "/xyz/openbmc_project/object_mapper",
688             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
689             std::array<const char *, 1>{cpuLogInterface});
690     }
691 };
692 
693 std::string getLogCreatedTime(const nlohmann::json &cpuLog)
694 {
695     nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
696     if (metaIt != cpuLog.end())
697     {
698         nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
699         if (tsIt != metaIt->end())
700         {
701             const std::string *logTime = tsIt->get_ptr<const std::string *>();
702             if (logTime != nullptr)
703             {
704                 return *logTime;
705             }
706         }
707     }
708     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
709 
710     return std::string();
711 }
712 
713 class CPULogEntry : public Node
714 {
715   public:
716     CPULogEntry(CrowApp &app) :
717         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
718              std::string())
719     {
720         entityPrivileges = {
721             {boost::beast::http::verb::get, {{"Login"}}},
722             {boost::beast::http::verb::head, {{"Login"}}},
723             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
724             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
725             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
726             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
727     }
728 
729   private:
730     void doGet(crow::Response &res, const crow::Request &req,
731                const std::vector<std::string> &params) override
732     {
733         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
734         if (params.size() != 1)
735         {
736             messages::internalError(asyncResp->res);
737             return;
738         }
739         const uint8_t logId = std::atoi(params[0].c_str());
740         auto getStoredLogCallback =
741             [asyncResp,
742              logId](const boost::system::error_code ec,
743                     const sdbusplus::message::variant<std::string> &resp) {
744                 if (ec)
745                 {
746                     BMCWEB_LOG_DEBUG << "failed to get log ec: "
747                                      << ec.message();
748                     messages::internalError(asyncResp->res);
749                     return;
750                 }
751                 const std::string *log =
752                     mapbox::getPtr<const std::string>(resp);
753                 if (log == nullptr)
754                 {
755                     messages::internalError(asyncResp->res);
756                     return;
757                 }
758                 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
759                 if (j.is_discarded())
760                 {
761                     messages::internalError(asyncResp->res);
762                     return;
763                 }
764                 std::string t = getLogCreatedTime(j);
765                 asyncResp->res.jsonValue = {
766                     {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
767                     {"@odata.context",
768                      "/redfish/v1/$metadata#LogEntry.LogEntry"},
769                     {"@odata.id",
770                      "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
771                          std::to_string(logId)},
772                     {"Name", "CPU Debug Log"},
773                     {"Id", logId},
774                     {"EntryType", "Oem"},
775                     {"OemRecordFormat", "Intel CPU Log"},
776                     {"Oem", {{"Intel", std::move(j)}}},
777                     {"Created", std::move(t)}};
778             };
779         crow::connections::systemBus->async_method_call(
780             std::move(getStoredLogCallback), cpuLogObject,
781             cpuLogPath + std::string("/") + std::to_string(logId),
782             "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
783     }
784 };
785 
786 class ImmediateCPULog : public Node
787 {
788   public:
789     ImmediateCPULog(CrowApp &app) :
790         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
791                   "CpuLog.Immediate/")
792     {
793         entityPrivileges = {
794             {boost::beast::http::verb::get, {{"Login"}}},
795             {boost::beast::http::verb::head, {{"Login"}}},
796             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
797             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
798             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
799             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
800     }
801 
802   private:
803     void doPost(crow::Response &res, const crow::Request &req,
804                 const std::vector<std::string> &params) override
805     {
806         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
807         static std::unique_ptr<sdbusplus::bus::match::match>
808             immediateLogMatcher;
809 
810         // Only allow one Immediate Log request at a time
811         if (immediateLogMatcher != nullptr)
812         {
813             asyncResp->res.addHeader("Retry-After", "30");
814             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
815             return;
816         }
817         // Make this static so it survives outside this method
818         static boost::asio::deadline_timer timeout(*req.ioService);
819 
820         timeout.expires_from_now(boost::posix_time::seconds(30));
821         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
822             immediateLogMatcher = nullptr;
823             if (ec)
824             {
825                 // operation_aborted is expected if timer is canceled before
826                 // completion.
827                 if (ec != boost::asio::error::operation_aborted)
828                 {
829                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
830                 }
831                 return;
832             }
833             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
834 
835             messages::internalError(asyncResp->res);
836         });
837 
838         auto immediateLogMatcherCallback = [asyncResp](
839                                                sdbusplus::message::message &m) {
840             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
841             boost::system::error_code ec;
842             timeout.cancel(ec);
843             if (ec)
844             {
845                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
846             }
847             sdbusplus::message::object_path objPath;
848             boost::container::flat_map<
849                 std::string,
850                 boost::container::flat_map<
851                     std::string, sdbusplus::message::variant<std::string>>>
852                 interfacesAdded;
853             m.read(objPath, interfacesAdded);
854             const std::string *log = mapbox::getPtr<const std::string>(
855                 interfacesAdded[cpuLogInterface]["Log"]);
856             if (log == nullptr)
857             {
858                 messages::internalError(asyncResp->res);
859                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
860                 // match object inside which this lambda is executing.  Once it
861                 // is set to nullptr, the match object will be destroyed and the
862                 // lambda will lose its context, including res, so it needs to
863                 // be the last thing done.
864                 immediateLogMatcher = nullptr;
865                 return;
866             }
867             nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
868             if (j.is_discarded())
869             {
870                 messages::internalError(asyncResp->res);
871                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
872                 // match object inside which this lambda is executing.  Once it
873                 // is set to nullptr, the match object will be destroyed and the
874                 // lambda will lose its context, including res, so it needs to
875                 // be the last thing done.
876                 immediateLogMatcher = nullptr;
877                 return;
878             }
879             std::string t = getLogCreatedTime(j);
880             asyncResp->res.jsonValue = {
881                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
882                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
883                 {"Name", "CPU Debug Log"},
884                 {"EntryType", "Oem"},
885                 {"OemRecordFormat", "Intel CPU Log"},
886                 {"Oem", {{"Intel", std::move(j)}}},
887                 {"Created", std::move(t)}};
888             // Careful with immediateLogMatcher.  It is a unique_ptr to the
889             // match object inside which this lambda is executing.  Once it is
890             // set to nullptr, the match object will be destroyed and the lambda
891             // will lose its context, including res, so it needs to be the last
892             // thing done.
893             immediateLogMatcher = nullptr;
894         };
895         immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
896             *crow::connections::systemBus,
897             sdbusplus::bus::match::rules::interfacesAdded() +
898                 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
899             std::move(immediateLogMatcherCallback));
900 
901         auto generateImmediateLogCallback =
902             [asyncResp](const boost::system::error_code ec,
903                         const std::string &resp) {
904                 if (ec)
905                 {
906                     if (ec.value() ==
907                         boost::system::errc::operation_not_supported)
908                     {
909                         messages::resourceInStandby(asyncResp->res);
910                     }
911                     else
912                     {
913                         messages::internalError(asyncResp->res);
914                     }
915                     boost::system::error_code timeoutec;
916                     timeout.cancel(timeoutec);
917                     if (timeoutec)
918                     {
919                         BMCWEB_LOG_ERROR << "error canceling timer "
920                                          << timeoutec;
921                     }
922                     immediateLogMatcher = nullptr;
923                     return;
924                 }
925             };
926         crow::connections::systemBus->async_method_call(
927             std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
928             cpuLogImmediateInterface, "GenerateImmediateLog");
929     }
930 };
931 
932 class SendRawPECI : public Node
933 {
934   public:
935     SendRawPECI(CrowApp &app) :
936         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
937                   "CpuLog.SendRawPeci/")
938     {
939         entityPrivileges = {
940             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
941             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
942             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
943             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
944             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
945             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
946     }
947 
948   private:
949     void doPost(crow::Response &res, const crow::Request &req,
950                 const std::vector<std::string> &params) override
951     {
952         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
953         // Get the Raw PECI command from the request
954         nlohmann::json rawPECICmd;
955         if (!json_util::processJsonFromRequest(res, req, rawPECICmd))
956         {
957             return;
958         }
959         // Get the Client Address from the request
960         nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress");
961         if (caIt == rawPECICmd.end())
962         {
963             messages::propertyMissing(asyncResp->res, "ClientAddress");
964             return;
965         }
966         const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
967         if (ca == nullptr)
968         {
969             messages::propertyValueTypeError(asyncResp->res, caIt->dump(),
970                                              "ClientAddress");
971             return;
972         }
973         // Get the Read Length from the request
974         const uint8_t clientAddress = static_cast<uint8_t>(*ca);
975         nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength");
976         if (rlIt == rawPECICmd.end())
977         {
978             messages::propertyMissing(asyncResp->res, "ReadLength");
979             return;
980         }
981         const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
982         if (rl == nullptr)
983         {
984             messages::propertyValueTypeError(asyncResp->res, rlIt->dump(),
985                                              "ReadLength");
986             return;
987         }
988         // Get the PECI Command from the request
989         const uint32_t readLength = static_cast<uint32_t>(*rl);
990         nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand");
991         if (pcIt == rawPECICmd.end())
992         {
993             messages::propertyMissing(asyncResp->res, "PECICommand");
994             return;
995         }
996         std::vector<uint8_t> peciCommand;
997         for (auto pc : *pcIt)
998         {
999             const uint64_t *val = pc.get_ptr<const uint64_t *>();
1000             if (val == nullptr)
1001             {
1002                 messages::propertyValueTypeError(
1003                     asyncResp->res, pc.dump(),
1004                     "PECICommand/" + std::to_string(peciCommand.size()));
1005                 return;
1006             }
1007             peciCommand.push_back(static_cast<uint8_t>(*val));
1008         }
1009         // Callback to return the Raw PECI response
1010         auto sendRawPECICallback =
1011             [asyncResp](const boost::system::error_code ec,
1012                         const std::vector<uint8_t> &resp) {
1013                 if (ec)
1014                 {
1015                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
1016                                      << ec.message();
1017                     messages::internalError(asyncResp->res);
1018                     return;
1019                 }
1020                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
1021                                             {"PECIResponse", resp}};
1022             };
1023         // Call the SendRawPECI command with the provided data
1024         crow::connections::systemBus->async_method_call(
1025             std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
1026             cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
1027             peciCommand);
1028     }
1029 };
1030 
1031 } // namespace redfish
1032