xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision a08b46ccf0fc0081cecc4843484c4f0eb13f5a9a)
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 class LogServiceCollection : public Node
41 {
42   public:
43     template <typename CrowApp>
44     LogServiceCollection(CrowApp &app) :
45         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
46     {
47         // Collections use static ID for SubRoute to add to its parent, but only
48         // load dynamic data so the duplicate static members don't get displayed
49         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices";
50         entityPrivileges = {
51             {boost::beast::http::verb::get, {{"Login"}}},
52             {boost::beast::http::verb::head, {{"Login"}}},
53             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
54             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
55             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
56             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
57     }
58 
59   private:
60     /**
61      * Functions triggers appropriate requests on DBus
62      */
63     void doGet(crow::Response &res, const crow::Request &req,
64                const std::vector<std::string> &params) override
65     {
66         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
67         // Collections don't include the static data added by SubRoute because
68         // it has a duplicate entry for members
69         asyncResp->res.jsonValue["@odata.type"] =
70             "#LogServiceCollection.LogServiceCollection";
71         asyncResp->res.jsonValue["@odata.context"] =
72             "/redfish/v1/"
73             "$metadata#LogServiceCollection.LogServiceCollection";
74         asyncResp->res.jsonValue["@odata.id"] =
75             "/redfish/v1/Managers/bmc/LogServices";
76         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
77         asyncResp->res.jsonValue["Description"] =
78             "Collection of LogServices for this Manager";
79         nlohmann::json &logserviceArray = asyncResp->res.jsonValue["Members"];
80         logserviceArray = nlohmann::json::array();
81         logserviceArray.push_back(
82             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog"}});
83 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
84         logserviceArray.push_back(
85             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}});
86 #endif
87         asyncResp->res.jsonValue["Members@odata.count"] =
88             logserviceArray.size();
89     }
90 };
91 
92 class BMCLogService : public Node
93 {
94   public:
95     template <typename CrowApp>
96     BMCLogService(CrowApp &app) :
97         Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/")
98     {
99         // Set the id for SubRoute
100         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/BmcLog";
101         entityPrivileges = {
102             {boost::beast::http::verb::get, {{"Login"}}},
103             {boost::beast::http::verb::head, {{"Login"}}},
104             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
105             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
106             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
107             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
108     }
109 
110   private:
111     void doGet(crow::Response &res, const crow::Request &req,
112                const std::vector<std::string> &params) override
113     {
114         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
115         // Copy over the static data to include the entries added by SubRoute
116         asyncResp->res.jsonValue = Node::json;
117         asyncResp->res.jsonValue["@odata.type"] =
118             "#LogService.v1_1_0.LogService";
119         asyncResp->res.jsonValue["@odata.context"] =
120             "/redfish/v1/$metadata#LogService.LogService";
121         asyncResp->res.jsonValue["Name"] = "Open BMC Log Service";
122         asyncResp->res.jsonValue["Description"] = "BMC Log Service";
123         asyncResp->res.jsonValue["Id"] = "BMC Log";
124         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
125     }
126 };
127 
128 static int fillBMCLogEntryJson(const std::string &bmcLogEntryID,
129                                sd_journal *journal,
130                                nlohmann::json &bmcLogEntryJson)
131 {
132     // Get the Log Entry contents
133     int ret = 0;
134     const char *data = nullptr;
135     size_t length = 0;
136 
137     ret =
138         sd_journal_get_data(journal, "MESSAGE", (const void **)&data, &length);
139     if (ret < 0)
140     {
141         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
142         return 1;
143     }
144     boost::string_view msg;
145     msg = boost::string_view(data, length);
146     // Only use the content after the "=" character.
147     msg.remove_prefix(std::min(msg.find("=") + 1, msg.size()));
148 
149     // Get the severity from the PRIORITY field
150     boost::string_view priority;
151     int severity = 8; // Default to an invalid priority
152     ret =
153         sd_journal_get_data(journal, "PRIORITY", (const void **)&data, &length);
154     if (ret < 0)
155     {
156         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
157         return 1;
158     }
159     priority = boost::string_view(data, length);
160     // Check length for sanity. Must be a single digit in the form
161     // "PRIORITY=[0-7]"
162     if (priority.size() > sizeof("PRIORITY=0"))
163     {
164         BMCWEB_LOG_ERROR << "Invalid PRIORITY field length";
165         return 1;
166     }
167     // Only use the content after the "=" character.
168     priority.remove_prefix(std::min(priority.find("=") + 1, priority.size()));
169     severity = strtol(priority.data(), nullptr, 10);
170 
171     // Get the Created time from the timestamp
172     // Get the entry timestamp
173     uint64_t timestamp = 0;
174     ret = sd_journal_get_realtime_usec(journal, &timestamp);
175     if (ret < 0)
176     {
177         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
178                          << strerror(-ret);
179     }
180     time_t t =
181         static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s
182     struct tm *loctime = localtime(&t);
183     char entryTime[64] = {};
184     if (NULL != loctime)
185     {
186         strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime);
187     }
188     // Insert the ':' into the timezone
189     boost::string_view t1(entryTime);
190     boost::string_view t2(entryTime);
191     if (t1.size() > 2 && t2.size() > 2)
192     {
193         t1.remove_suffix(2);
194         t2.remove_prefix(t2.size() - 2);
195     }
196     const std::string entryTimeStr(t1.to_string() + ":" + t2.to_string());
197 
198     // Fill in the log entry with the gathered data
199     bmcLogEntryJson = {
200         {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
201         {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
202         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/" +
203                           bmcLogEntryID},
204         {"Name", "BMC Journal Entry"},
205         {"Id", bmcLogEntryID},
206         {"Message", msg.to_string()},
207         {"EntryType", "Oem"},
208         {"Severity",
209          severity <= 2 ? "Critical"
210                        : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""},
211         {"OemRecordFormat", "Intel BMC Journal Entry"},
212         {"Created", std::move(entryTimeStr)}};
213     return 0;
214 }
215 
216 class BMCLogEntryCollection : public Node
217 {
218   public:
219     template <typename CrowApp>
220     BMCLogEntryCollection(CrowApp &app) :
221         Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/")
222     {
223         // Collections use static ID for SubRoute to add to its parent, but only
224         // load dynamic data so the duplicate static members don't get displayed
225         Node::json["@odata.id"] =
226             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
227         entityPrivileges = {
228             {boost::beast::http::verb::get, {{"Login"}}},
229             {boost::beast::http::verb::head, {{"Login"}}},
230             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
231             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
232             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
233             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
234     }
235 
236   private:
237     void doGet(crow::Response &res, const crow::Request &req,
238                const std::vector<std::string> &params) override
239     {
240         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
241         static constexpr const long maxEntriesPerPage = 1000;
242         long skip = 0;
243         long top = maxEntriesPerPage; // Show max entries by default
244         char *skipParam = req.urlParams.get("$skip");
245         if (skipParam != nullptr)
246         {
247             char *ptr = nullptr;
248             skip = std::strtol(skipParam, &ptr, 10);
249             if (*skipParam == '\0' || *ptr != '\0')
250             {
251 
252                 messages::queryParameterValueTypeError(
253                     asyncResp->res, std::string(skipParam), "$skip");
254                 return;
255             }
256             if (skip < 0)
257             {
258 
259                 messages::queryParameterOutOfRange(asyncResp->res,
260                                                    std::to_string(skip),
261                                                    "$skip", "greater than 0");
262                 return;
263             }
264         }
265         char *topParam = req.urlParams.get("$top");
266         if (topParam != nullptr)
267         {
268             char *ptr = nullptr;
269             top = std::strtol(topParam, &ptr, 10);
270             if (*topParam == '\0' || *ptr != '\0')
271             {
272                 messages::queryParameterValueTypeError(
273                     asyncResp->res, std::string(topParam), "$top");
274                 return;
275             }
276             if (top < 1 || top > maxEntriesPerPage)
277             {
278 
279                 messages::queryParameterOutOfRange(
280                     asyncResp->res, std::to_string(top), "$top",
281                     "1-" + std::to_string(maxEntriesPerPage));
282                 asyncResp->res.result(boost::beast::http::status::bad_request);
283                 return;
284             }
285         }
286         // Collections don't include the static data added by SubRoute because
287         // it has a duplicate entry for members
288         asyncResp->res.jsonValue["@odata.type"] =
289             "#LogEntryCollection.LogEntryCollection";
290         asyncResp->res.jsonValue["@odata.context"] =
291             "/redfish/v1/"
292             "$metadata#LogEntryCollection.LogEntryCollection";
293         asyncResp->res.jsonValue["@odata.id"] =
294             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
295         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
296         asyncResp->res.jsonValue["Description"] =
297             "Collection of BMC Journal Entries";
298         nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
299         logEntryArray = nlohmann::json::array();
300 
301         // Go through the journal and use the timestamp to create a unique ID
302         // for each entry
303         sd_journal *journalTmp = nullptr;
304         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
305         if (ret < 0)
306         {
307             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
308             messages::internalError(asyncResp->res);
309             return;
310         }
311         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
312             journalTmp, sd_journal_close);
313         journalTmp = nullptr;
314         uint64_t prevTs = 0;
315         int index = 0;
316         uint64_t entryCount = 0;
317         SD_JOURNAL_FOREACH(journal.get())
318         {
319             entryCount++;
320             // Handle paging using skip (number of entries to skip from the
321             // start) and top (number of entries to display)
322             if (entryCount <= skip || entryCount > skip + top)
323             {
324                 continue;
325             }
326 
327             // Get the entry timestamp
328             uint64_t curTs = 0;
329             ret = sd_journal_get_realtime_usec(journal.get(), &curTs);
330             if (ret < 0)
331             {
332                 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
333                                  << strerror(-ret);
334                 continue;
335             }
336             // If the timestamp isn't unique, increment the index
337             if (curTs == prevTs)
338             {
339                 index++;
340             }
341             else
342             {
343                 // Otherwise, reset it
344                 index = 0;
345             }
346             // Save the timestamp
347             prevTs = curTs;
348 
349             std::string idStr(std::to_string(curTs));
350             if (index > 0)
351             {
352                 idStr += "_" + std::to_string(index);
353             }
354             logEntryArray.push_back({});
355             nlohmann::json &bmcLogEntry = logEntryArray.back();
356             if (fillBMCLogEntryJson(idStr, journal.get(), bmcLogEntry) != 0)
357             {
358                 messages::internalError(asyncResp->res);
359                 return;
360             }
361         }
362         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
363         if (skip + top < entryCount)
364         {
365             asyncResp->res.jsonValue["Members@odata.nextLink"] =
366                 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" +
367                 std::to_string(skip + top);
368         }
369     }
370 };
371 
372 class BMCLogEntry : public Node
373 {
374   public:
375     BMCLogEntry(CrowApp &app) :
376         Node(app, "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries/<str>/",
377              std::string())
378     {
379         entityPrivileges = {
380             {boost::beast::http::verb::get, {{"Login"}}},
381             {boost::beast::http::verb::head, {{"Login"}}},
382             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
383             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
384             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
385             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
386     }
387 
388   private:
389     void doGet(crow::Response &res, const crow::Request &req,
390                const std::vector<std::string> &params) override
391     {
392         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
393         if (params.size() != 1)
394         {
395             messages::internalError(asyncResp->res);
396             return;
397         }
398         // Convert the unique ID back to a timestamp to find the entry
399         boost::string_view tsStr(params[0]);
400         boost::string_view indexStr(params[0]);
401         uint64_t ts = 0;
402         uint16_t index = 0;
403         auto underscorePos = tsStr.find("_");
404         if (underscorePos == tsStr.npos)
405         {
406             // Timestamp has no index
407             ts = strtoull(tsStr.data(), nullptr, 10);
408         }
409         else
410         {
411             // Timestamp has an index
412             tsStr.remove_suffix(tsStr.size() - underscorePos + 1);
413             ts = strtoull(tsStr.data(), nullptr, 10);
414             indexStr.remove_prefix(underscorePos + 1);
415             index =
416                 static_cast<uint16_t>(strtoul(indexStr.data(), nullptr, 10));
417         }
418 
419         sd_journal *journalTmp = nullptr;
420         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
421         if (ret < 0)
422         {
423             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
424             messages::internalError(asyncResp->res);
425             return;
426         }
427         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
428             journalTmp, sd_journal_close);
429         journalTmp = nullptr;
430         // Go to the timestamp in the log and move to the entry at the index
431         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
432         for (int i = 0; i <= index; i++)
433         {
434             sd_journal_next(journal.get());
435         }
436         if (fillBMCLogEntryJson(params[0], journal.get(),
437                                 asyncResp->res.jsonValue) != 0)
438         {
439             messages::internalError(asyncResp->res);
440             return;
441         }
442     }
443 };
444 
445 class CPULogService : public Node
446 {
447   public:
448     template <typename CrowApp>
449     CPULogService(CrowApp &app) :
450         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/")
451     {
452         // Set the id for SubRoute
453         Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog";
454         entityPrivileges = {
455             {boost::beast::http::verb::get, {{"Login"}}},
456             {boost::beast::http::verb::head, {{"Login"}}},
457             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
458             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
459             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
460             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
461     }
462 
463   private:
464     /**
465      * Functions triggers appropriate requests on DBus
466      */
467     void doGet(crow::Response &res, const crow::Request &req,
468                const std::vector<std::string> &params) override
469     {
470         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
471         // Copy over the static data to include the entries added by SubRoute
472         asyncResp->res.jsonValue = Node::json;
473         asyncResp->res.jsonValue["@odata.type"] =
474             "#LogService.v1_1_0.LogService";
475         asyncResp->res.jsonValue["@odata.context"] =
476             "/redfish/v1/"
477             "$metadata#LogService.LogService";
478         asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service";
479         asyncResp->res.jsonValue["Description"] = "CPU Log Service";
480         asyncResp->res.jsonValue["Id"] = "CPU Log";
481         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
482         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
483         asyncResp->res.jsonValue["Actions"] = {
484             {"Oem",
485              {{"#CpuLog.Immediate",
486                {{"target",
487                  "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
488                  "CpuLog.Immediate"}}}}}};
489 
490 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
491         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
492             {"#CpuLog.SendRawPeci",
493              {{"target",
494                "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
495                "CpuLog.SendRawPeci"}}});
496 #endif
497     }
498 };
499 
500 class CPULogEntryCollection : public Node
501 {
502   public:
503     template <typename CrowApp>
504     CPULogEntryCollection(CrowApp &app) :
505         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/")
506     {
507         // Collections use static ID for SubRoute to add to its parent, but only
508         // load dynamic data so the duplicate static members don't get displayed
509         Node::json["@odata.id"] =
510             "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
511         entityPrivileges = {
512             {boost::beast::http::verb::get, {{"Login"}}},
513             {boost::beast::http::verb::head, {{"Login"}}},
514             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
515             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
516             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
517             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
518     }
519 
520   private:
521     /**
522      * Functions triggers appropriate requests on DBus
523      */
524     void doGet(crow::Response &res, const crow::Request &req,
525                const std::vector<std::string> &params) override
526     {
527         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
528         // Collections don't include the static data added by SubRoute because
529         // it has a duplicate entry for members
530         auto getLogEntriesCallback = [asyncResp](
531                                          const boost::system::error_code ec,
532                                          const std::vector<std::string> &resp) {
533             if (ec)
534             {
535                 if (ec.value() !=
536                     boost::system::errc::no_such_file_or_directory)
537                 {
538                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
539                                      << ec.message();
540                     messages::internalError(asyncResp->res);
541                     return;
542                 }
543             }
544             asyncResp->res.jsonValue["@odata.type"] =
545                 "#LogEntryCollection.LogEntryCollection";
546             asyncResp->res.jsonValue["@odata.context"] =
547                 "/redfish/v1/"
548                 "$metadata#LogEntryCollection.LogEntryCollection";
549             asyncResp->res.jsonValue["@odata.id"] =
550                 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries";
551             asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries";
552             asyncResp->res.jsonValue["Description"] =
553                 "Collection of CPU Log Entries";
554             nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"];
555             logEntryArray = nlohmann::json::array();
556             for (const std::string &objpath : resp)
557             {
558                 // Don't list the immediate log
559                 if (objpath.compare(cpuLogImmediatePath) == 0)
560                 {
561                     continue;
562                 }
563                 std::size_t lastPos = objpath.rfind("/");
564                 if (lastPos != std::string::npos)
565                 {
566                     logEntryArray.push_back(
567                         {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/"
568                                        "CpuLog/Entries/" +
569                                            objpath.substr(lastPos + 1)}});
570                 }
571             }
572             asyncResp->res.jsonValue["Members@odata.count"] =
573                 logEntryArray.size();
574         };
575         crow::connections::systemBus->async_method_call(
576             std::move(getLogEntriesCallback),
577             "xyz.openbmc_project.ObjectMapper",
578             "/xyz/openbmc_project/object_mapper",
579             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
580             std::array<const char *, 1>{cpuLogInterface});
581     }
582 };
583 
584 std::string getLogCreatedTime(const nlohmann::json &cpuLog)
585 {
586     nlohmann::json::const_iterator metaIt = cpuLog.find("metadata");
587     if (metaIt != cpuLog.end())
588     {
589         nlohmann::json::const_iterator tsIt = metaIt->find("timestamp");
590         if (tsIt != metaIt->end())
591         {
592             const std::string *logTime = tsIt->get_ptr<const std::string *>();
593             if (logTime != nullptr)
594             {
595                 return *logTime;
596             }
597         }
598     }
599     BMCWEB_LOG_DEBUG << "failed to find log timestamp";
600 
601     return std::string();
602 }
603 
604 class CPULogEntry : public Node
605 {
606   public:
607     CPULogEntry(CrowApp &app) :
608         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/",
609              std::string())
610     {
611         entityPrivileges = {
612             {boost::beast::http::verb::get, {{"Login"}}},
613             {boost::beast::http::verb::head, {{"Login"}}},
614             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
615             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
616             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
617             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
618     }
619 
620   private:
621     void doGet(crow::Response &res, const crow::Request &req,
622                const std::vector<std::string> &params) override
623     {
624         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
625         if (params.size() != 1)
626         {
627             messages::internalError(asyncResp->res);
628             return;
629         }
630         const uint8_t logId = std::atoi(params[0].c_str());
631         auto getStoredLogCallback =
632             [asyncResp,
633              logId](const boost::system::error_code ec,
634                     const sdbusplus::message::variant<std::string> &resp) {
635                 if (ec)
636                 {
637                     BMCWEB_LOG_DEBUG << "failed to get log ec: "
638                                      << ec.message();
639                     messages::internalError(asyncResp->res);
640                     return;
641                 }
642                 const std::string *log =
643                     mapbox::getPtr<const std::string>(resp);
644                 if (log == nullptr)
645                 {
646                     messages::internalError(asyncResp->res);
647                     return;
648                 }
649                 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false);
650                 if (j.is_discarded())
651                 {
652                     messages::internalError(asyncResp->res);
653                     return;
654                 }
655                 std::string t = getLogCreatedTime(j);
656                 asyncResp->res.jsonValue = {
657                     {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
658                     {"@odata.context",
659                      "/redfish/v1/$metadata#LogEntry.LogEntry"},
660                     {"@odata.id",
661                      "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" +
662                          std::to_string(logId)},
663                     {"Name", "CPU Debug Log"},
664                     {"Id", logId},
665                     {"EntryType", "Oem"},
666                     {"OemRecordFormat", "Intel CPU Log"},
667                     {"Oem", {{"Intel", std::move(j)}}},
668                     {"Created", std::move(t)}};
669             };
670         crow::connections::systemBus->async_method_call(
671             std::move(getStoredLogCallback), cpuLogObject,
672             cpuLogPath + std::string("/") + std::to_string(logId),
673             "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log");
674     }
675 };
676 
677 class ImmediateCPULog : public Node
678 {
679   public:
680     ImmediateCPULog(CrowApp &app) :
681         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
682                   "CpuLog.Immediate/")
683     {
684         entityPrivileges = {
685             {boost::beast::http::verb::get, {{"Login"}}},
686             {boost::beast::http::verb::head, {{"Login"}}},
687             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
688             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
689             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
690             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
691     }
692 
693   private:
694     void doPost(crow::Response &res, const crow::Request &req,
695                 const std::vector<std::string> &params) override
696     {
697         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
698         static std::unique_ptr<sdbusplus::bus::match::match>
699             immediateLogMatcher;
700 
701         // Only allow one Immediate Log request at a time
702         if (immediateLogMatcher != nullptr)
703         {
704             asyncResp->res.addHeader("Retry-After", "30");
705             messages::serviceTemporarilyUnavailable(asyncResp->res, "30");
706             return;
707         }
708         // Make this static so it survives outside this method
709         static boost::asio::deadline_timer timeout(*req.ioService);
710 
711         timeout.expires_from_now(boost::posix_time::seconds(30));
712         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
713             immediateLogMatcher = nullptr;
714             if (ec)
715             {
716                 // operation_aborted is expected if timer is canceled before
717                 // completion.
718                 if (ec != boost::asio::error::operation_aborted)
719                 {
720                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
721                 }
722                 return;
723             }
724             BMCWEB_LOG_ERROR << "Timed out waiting for immediate log";
725 
726             messages::internalError(asyncResp->res);
727         });
728 
729         auto immediateLogMatcherCallback = [asyncResp](
730                                                sdbusplus::message::message &m) {
731             BMCWEB_LOG_DEBUG << "Immediate log available match fired";
732             boost::system::error_code ec;
733             timeout.cancel(ec);
734             if (ec)
735             {
736                 BMCWEB_LOG_ERROR << "error canceling timer " << ec;
737             }
738             sdbusplus::message::object_path objPath;
739             boost::container::flat_map<
740                 std::string,
741                 boost::container::flat_map<
742                     std::string, sdbusplus::message::variant<std::string>>>
743                 interfacesAdded;
744             m.read(objPath, interfacesAdded);
745             const std::string *log = mapbox::getPtr<const std::string>(
746                 interfacesAdded[cpuLogInterface]["Log"]);
747             if (log == nullptr)
748             {
749                 messages::internalError(asyncResp->res);
750                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
751                 // match object inside which this lambda is executing.  Once it
752                 // is set to nullptr, the match object will be destroyed and the
753                 // lambda will lose its context, including res, so it needs to
754                 // be the last thing done.
755                 immediateLogMatcher = nullptr;
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                 // Careful with immediateLogMatcher.  It is a unique_ptr to the
763                 // match object inside which this lambda is executing.  Once it
764                 // is set to nullptr, the match object will be destroyed and the
765                 // lambda will lose its context, including res, so it needs to
766                 // be the last thing done.
767                 immediateLogMatcher = nullptr;
768                 return;
769             }
770             std::string t = getLogCreatedTime(j);
771             asyncResp->res.jsonValue = {
772                 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"},
773                 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
774                 {"Name", "CPU Debug Log"},
775                 {"EntryType", "Oem"},
776                 {"OemRecordFormat", "Intel CPU Log"},
777                 {"Oem", {{"Intel", std::move(j)}}},
778                 {"Created", std::move(t)}};
779             // Careful with immediateLogMatcher.  It is a unique_ptr to the
780             // match object inside which this lambda is executing.  Once it is
781             // set to nullptr, the match object will be destroyed and the lambda
782             // will lose its context, including res, so it needs to be the last
783             // thing done.
784             immediateLogMatcher = nullptr;
785         };
786         immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>(
787             *crow::connections::systemBus,
788             sdbusplus::bus::match::rules::interfacesAdded() +
789                 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath),
790             std::move(immediateLogMatcherCallback));
791 
792         auto generateImmediateLogCallback =
793             [asyncResp](const boost::system::error_code ec,
794                         const std::string &resp) {
795                 if (ec)
796                 {
797                     if (ec.value() ==
798                         boost::system::errc::operation_not_supported)
799                     {
800                         messages::resourceInStandby(asyncResp->res);
801                     }
802                     else
803                     {
804                         messages::internalError(asyncResp->res);
805                     }
806                     boost::system::error_code timeoutec;
807                     timeout.cancel(timeoutec);
808                     if (timeoutec)
809                     {
810                         BMCWEB_LOG_ERROR << "error canceling timer "
811                                          << timeoutec;
812                     }
813                     immediateLogMatcher = nullptr;
814                     return;
815                 }
816             };
817         crow::connections::systemBus->async_method_call(
818             std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath,
819             cpuLogImmediateInterface, "GenerateImmediateLog");
820     }
821 };
822 
823 class SendRawPECI : public Node
824 {
825   public:
826     SendRawPECI(CrowApp &app) :
827         Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/"
828                   "CpuLog.SendRawPeci/")
829     {
830         entityPrivileges = {
831             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
832             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
833             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
834             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
835             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
836             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
837     }
838 
839   private:
840     void doPost(crow::Response &res, const crow::Request &req,
841                 const std::vector<std::string> &params) override
842     {
843         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
844         // Get the Raw PECI command from the request
845         nlohmann::json rawPECICmd;
846         if (!json_util::processJsonFromRequest(res, req, rawPECICmd))
847         {
848             return;
849         }
850         // Get the Client Address from the request
851         nlohmann::json::const_iterator caIt = rawPECICmd.find("ClientAddress");
852         if (caIt == rawPECICmd.end())
853         {
854             messages::propertyMissing(asyncResp->res, "ClientAddress");
855             return;
856         }
857         const uint64_t *ca = caIt->get_ptr<const uint64_t *>();
858         if (ca == nullptr)
859         {
860             messages::propertyValueTypeError(asyncResp->res, caIt->dump(),
861                                              "ClientAddress");
862             return;
863         }
864         // Get the Read Length from the request
865         const uint8_t clientAddress = static_cast<uint8_t>(*ca);
866         nlohmann::json::const_iterator rlIt = rawPECICmd.find("ReadLength");
867         if (rlIt == rawPECICmd.end())
868         {
869             messages::propertyMissing(asyncResp->res, "ReadLength");
870             return;
871         }
872         const uint64_t *rl = rlIt->get_ptr<const uint64_t *>();
873         if (rl == nullptr)
874         {
875             messages::propertyValueTypeError(asyncResp->res, rlIt->dump(),
876                                              "ReadLength");
877             return;
878         }
879         // Get the PECI Command from the request
880         const uint32_t readLength = static_cast<uint32_t>(*rl);
881         nlohmann::json::const_iterator pcIt = rawPECICmd.find("PECICommand");
882         if (pcIt == rawPECICmd.end())
883         {
884             messages::propertyMissing(asyncResp->res, "PECICommand");
885             return;
886         }
887         std::vector<uint8_t> peciCommand;
888         for (auto pc : *pcIt)
889         {
890             const uint64_t *val = pc.get_ptr<const uint64_t *>();
891             if (val == nullptr)
892             {
893                 messages::propertyValueTypeError(
894                     asyncResp->res, pc.dump(),
895                     "PECICommand/" + std::to_string(peciCommand.size()));
896                 return;
897             }
898             peciCommand.push_back(static_cast<uint8_t>(*val));
899         }
900         // Callback to return the Raw PECI response
901         auto sendRawPECICallback =
902             [asyncResp](const boost::system::error_code ec,
903                         const std::vector<uint8_t> &resp) {
904                 if (ec)
905                 {
906                     BMCWEB_LOG_DEBUG << "failed to send PECI command ec: "
907                                      << ec.message();
908                     messages::internalError(asyncResp->res);
909                     return;
910                 }
911                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
912                                             {"PECIResponse", resp}};
913             };
914         // Call the SendRawPECI command with the provided data
915         crow::connections::systemBus->async_method_call(
916             std::move(sendRawPECICallback), cpuLogObject, cpuLogPath,
917             cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength,
918             peciCommand);
919     }
920 };
921 
922 } // namespace redfish
923