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