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