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