xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision d337bb7249e6791d7fc3769e3d8e1ed64c563608)
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 #include "registries.hpp"
20 #include "registries/base_message_registry.hpp"
21 #include "registries/openbmc_message_registry.hpp"
22 #include "task.hpp"
23 
24 #include <systemd/sd-journal.h>
25 
26 #include <boost/algorithm/string/split.hpp>
27 #include <boost/beast/core/span.hpp>
28 #include <boost/container/flat_map.hpp>
29 #include <boost/system/linux_error.hpp>
30 #include <error_messages.hpp>
31 
32 #include <filesystem>
33 #include <string_view>
34 #include <variant>
35 
36 namespace redfish
37 {
38 
39 constexpr char const* crashdumpObject = "com.intel.crashdump";
40 constexpr char const* crashdumpPath = "/com/intel/crashdump";
41 constexpr char const* crashdumpInterface = "com.intel.crashdump";
42 constexpr char const* deleteAllInterface =
43     "xyz.openbmc_project.Collection.DeleteAll";
44 constexpr char const* crashdumpOnDemandInterface =
45     "com.intel.crashdump.OnDemand";
46 constexpr char const* crashdumpRawPECIInterface =
47     "com.intel.crashdump.SendRawPeci";
48 constexpr char const* crashdumpTelemetryInterface =
49     "com.intel.crashdump.Telemetry";
50 
51 namespace message_registries
52 {
53 static const Message* getMessageFromRegistry(
54     const std::string& messageKey,
55     const boost::beast::span<const MessageEntry> registry)
56 {
57     boost::beast::span<const MessageEntry>::const_iterator messageIt =
58         std::find_if(registry.cbegin(), registry.cend(),
59                      [&messageKey](const MessageEntry& messageEntry) {
60                          return !std::strcmp(messageEntry.first,
61                                              messageKey.c_str());
62                      });
63     if (messageIt != registry.cend())
64     {
65         return &messageIt->second;
66     }
67 
68     return nullptr;
69 }
70 
71 static const Message* getMessage(const std::string_view& messageID)
72 {
73     // Redfish MessageIds are in the form
74     // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find
75     // the right Message
76     std::vector<std::string> fields;
77     fields.reserve(4);
78     boost::split(fields, messageID, boost::is_any_of("."));
79     std::string& registryName = fields[0];
80     std::string& messageKey = fields[3];
81 
82     // Find the right registry and check it for the MessageKey
83     if (std::string(base::header.registryPrefix) == registryName)
84     {
85         return getMessageFromRegistry(
86             messageKey, boost::beast::span<const MessageEntry>(base::registry));
87     }
88     if (std::string(openbmc::header.registryPrefix) == registryName)
89     {
90         return getMessageFromRegistry(
91             messageKey,
92             boost::beast::span<const MessageEntry>(openbmc::registry));
93     }
94     return nullptr;
95 }
96 } // namespace message_registries
97 
98 namespace fs = std::filesystem;
99 
100 using GetManagedPropertyType = boost::container::flat_map<
101     std::string, std::variant<std::string, bool, uint8_t, int16_t, uint16_t,
102                               int32_t, uint32_t, int64_t, uint64_t, double>>;
103 
104 using GetManagedObjectsType = boost::container::flat_map<
105     sdbusplus::message::object_path,
106     boost::container::flat_map<std::string, GetManagedPropertyType>>;
107 
108 inline std::string translateSeverityDbusToRedfish(const std::string& s)
109 {
110     if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert")
111     {
112         return "Critical";
113     }
114     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical")
115     {
116         return "Critical";
117     }
118     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug")
119     {
120         return "OK";
121     }
122     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency")
123     {
124         return "Critical";
125     }
126     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error")
127     {
128         return "Critical";
129     }
130     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational")
131     {
132         return "OK";
133     }
134     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")
135     {
136         return "OK";
137     }
138     else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
139     {
140         return "Warning";
141     }
142     return "";
143 }
144 
145 static int getJournalMetadata(sd_journal* journal,
146                               const std::string_view& field,
147                               std::string_view& contents)
148 {
149     const char* data = nullptr;
150     size_t length = 0;
151     int ret = 0;
152     // Get the metadata from the requested field of the journal entry
153     ret = sd_journal_get_data(journal, field.data(),
154                               reinterpret_cast<const void**>(&data), &length);
155     if (ret < 0)
156     {
157         return ret;
158     }
159     contents = std::string_view(data, length);
160     // Only use the content after the "=" character.
161     contents.remove_prefix(std::min(contents.find("=") + 1, contents.size()));
162     return ret;
163 }
164 
165 static int getJournalMetadata(sd_journal* journal,
166                               const std::string_view& field, const int& base,
167                               long int& contents)
168 {
169     int ret = 0;
170     std::string_view metadata;
171     // Get the metadata from the requested field of the journal entry
172     ret = getJournalMetadata(journal, field, metadata);
173     if (ret < 0)
174     {
175         return ret;
176     }
177     contents = strtol(metadata.data(), nullptr, base);
178     return ret;
179 }
180 
181 static bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp)
182 {
183     int ret = 0;
184     uint64_t timestamp = 0;
185     ret = sd_journal_get_realtime_usec(journal, &timestamp);
186     if (ret < 0)
187     {
188         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
189                          << strerror(-ret);
190         return false;
191     }
192     entryTimestamp = crow::utility::getDateTime(
193         static_cast<std::time_t>(timestamp / 1000 / 1000));
194     return true;
195 }
196 
197 static bool getSkipParam(crow::Response& res, const crow::Request& req,
198                          uint64_t& skip)
199 {
200     boost::urls::url_view::params_type::iterator it =
201         req.urlParams.find("$skip");
202     if (it != req.urlParams.end())
203     {
204         std::string skipParam = it->value();
205         char* ptr = nullptr;
206         skip = std::strtoul(skipParam.c_str(), &ptr, 10);
207         if (skipParam.empty() || *ptr != '\0')
208         {
209 
210             messages::queryParameterValueTypeError(res, std::string(skipParam),
211                                                    "$skip");
212             return false;
213         }
214     }
215     return true;
216 }
217 
218 static constexpr const uint64_t maxEntriesPerPage = 1000;
219 static bool getTopParam(crow::Response& res, const crow::Request& req,
220                         uint64_t& top)
221 {
222     boost::urls::url_view::params_type::iterator it =
223         req.urlParams.find("$top");
224     if (it != req.urlParams.end())
225     {
226         std::string topParam = it->value();
227         char* ptr = nullptr;
228         top = std::strtoul(topParam.c_str(), &ptr, 10);
229         if (topParam.empty() || *ptr != '\0')
230         {
231             messages::queryParameterValueTypeError(res, std::string(topParam),
232                                                    "$top");
233             return false;
234         }
235         if (top < 1U || top > maxEntriesPerPage)
236         {
237 
238             messages::queryParameterOutOfRange(
239                 res, std::to_string(top), "$top",
240                 "1-" + std::to_string(maxEntriesPerPage));
241             return false;
242         }
243     }
244     return true;
245 }
246 
247 static bool getUniqueEntryID(sd_journal* journal, std::string& entryID,
248                              const bool firstEntry = true)
249 {
250     int ret = 0;
251     static uint64_t prevTs = 0;
252     static int index = 0;
253     if (firstEntry)
254     {
255         prevTs = 0;
256     }
257 
258     // Get the entry timestamp
259     uint64_t curTs = 0;
260     ret = sd_journal_get_realtime_usec(journal, &curTs);
261     if (ret < 0)
262     {
263         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
264                          << strerror(-ret);
265         return false;
266     }
267     // If the timestamp isn't unique, increment the index
268     if (curTs == prevTs)
269     {
270         index++;
271     }
272     else
273     {
274         // Otherwise, reset it
275         index = 0;
276     }
277     // Save the timestamp
278     prevTs = curTs;
279 
280     entryID = std::to_string(curTs);
281     if (index > 0)
282     {
283         entryID += "_" + std::to_string(index);
284     }
285     return true;
286 }
287 
288 static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
289                              const bool firstEntry = true)
290 {
291     static time_t prevTs = 0;
292     static int index = 0;
293     if (firstEntry)
294     {
295         prevTs = 0;
296     }
297 
298     // Get the entry timestamp
299     std::time_t curTs = 0;
300     std::tm timeStruct = {};
301     std::istringstream entryStream(logEntry);
302     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
303     {
304         curTs = std::mktime(&timeStruct);
305     }
306     // If the timestamp isn't unique, increment the index
307     if (curTs == prevTs)
308     {
309         index++;
310     }
311     else
312     {
313         // Otherwise, reset it
314         index = 0;
315     }
316     // Save the timestamp
317     prevTs = curTs;
318 
319     entryID = std::to_string(curTs);
320     if (index > 0)
321     {
322         entryID += "_" + std::to_string(index);
323     }
324     return true;
325 }
326 
327 static bool getTimestampFromID(crow::Response& res, const std::string& entryID,
328                                uint64_t& timestamp, uint64_t& index)
329 {
330     if (entryID.empty())
331     {
332         return false;
333     }
334     // Convert the unique ID back to a timestamp to find the entry
335     std::string_view tsStr(entryID);
336 
337     auto underscorePos = tsStr.find("_");
338     if (underscorePos != tsStr.npos)
339     {
340         // Timestamp has an index
341         tsStr.remove_suffix(tsStr.size() - underscorePos);
342         std::string_view indexStr(entryID);
343         indexStr.remove_prefix(underscorePos + 1);
344         std::size_t pos;
345         try
346         {
347             index = std::stoul(std::string(indexStr), &pos);
348         }
349         catch (std::invalid_argument&)
350         {
351             messages::resourceMissingAtURI(res, entryID);
352             return false;
353         }
354         catch (std::out_of_range&)
355         {
356             messages::resourceMissingAtURI(res, entryID);
357             return false;
358         }
359         if (pos != indexStr.size())
360         {
361             messages::resourceMissingAtURI(res, entryID);
362             return false;
363         }
364     }
365     // Timestamp has no index
366     std::size_t pos;
367     try
368     {
369         timestamp = std::stoull(std::string(tsStr), &pos);
370     }
371     catch (std::invalid_argument&)
372     {
373         messages::resourceMissingAtURI(res, entryID);
374         return false;
375     }
376     catch (std::out_of_range&)
377     {
378         messages::resourceMissingAtURI(res, entryID);
379         return false;
380     }
381     if (pos != tsStr.size())
382     {
383         messages::resourceMissingAtURI(res, entryID);
384         return false;
385     }
386     return true;
387 }
388 
389 static bool
390     getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles)
391 {
392     static const std::filesystem::path redfishLogDir = "/var/log";
393     static const std::string redfishLogFilename = "redfish";
394 
395     // Loop through the directory looking for redfish log files
396     for (const std::filesystem::directory_entry& dirEnt :
397          std::filesystem::directory_iterator(redfishLogDir))
398     {
399         // If we find a redfish log file, save the path
400         std::string filename = dirEnt.path().filename();
401         if (boost::starts_with(filename, redfishLogFilename))
402         {
403             redfishLogFiles.emplace_back(redfishLogDir / filename);
404         }
405     }
406     // As the log files rotate, they are appended with a ".#" that is higher for
407     // the older logs. Since we don't expect more than 10 log files, we
408     // can just sort the list to get them in order from newest to oldest
409     std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
410 
411     return !redfishLogFiles.empty();
412 }
413 
414 inline void getDumpEntryCollection(std::shared_ptr<AsyncResp>& asyncResp,
415                                    const std::string& dumpType)
416 {
417     std::string dumpPath;
418     if (dumpType == "BMC")
419     {
420         dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
421     }
422     else if (dumpType == "System")
423     {
424         dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
425     }
426     else
427     {
428         BMCWEB_LOG_ERROR << "Invalid dump type" << dumpType;
429         messages::internalError(asyncResp->res);
430         return;
431     }
432 
433     crow::connections::systemBus->async_method_call(
434         [asyncResp, dumpPath, dumpType](const boost::system::error_code ec,
435                                         GetManagedObjectsType& resp) {
436             if (ec)
437             {
438                 BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
439                 messages::internalError(asyncResp->res);
440                 return;
441             }
442 
443             nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
444             entriesArray = nlohmann::json::array();
445 
446             for (auto& object : resp)
447             {
448                 bool foundDumpEntry = false;
449                 for (auto& interfaceMap : object.second)
450                 {
451                     if (interfaceMap.first ==
452                         ("xyz.openbmc_project.Dump.Entry." + dumpType))
453                     {
454                         foundDumpEntry = true;
455                         break;
456                     }
457                 }
458 
459                 if (foundDumpEntry == false)
460                 {
461                     continue;
462                 }
463                 std::time_t timestamp;
464                 uint64_t size = 0;
465                 entriesArray.push_back({});
466                 nlohmann::json& thisEntry = entriesArray.back();
467                 const std::string& path =
468                     static_cast<const std::string&>(object.first);
469                 std::size_t lastPos = path.rfind("/");
470                 if (lastPos == std::string::npos)
471                 {
472                     continue;
473                 }
474                 std::string entryID = path.substr(lastPos + 1);
475 
476                 for (auto& interfaceMap : object.second)
477                 {
478                     if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
479                     {
480 
481                         for (auto& propertyMap : interfaceMap.second)
482                         {
483                             if (propertyMap.first == "Size")
484                             {
485                                 auto sizePtr =
486                                     std::get_if<uint64_t>(&propertyMap.second);
487                                 if (sizePtr == nullptr)
488                                 {
489                                     messages::internalError(asyncResp->res);
490                                     break;
491                                 }
492                                 size = *sizePtr;
493                                 break;
494                             }
495                         }
496                     }
497                     else if (interfaceMap.first ==
498                              "xyz.openbmc_project.Time.EpochTime")
499                     {
500 
501                         for (auto& propertyMap : interfaceMap.second)
502                         {
503                             if (propertyMap.first == "Elapsed")
504                             {
505                                 const uint64_t* usecsTimeStamp =
506                                     std::get_if<uint64_t>(&propertyMap.second);
507                                 if (usecsTimeStamp == nullptr)
508                                 {
509                                     messages::internalError(asyncResp->res);
510                                     break;
511                                 }
512                                 timestamp =
513                                     static_cast<std::time_t>(*usecsTimeStamp);
514                                 break;
515                             }
516                         }
517                     }
518                 }
519 
520                 thisEntry["@odata.type"] = "#LogEntry.v1_7_0.LogEntry";
521                 thisEntry["@odata.id"] = dumpPath + entryID;
522                 thisEntry["Id"] = entryID;
523                 thisEntry["EntryType"] = "Event";
524                 thisEntry["Created"] = crow::utility::getDateTime(timestamp);
525                 thisEntry["Name"] = dumpType + " Dump Entry";
526 
527                 thisEntry["AdditionalDataSizeBytes"] = size;
528 
529                 if (dumpType == "BMC")
530                 {
531                     thisEntry["DiagnosticDataType"] = "Manager";
532                     thisEntry["AdditionalDataURI"] =
533                         "/redfish/v1/Managers/bmc/LogServices/Dump/"
534                         "attachment/" +
535                         entryID;
536                 }
537                 else if (dumpType == "System")
538                 {
539                     thisEntry["DiagnosticDataType"] = "OEM";
540                     thisEntry["OEMDiagnosticDataType"] = "System";
541                     thisEntry["AdditionalDataURI"] =
542                         "/redfish/v1/Systems/system/LogServices/Dump/"
543                         "attachment/" +
544                         entryID;
545                 }
546             }
547             asyncResp->res.jsonValue["Members@odata.count"] =
548                 entriesArray.size();
549         },
550         "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
551         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
552 }
553 
554 inline void getDumpEntryById(std::shared_ptr<AsyncResp>& asyncResp,
555                              const std::string& entryID,
556                              const std::string& dumpType)
557 {
558     std::string dumpPath;
559     if (dumpType == "BMC")
560     {
561         dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
562     }
563     else if (dumpType == "System")
564     {
565         dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
566     }
567     else
568     {
569         BMCWEB_LOG_ERROR << "Invalid dump type" << dumpType;
570         messages::internalError(asyncResp->res);
571         return;
572     }
573 
574     crow::connections::systemBus->async_method_call(
575         [asyncResp, entryID, dumpPath, dumpType](
576             const boost::system::error_code ec, GetManagedObjectsType& resp) {
577             if (ec)
578             {
579                 BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
580                 messages::internalError(asyncResp->res);
581                 return;
582             }
583 
584             for (auto& objectPath : resp)
585             {
586                 if (objectPath.first.str.find(
587                         "/xyz/openbmc_project/dump/entry/" + entryID) ==
588                     std::string::npos)
589                 {
590                     continue;
591                 }
592 
593                 bool foundDumpEntry = false;
594                 for (auto& interfaceMap : objectPath.second)
595                 {
596                     if (interfaceMap.first ==
597                         ("xyz.openbmc_project.Dump.Entry." + dumpType))
598                     {
599                         foundDumpEntry = true;
600                         break;
601                     }
602                 }
603                 if (foundDumpEntry == false)
604                 {
605                     BMCWEB_LOG_ERROR << "Can't find Dump Entry";
606                     messages::internalError(asyncResp->res);
607                     return;
608                 }
609 
610                 std::time_t timestamp;
611                 uint64_t size = 0;
612 
613                 for (auto& interfaceMap : objectPath.second)
614                 {
615                     if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
616                     {
617                         for (auto& propertyMap : interfaceMap.second)
618                         {
619                             if (propertyMap.first == "Size")
620                             {
621                                 auto sizePtr =
622                                     std::get_if<uint64_t>(&propertyMap.second);
623                                 if (sizePtr == nullptr)
624                                 {
625                                     messages::internalError(asyncResp->res);
626                                     break;
627                                 }
628                                 size = *sizePtr;
629                                 break;
630                             }
631                         }
632                     }
633                     else if (interfaceMap.first ==
634                              "xyz.openbmc_project.Time.EpochTime")
635                     {
636                         for (auto& propertyMap : interfaceMap.second)
637                         {
638                             if (propertyMap.first == "Elapsed")
639                             {
640                                 const uint64_t* usecsTimeStamp =
641                                     std::get_if<uint64_t>(&propertyMap.second);
642                                 if (usecsTimeStamp == nullptr)
643                                 {
644                                     messages::internalError(asyncResp->res);
645                                     break;
646                                 }
647                                 timestamp =
648                                     static_cast<std::time_t>(*usecsTimeStamp);
649                                 break;
650                             }
651                         }
652                     }
653                 }
654 
655                 asyncResp->res.jsonValue["@odata.type"] =
656                     "#LogEntry.v1_7_0.LogEntry";
657                 asyncResp->res.jsonValue["@odata.id"] = dumpPath + entryID;
658                 asyncResp->res.jsonValue["Id"] = entryID;
659                 asyncResp->res.jsonValue["EntryType"] = "Event";
660                 asyncResp->res.jsonValue["Created"] =
661                     crow::utility::getDateTime(timestamp);
662                 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry";
663 
664                 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
665 
666                 if (dumpType == "BMC")
667                 {
668                     asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager";
669                     asyncResp->res.jsonValue["AdditionalDataURI"] =
670                         "/redfish/v1/Managers/bmc/LogServices/Dump/"
671                         "attachment/" +
672                         entryID;
673                 }
674                 else if (dumpType == "System")
675                 {
676                     asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
677                     asyncResp->res.jsonValue["OEMDiagnosticDataType"] =
678                         "System";
679                     asyncResp->res.jsonValue["AdditionalDataURI"] =
680                         "/redfish/v1/Systems/system/LogServices/Dump/"
681                         "attachment/" +
682                         entryID;
683                 }
684             }
685         },
686         "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
687         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
688 }
689 
690 inline void deleteDumpEntry(crow::Response& res, const std::string& entryID)
691 {
692     std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
693 
694     auto respHandler = [asyncResp](const boost::system::error_code ec) {
695         BMCWEB_LOG_DEBUG << "Dump Entry doDelete callback: Done";
696         if (ec)
697         {
698             BMCWEB_LOG_ERROR << "Dump (DBus) doDelete respHandler got error "
699                              << ec;
700             messages::internalError(asyncResp->res);
701             return;
702         }
703     };
704     crow::connections::systemBus->async_method_call(
705         respHandler, "xyz.openbmc_project.Dump.Manager",
706         "/xyz/openbmc_project/dump/entry/" + entryID,
707         "xyz.openbmc_project.Object.Delete", "Delete");
708 }
709 
710 inline void createDumpTaskCallback(const crow::Request& req,
711                                    std::shared_ptr<AsyncResp> asyncResp,
712                                    const uint32_t& dumpId,
713                                    const std::string& dumpPath,
714                                    const std::string& dumpType)
715 {
716     std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
717         [dumpId, dumpPath, dumpType, asyncResp](
718             boost::system::error_code err, sdbusplus::message::message& m,
719             const std::shared_ptr<task::TaskData>& taskData) {
720             if (err)
721             {
722                 messages::internalError(asyncResp->res);
723                 return false;
724             }
725             std::vector<std::pair<
726                 std::string,
727                 std::vector<std::pair<std::string, std::variant<std::string>>>>>
728                 interfacesList;
729 
730             sdbusplus::message::object_path objPath;
731 
732             m.read(objPath, interfacesList);
733 
734             for (auto& interface : interfacesList)
735             {
736                 if (interface.first ==
737                     ("xyz.openbmc_project.Dump.Entry." + dumpType))
738                 {
739                     nlohmann::json retMessage = messages::success();
740                     taskData->messages.emplace_back(retMessage);
741 
742                     std::string headerLoc =
743                         "Location: " + dumpPath + std::to_string(dumpId);
744                     taskData->payload->httpHeaders.emplace_back(
745                         std::move(headerLoc));
746 
747                     taskData->state = "Completed";
748                     return task::completed;
749                 }
750             }
751             return !task::completed;
752         },
753         "type='signal',interface='org.freedesktop.DBus."
754         "ObjectManager',"
755         "member='InterfacesAdded', "
756         "path='/xyz/openbmc_project/dump'");
757 
758     task->startTimer(std::chrono::minutes(3));
759     task->populateResp(asyncResp->res);
760     task->payload.emplace(req);
761 }
762 
763 inline void createDump(crow::Response& res, const crow::Request& req,
764                        const std::string& dumpType)
765 {
766     std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
767 
768     std::string dumpPath;
769     if (dumpType == "BMC")
770     {
771         dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
772     }
773     else if (dumpType == "System")
774     {
775         dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
776     }
777     else
778     {
779         BMCWEB_LOG_ERROR << "Invalid dump type: " << dumpType;
780         messages::internalError(asyncResp->res);
781         return;
782     }
783 
784     std::optional<std::string> diagnosticDataType;
785     std::optional<std::string> oemDiagnosticDataType;
786 
787     if (!redfish::json_util::readJson(
788             req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
789             "OEMDiagnosticDataType", oemDiagnosticDataType))
790     {
791         return;
792     }
793 
794     if (dumpType == "System")
795     {
796         if (!oemDiagnosticDataType || !diagnosticDataType)
797         {
798             BMCWEB_LOG_ERROR << "CreateDump action parameter "
799                                 "'DiagnosticDataType'/"
800                                 "'OEMDiagnosticDataType' value not found!";
801             messages::actionParameterMissing(
802                 asyncResp->res, "CollectDiagnosticData",
803                 "DiagnosticDataType & OEMDiagnosticDataType");
804             return;
805         }
806         else if ((*oemDiagnosticDataType != "System") ||
807                  (*diagnosticDataType != "OEM"))
808         {
809             BMCWEB_LOG_ERROR << "Wrong parameter values passed";
810             messages::invalidObject(asyncResp->res,
811                                     "System Dump creation parameters");
812             return;
813         }
814     }
815     else if (dumpType == "BMC")
816     {
817         if (!diagnosticDataType)
818         {
819             BMCWEB_LOG_ERROR << "CreateDump action parameter "
820                                 "'DiagnosticDataType' not found!";
821             messages::actionParameterMissing(
822                 asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
823             return;
824         }
825         else if (*diagnosticDataType != "Manager")
826         {
827             BMCWEB_LOG_ERROR
828                 << "Wrong parameter value passed for 'DiagnosticDataType'";
829             messages::invalidObject(asyncResp->res,
830                                     "BMC Dump creation parameters");
831             return;
832         }
833     }
834 
835     crow::connections::systemBus->async_method_call(
836         [asyncResp, req, dumpPath, dumpType](const boost::system::error_code ec,
837                                              const uint32_t& dumpId) {
838             if (ec)
839             {
840                 BMCWEB_LOG_ERROR << "CreateDump resp_handler got error " << ec;
841                 messages::internalError(asyncResp->res);
842                 return;
843             }
844             BMCWEB_LOG_DEBUG << "Dump Created. Id: " << dumpId;
845 
846             createDumpTaskCallback(req, asyncResp, dumpId, dumpPath, dumpType);
847         },
848         "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
849         "xyz.openbmc_project.Dump.Create", "CreateDump");
850 }
851 
852 inline void clearDump(crow::Response& res, const std::string& dumpInterface)
853 {
854     std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
855     crow::connections::systemBus->async_method_call(
856         [asyncResp](const boost::system::error_code ec,
857                     const std::vector<std::string>& subTreePaths) {
858             if (ec)
859             {
860                 BMCWEB_LOG_ERROR << "resp_handler got error " << ec;
861                 messages::internalError(asyncResp->res);
862                 return;
863             }
864 
865             for (const std::string& path : subTreePaths)
866             {
867                 std::size_t pos = path.rfind("/");
868                 if (pos != std::string::npos)
869                 {
870                     std::string logID = path.substr(pos + 1);
871                     deleteDumpEntry(asyncResp->res, logID);
872                 }
873             }
874         },
875         "xyz.openbmc_project.ObjectMapper",
876         "/xyz/openbmc_project/object_mapper",
877         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
878         "/xyz/openbmc_project/dump", 0,
879         std::array<std::string, 1>{dumpInterface});
880 }
881 
882 static void ParseCrashdumpParameters(
883     const std::vector<std::pair<std::string, VariantType>>& params,
884     std::string& filename, std::string& timestamp, std::string& logfile)
885 {
886     for (auto property : params)
887     {
888         if (property.first == "Timestamp")
889         {
890             const std::string* value =
891                 std::get_if<std::string>(&property.second);
892             if (value != nullptr)
893             {
894                 timestamp = *value;
895             }
896         }
897         else if (property.first == "Filename")
898         {
899             const std::string* value =
900                 std::get_if<std::string>(&property.second);
901             if (value != nullptr)
902             {
903                 filename = *value;
904             }
905         }
906         else if (property.first == "Log")
907         {
908             const std::string* value =
909                 std::get_if<std::string>(&property.second);
910             if (value != nullptr)
911             {
912                 logfile = *value;
913             }
914         }
915     }
916 }
917 
918 constexpr char const* postCodeIface = "xyz.openbmc_project.State.Boot.PostCode";
919 class SystemLogServiceCollection : public Node
920 {
921   public:
922     SystemLogServiceCollection(App& app) :
923         Node(app, "/redfish/v1/Systems/system/LogServices/")
924     {
925         entityPrivileges = {
926             {boost::beast::http::verb::get, {{"Login"}}},
927             {boost::beast::http::verb::head, {{"Login"}}},
928             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
929             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
930             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
931             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
932     }
933 
934   private:
935     /**
936      * Functions triggers appropriate requests on DBus
937      */
938     void doGet(crow::Response& res, const crow::Request&,
939                const std::vector<std::string>&) override
940     {
941         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
942         // Collections don't include the static data added by SubRoute because
943         // it has a duplicate entry for members
944         asyncResp->res.jsonValue["@odata.type"] =
945             "#LogServiceCollection.LogServiceCollection";
946         asyncResp->res.jsonValue["@odata.id"] =
947             "/redfish/v1/Systems/system/LogServices";
948         asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
949         asyncResp->res.jsonValue["Description"] =
950             "Collection of LogServices for this Computer System";
951         nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
952         logServiceArray = nlohmann::json::array();
953         logServiceArray.push_back(
954             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}});
955 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
956         logServiceArray.push_back(
957             {{"@odata.id", "/redfish/v1/Systems/system/LogServices/Dump"}});
958 #endif
959 
960 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
961         logServiceArray.push_back(
962             {{"@odata.id",
963               "/redfish/v1/Systems/system/LogServices/Crashdump"}});
964 #endif
965         asyncResp->res.jsonValue["Members@odata.count"] =
966             logServiceArray.size();
967 
968         crow::connections::systemBus->async_method_call(
969             [asyncResp](const boost::system::error_code ec,
970                         const std::vector<std::string>& subtreePath) {
971                 if (ec)
972                 {
973                     BMCWEB_LOG_ERROR << ec;
974                     return;
975                 }
976 
977                 for (auto& pathStr : subtreePath)
978                 {
979                     if (pathStr.find("PostCode") != std::string::npos)
980                     {
981                         nlohmann::json& logServiceArrayLocal =
982                             asyncResp->res.jsonValue["Members"];
983                         logServiceArrayLocal.push_back(
984                             {{"@odata.id", "/redfish/v1/Systems/system/"
985                                            "LogServices/PostCodes"}});
986                         asyncResp->res.jsonValue["Members@odata.count"] =
987                             logServiceArrayLocal.size();
988                         return;
989                     }
990                 }
991             },
992             "xyz.openbmc_project.ObjectMapper",
993             "/xyz/openbmc_project/object_mapper",
994             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 0,
995             std::array<const char*, 1>{postCodeIface});
996     }
997 };
998 
999 class EventLogService : public Node
1000 {
1001   public:
1002     EventLogService(App& app) :
1003         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/")
1004     {
1005         entityPrivileges = {
1006             {boost::beast::http::verb::get, {{"Login"}}},
1007             {boost::beast::http::verb::head, {{"Login"}}},
1008             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1009             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1010             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1011             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1012     }
1013 
1014   private:
1015     void doGet(crow::Response& res, const crow::Request&,
1016                const std::vector<std::string>&) override
1017     {
1018         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1019 
1020         asyncResp->res.jsonValue["@odata.id"] =
1021             "/redfish/v1/Systems/system/LogServices/EventLog";
1022         asyncResp->res.jsonValue["@odata.type"] =
1023             "#LogService.v1_1_0.LogService";
1024         asyncResp->res.jsonValue["Name"] = "Event Log Service";
1025         asyncResp->res.jsonValue["Description"] = "System Event Log Service";
1026         asyncResp->res.jsonValue["Id"] = "EventLog";
1027         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1028         asyncResp->res.jsonValue["Entries"] = {
1029             {"@odata.id",
1030              "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}};
1031         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
1032 
1033             {"target", "/redfish/v1/Systems/system/LogServices/EventLog/"
1034                        "Actions/LogService.ClearLog"}};
1035     }
1036 };
1037 
1038 class JournalEventLogClear : public Node
1039 {
1040   public:
1041     JournalEventLogClear(App& app) :
1042         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
1043                   "LogService.ClearLog/")
1044     {
1045         entityPrivileges = {
1046             {boost::beast::http::verb::get, {{"Login"}}},
1047             {boost::beast::http::verb::head, {{"Login"}}},
1048             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1049             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1050             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1051             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1052     }
1053 
1054   private:
1055     void doPost(crow::Response& res, const crow::Request&,
1056                 const std::vector<std::string>&) override
1057     {
1058         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1059 
1060         // Clear the EventLog by deleting the log files
1061         std::vector<std::filesystem::path> redfishLogFiles;
1062         if (getRedfishLogFiles(redfishLogFiles))
1063         {
1064             for (const std::filesystem::path& file : redfishLogFiles)
1065             {
1066                 std::error_code ec;
1067                 std::filesystem::remove(file, ec);
1068             }
1069         }
1070 
1071         // Reload rsyslog so it knows to start new log files
1072         crow::connections::systemBus->async_method_call(
1073             [asyncResp](const boost::system::error_code ec) {
1074                 if (ec)
1075                 {
1076                     BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
1077                     messages::internalError(asyncResp->res);
1078                     return;
1079                 }
1080 
1081                 messages::success(asyncResp->res);
1082             },
1083             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1084             "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
1085             "replace");
1086     }
1087 };
1088 
1089 static int fillEventLogEntryJson(const std::string& logEntryID,
1090                                  const std::string logEntry,
1091                                  nlohmann::json& logEntryJson)
1092 {
1093     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
1094     // First get the Timestamp
1095     size_t space = logEntry.find_first_of(" ");
1096     if (space == std::string::npos)
1097     {
1098         return 1;
1099     }
1100     std::string timestamp = logEntry.substr(0, space);
1101     // Then get the log contents
1102     size_t entryStart = logEntry.find_first_not_of(" ", space);
1103     if (entryStart == std::string::npos)
1104     {
1105         return 1;
1106     }
1107     std::string_view entry(logEntry);
1108     entry.remove_prefix(entryStart);
1109     // Use split to separate the entry into its fields
1110     std::vector<std::string> logEntryFields;
1111     boost::split(logEntryFields, entry, boost::is_any_of(","),
1112                  boost::token_compress_on);
1113     // We need at least a MessageId to be valid
1114     if (logEntryFields.size() < 1)
1115     {
1116         return 1;
1117     }
1118     std::string& messageID = logEntryFields[0];
1119 
1120     // Get the Message from the MessageRegistry
1121     const message_registries::Message* message =
1122         message_registries::getMessage(messageID);
1123 
1124     std::string msg;
1125     std::string severity;
1126     if (message != nullptr)
1127     {
1128         msg = message->message;
1129         severity = message->severity;
1130     }
1131 
1132     // Get the MessageArgs from the log if there are any
1133     boost::beast::span<std::string> messageArgs;
1134     if (logEntryFields.size() > 1)
1135     {
1136         std::string& messageArgsStart = logEntryFields[1];
1137         // If the first string is empty, assume there are no MessageArgs
1138         std::size_t messageArgsSize = 0;
1139         if (!messageArgsStart.empty())
1140         {
1141             messageArgsSize = logEntryFields.size() - 1;
1142         }
1143 
1144         messageArgs = {&messageArgsStart, messageArgsSize};
1145 
1146         // Fill the MessageArgs into the Message
1147         int i = 0;
1148         for (const std::string& messageArg : messageArgs)
1149         {
1150             std::string argStr = "%" + std::to_string(++i);
1151             size_t argPos = msg.find(argStr);
1152             if (argPos != std::string::npos)
1153             {
1154                 msg.replace(argPos, argStr.length(), messageArg);
1155             }
1156         }
1157     }
1158 
1159     // Get the Created time from the timestamp. The log timestamp is in RFC3339
1160     // format which matches the Redfish format except for the fractional seconds
1161     // between the '.' and the '+', so just remove them.
1162     std::size_t dot = timestamp.find_first_of(".");
1163     std::size_t plus = timestamp.find_first_of("+");
1164     if (dot != std::string::npos && plus != std::string::npos)
1165     {
1166         timestamp.erase(dot, plus - dot);
1167     }
1168 
1169     // Fill in the log entry with the gathered data
1170     logEntryJson = {
1171         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1172         {"@odata.id",
1173          "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1174              logEntryID},
1175         {"Name", "System Event Log Entry"},
1176         {"Id", logEntryID},
1177         {"Message", std::move(msg)},
1178         {"MessageId", std::move(messageID)},
1179         {"MessageArgs", std::move(messageArgs)},
1180         {"EntryType", "Event"},
1181         {"Severity", std::move(severity)},
1182         {"Created", std::move(timestamp)}};
1183     return 0;
1184 }
1185 
1186 class JournalEventLogEntryCollection : public Node
1187 {
1188   public:
1189     JournalEventLogEntryCollection(App& app) :
1190         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
1191     {
1192         entityPrivileges = {
1193             {boost::beast::http::verb::get, {{"Login"}}},
1194             {boost::beast::http::verb::head, {{"Login"}}},
1195             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1196             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1197             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1198             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1199     }
1200 
1201   private:
1202     void doGet(crow::Response& res, const crow::Request& req,
1203                const std::vector<std::string>&) override
1204     {
1205         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1206         uint64_t skip = 0;
1207         uint64_t top = maxEntriesPerPage; // Show max entries by default
1208         if (!getSkipParam(asyncResp->res, req, skip))
1209         {
1210             return;
1211         }
1212         if (!getTopParam(asyncResp->res, req, top))
1213         {
1214             return;
1215         }
1216         // Collections don't include the static data added by SubRoute because
1217         // it has a duplicate entry for members
1218         asyncResp->res.jsonValue["@odata.type"] =
1219             "#LogEntryCollection.LogEntryCollection";
1220         asyncResp->res.jsonValue["@odata.id"] =
1221             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
1222         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1223         asyncResp->res.jsonValue["Description"] =
1224             "Collection of System Event Log Entries";
1225 
1226         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1227         logEntryArray = nlohmann::json::array();
1228         // Go through the log files and create a unique ID for each entry
1229         std::vector<std::filesystem::path> redfishLogFiles;
1230         getRedfishLogFiles(redfishLogFiles);
1231         uint64_t entryCount = 0;
1232         std::string logEntry;
1233 
1234         // Oldest logs are in the last file, so start there and loop backwards
1235         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1236              it++)
1237         {
1238             std::ifstream logStream(*it);
1239             if (!logStream.is_open())
1240             {
1241                 continue;
1242             }
1243 
1244             // Reset the unique ID on the first entry
1245             bool firstEntry = true;
1246             while (std::getline(logStream, logEntry))
1247             {
1248                 entryCount++;
1249                 // Handle paging using skip (number of entries to skip from the
1250                 // start) and top (number of entries to display)
1251                 if (entryCount <= skip || entryCount > skip + top)
1252                 {
1253                     continue;
1254                 }
1255 
1256                 std::string idStr;
1257                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1258                 {
1259                     continue;
1260                 }
1261 
1262                 if (firstEntry)
1263                 {
1264                     firstEntry = false;
1265                 }
1266 
1267                 logEntryArray.push_back({});
1268                 nlohmann::json& bmcLogEntry = logEntryArray.back();
1269                 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
1270                 {
1271                     messages::internalError(asyncResp->res);
1272                     return;
1273                 }
1274             }
1275         }
1276         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1277         if (skip + top < entryCount)
1278         {
1279             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1280                 "/redfish/v1/Systems/system/LogServices/EventLog/"
1281                 "Entries?$skip=" +
1282                 std::to_string(skip + top);
1283         }
1284     }
1285 };
1286 
1287 class JournalEventLogEntry : public Node
1288 {
1289   public:
1290     JournalEventLogEntry(App& app) :
1291         Node(app,
1292              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
1293              std::string())
1294     {
1295         entityPrivileges = {
1296             {boost::beast::http::verb::get, {{"Login"}}},
1297             {boost::beast::http::verb::head, {{"Login"}}},
1298             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1299             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1300             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1301             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1302     }
1303 
1304   private:
1305     void doGet(crow::Response& res, const crow::Request&,
1306                const std::vector<std::string>& params) override
1307     {
1308         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1309         if (params.size() != 1)
1310         {
1311             messages::internalError(asyncResp->res);
1312             return;
1313         }
1314         const std::string& targetID = params[0];
1315 
1316         // Go through the log files and check the unique ID for each entry to
1317         // find the target entry
1318         std::vector<std::filesystem::path> redfishLogFiles;
1319         getRedfishLogFiles(redfishLogFiles);
1320         std::string logEntry;
1321 
1322         // Oldest logs are in the last file, so start there and loop backwards
1323         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1324              it++)
1325         {
1326             std::ifstream logStream(*it);
1327             if (!logStream.is_open())
1328             {
1329                 continue;
1330             }
1331 
1332             // Reset the unique ID on the first entry
1333             bool firstEntry = true;
1334             while (std::getline(logStream, logEntry))
1335             {
1336                 std::string idStr;
1337                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1338                 {
1339                     continue;
1340                 }
1341 
1342                 if (firstEntry)
1343                 {
1344                     firstEntry = false;
1345                 }
1346 
1347                 if (idStr == targetID)
1348                 {
1349                     if (fillEventLogEntryJson(idStr, logEntry,
1350                                               asyncResp->res.jsonValue) != 0)
1351                     {
1352                         messages::internalError(asyncResp->res);
1353                         return;
1354                     }
1355                     return;
1356                 }
1357             }
1358         }
1359         // Requested ID was not found
1360         messages::resourceMissingAtURI(asyncResp->res, targetID);
1361     }
1362 };
1363 
1364 class DBusEventLogEntryCollection : public Node
1365 {
1366   public:
1367     DBusEventLogEntryCollection(App& app) :
1368         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
1369     {
1370         entityPrivileges = {
1371             {boost::beast::http::verb::get, {{"Login"}}},
1372             {boost::beast::http::verb::head, {{"Login"}}},
1373             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1374             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1375             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1376             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1377     }
1378 
1379   private:
1380     void doGet(crow::Response& res, const crow::Request&,
1381                const std::vector<std::string>&) override
1382     {
1383         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1384 
1385         // Collections don't include the static data added by SubRoute because
1386         // it has a duplicate entry for members
1387         asyncResp->res.jsonValue["@odata.type"] =
1388             "#LogEntryCollection.LogEntryCollection";
1389         asyncResp->res.jsonValue["@odata.id"] =
1390             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
1391         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1392         asyncResp->res.jsonValue["Description"] =
1393             "Collection of System Event Log Entries";
1394 
1395         // DBus implementation of EventLog/Entries
1396         // Make call to Logging Service to find all log entry objects
1397         crow::connections::systemBus->async_method_call(
1398             [asyncResp](const boost::system::error_code ec,
1399                         GetManagedObjectsType& resp) {
1400                 if (ec)
1401                 {
1402                     // TODO Handle for specific error code
1403                     BMCWEB_LOG_ERROR
1404                         << "getLogEntriesIfaceData resp_handler got error "
1405                         << ec;
1406                     messages::internalError(asyncResp->res);
1407                     return;
1408                 }
1409                 nlohmann::json& entriesArray =
1410                     asyncResp->res.jsonValue["Members"];
1411                 entriesArray = nlohmann::json::array();
1412                 for (auto& objectPath : resp)
1413                 {
1414                     for (auto& interfaceMap : objectPath.second)
1415                     {
1416                         if (interfaceMap.first !=
1417                             "xyz.openbmc_project.Logging.Entry")
1418                         {
1419                             BMCWEB_LOG_DEBUG << "Bailing early on "
1420                                              << interfaceMap.first;
1421                             continue;
1422                         }
1423                         entriesArray.push_back({});
1424                         nlohmann::json& thisEntry = entriesArray.back();
1425                         uint32_t* id = nullptr;
1426                         std::time_t timestamp{};
1427                         std::time_t updateTimestamp{};
1428                         std::string* severity = nullptr;
1429                         std::string* message = nullptr;
1430 
1431                         for (auto& propertyMap : interfaceMap.second)
1432                         {
1433                             if (propertyMap.first == "Id")
1434                             {
1435                                 id = std::get_if<uint32_t>(&propertyMap.second);
1436                                 if (id == nullptr)
1437                                 {
1438                                     messages::internalError(asyncResp->res);
1439                                 }
1440                             }
1441                             else if (propertyMap.first == "Timestamp")
1442                             {
1443                                 const uint64_t* millisTimeStamp =
1444                                     std::get_if<uint64_t>(&propertyMap.second);
1445                                 if (millisTimeStamp == nullptr)
1446                                 {
1447                                     messages::internalError(asyncResp->res);
1448                                 }
1449                                 else
1450                                 {
1451                                     timestamp = crow::utility::getTimestamp(
1452                                         *millisTimeStamp);
1453                                 }
1454                             }
1455                             else if (propertyMap.first == "UpdateTimestamp")
1456                             {
1457                                 const uint64_t* millisTimeStamp =
1458                                     std::get_if<uint64_t>(&propertyMap.second);
1459                                 if (millisTimeStamp == nullptr)
1460                                 {
1461                                     messages::internalError(asyncResp->res);
1462                                 }
1463                                 else
1464                                 {
1465                                     updateTimestamp =
1466                                         crow::utility::getTimestamp(
1467                                             *millisTimeStamp);
1468                                 }
1469                             }
1470                             else if (propertyMap.first == "Severity")
1471                             {
1472                                 severity = std::get_if<std::string>(
1473                                     &propertyMap.second);
1474                                 if (severity == nullptr)
1475                                 {
1476                                     messages::internalError(asyncResp->res);
1477                                 }
1478                             }
1479                             else if (propertyMap.first == "Message")
1480                             {
1481                                 message = std::get_if<std::string>(
1482                                     &propertyMap.second);
1483                                 if (message == nullptr)
1484                                 {
1485                                     messages::internalError(asyncResp->res);
1486                                 }
1487                             }
1488                         }
1489                         thisEntry = {
1490                             {"@odata.type", "#LogEntry.v1_6_0.LogEntry"},
1491                             {"@odata.id",
1492                              "/redfish/v1/Systems/system/LogServices/EventLog/"
1493                              "Entries/" +
1494                                  std::to_string(*id)},
1495                             {"Name", "System Event Log Entry"},
1496                             {"Id", std::to_string(*id)},
1497                             {"Message", *message},
1498                             {"EntryType", "Event"},
1499                             {"Severity",
1500                              translateSeverityDbusToRedfish(*severity)},
1501                             {"Created", crow::utility::getDateTime(timestamp)},
1502                             {"Modified",
1503                              crow::utility::getDateTime(updateTimestamp)}};
1504                     }
1505                 }
1506                 std::sort(entriesArray.begin(), entriesArray.end(),
1507                           [](const nlohmann::json& left,
1508                              const nlohmann::json& right) {
1509                               return (left["Id"] <= right["Id"]);
1510                           });
1511                 asyncResp->res.jsonValue["Members@odata.count"] =
1512                     entriesArray.size();
1513             },
1514             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
1515             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1516     }
1517 };
1518 
1519 class DBusEventLogEntry : public Node
1520 {
1521   public:
1522     DBusEventLogEntry(App& app) :
1523         Node(app,
1524              "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/",
1525              std::string())
1526     {
1527         entityPrivileges = {
1528             {boost::beast::http::verb::get, {{"Login"}}},
1529             {boost::beast::http::verb::head, {{"Login"}}},
1530             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1531             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1532             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1533             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1534     }
1535 
1536   private:
1537     void doGet(crow::Response& res, const crow::Request&,
1538                const std::vector<std::string>& params) override
1539     {
1540         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1541         if (params.size() != 1)
1542         {
1543             messages::internalError(asyncResp->res);
1544             return;
1545         }
1546         const std::string& entryID = params[0];
1547 
1548         // DBus implementation of EventLog/Entries
1549         // Make call to Logging Service to find all log entry objects
1550         crow::connections::systemBus->async_method_call(
1551             [asyncResp, entryID](const boost::system::error_code ec,
1552                                  GetManagedPropertyType& resp) {
1553                 if (ec)
1554                 {
1555                     BMCWEB_LOG_ERROR
1556                         << "EventLogEntry (DBus) resp_handler got error " << ec;
1557                     messages::internalError(asyncResp->res);
1558                     return;
1559                 }
1560                 uint32_t* id = nullptr;
1561                 std::time_t timestamp{};
1562                 std::time_t updateTimestamp{};
1563                 std::string* severity = nullptr;
1564                 std::string* message = nullptr;
1565 
1566                 for (auto& propertyMap : resp)
1567                 {
1568                     if (propertyMap.first == "Id")
1569                     {
1570                         id = std::get_if<uint32_t>(&propertyMap.second);
1571                         if (id == nullptr)
1572                         {
1573                             messages::internalError(asyncResp->res);
1574                         }
1575                     }
1576                     else if (propertyMap.first == "Timestamp")
1577                     {
1578                         const uint64_t* millisTimeStamp =
1579                             std::get_if<uint64_t>(&propertyMap.second);
1580                         if (millisTimeStamp == nullptr)
1581                         {
1582                             messages::internalError(asyncResp->res);
1583                         }
1584                         else
1585                         {
1586                             timestamp =
1587                                 crow::utility::getTimestamp(*millisTimeStamp);
1588                         }
1589                     }
1590                     else if (propertyMap.first == "UpdateTimestamp")
1591                     {
1592                         const uint64_t* millisTimeStamp =
1593                             std::get_if<uint64_t>(&propertyMap.second);
1594                         if (millisTimeStamp == nullptr)
1595                         {
1596                             messages::internalError(asyncResp->res);
1597                         }
1598                         else
1599                         {
1600                             updateTimestamp =
1601                                 crow::utility::getTimestamp(*millisTimeStamp);
1602                         }
1603                     }
1604                     else if (propertyMap.first == "Severity")
1605                     {
1606                         severity =
1607                             std::get_if<std::string>(&propertyMap.second);
1608                         if (severity == nullptr)
1609                         {
1610                             messages::internalError(asyncResp->res);
1611                         }
1612                     }
1613                     else if (propertyMap.first == "Message")
1614                     {
1615                         message = std::get_if<std::string>(&propertyMap.second);
1616                         if (message == nullptr)
1617                         {
1618                             messages::internalError(asyncResp->res);
1619                         }
1620                     }
1621                 }
1622                 if (id == nullptr || message == nullptr || severity == nullptr)
1623                 {
1624                     return;
1625                 }
1626                 asyncResp->res.jsonValue = {
1627                     {"@odata.type", "#LogEntry.v1_6_0.LogEntry"},
1628                     {"@odata.id",
1629                      "/redfish/v1/Systems/system/LogServices/EventLog/"
1630                      "Entries/" +
1631                          std::to_string(*id)},
1632                     {"Name", "System Event Log Entry"},
1633                     {"Id", std::to_string(*id)},
1634                     {"Message", *message},
1635                     {"EntryType", "Event"},
1636                     {"Severity", translateSeverityDbusToRedfish(*severity)},
1637                     {"Created", crow::utility::getDateTime(timestamp)},
1638                     {"Modified", crow::utility::getDateTime(updateTimestamp)}};
1639             },
1640             "xyz.openbmc_project.Logging",
1641             "/xyz/openbmc_project/logging/entry/" + entryID,
1642             "org.freedesktop.DBus.Properties", "GetAll",
1643             "xyz.openbmc_project.Logging.Entry");
1644     }
1645 
1646     void doDelete(crow::Response& res, const crow::Request&,
1647                   const std::vector<std::string>& params) override
1648     {
1649 
1650         BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1651 
1652         auto asyncResp = std::make_shared<AsyncResp>(res);
1653 
1654         if (params.size() != 1)
1655         {
1656             messages::internalError(asyncResp->res);
1657             return;
1658         }
1659         std::string entryID = params[0];
1660 
1661         dbus::utility::escapePathForDbus(entryID);
1662 
1663         // Process response from Logging service.
1664         auto respHandler = [asyncResp](const boost::system::error_code ec) {
1665             BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1666             if (ec)
1667             {
1668                 // TODO Handle for specific error code
1669                 BMCWEB_LOG_ERROR
1670                     << "EventLogEntry (DBus) doDelete respHandler got error "
1671                     << ec;
1672                 asyncResp->res.result(
1673                     boost::beast::http::status::internal_server_error);
1674                 return;
1675             }
1676 
1677             asyncResp->res.result(boost::beast::http::status::ok);
1678         };
1679 
1680         // Make call to Logging service to request Delete Log
1681         crow::connections::systemBus->async_method_call(
1682             respHandler, "xyz.openbmc_project.Logging",
1683             "/xyz/openbmc_project/logging/entry/" + entryID,
1684             "xyz.openbmc_project.Object.Delete", "Delete");
1685     }
1686 };
1687 
1688 class BMCLogServiceCollection : public Node
1689 {
1690   public:
1691     BMCLogServiceCollection(App& app) :
1692         Node(app, "/redfish/v1/Managers/bmc/LogServices/")
1693     {
1694         entityPrivileges = {
1695             {boost::beast::http::verb::get, {{"Login"}}},
1696             {boost::beast::http::verb::head, {{"Login"}}},
1697             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1698             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1699             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1700             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1701     }
1702 
1703   private:
1704     /**
1705      * Functions triggers appropriate requests on DBus
1706      */
1707     void doGet(crow::Response& res, const crow::Request&,
1708                const std::vector<std::string>&) override
1709     {
1710         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1711         // Collections don't include the static data added by SubRoute because
1712         // it has a duplicate entry for members
1713         asyncResp->res.jsonValue["@odata.type"] =
1714             "#LogServiceCollection.LogServiceCollection";
1715         asyncResp->res.jsonValue["@odata.id"] =
1716             "/redfish/v1/Managers/bmc/LogServices";
1717         asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
1718         asyncResp->res.jsonValue["Description"] =
1719             "Collection of LogServices for this Manager";
1720         nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
1721         logServiceArray = nlohmann::json::array();
1722 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
1723         logServiceArray.push_back(
1724             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump"}});
1725 #endif
1726 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
1727         logServiceArray.push_back(
1728             {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
1729 #endif
1730         asyncResp->res.jsonValue["Members@odata.count"] =
1731             logServiceArray.size();
1732     }
1733 };
1734 
1735 class BMCJournalLogService : public Node
1736 {
1737   public:
1738     BMCJournalLogService(App& app) :
1739         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
1740     {
1741         entityPrivileges = {
1742             {boost::beast::http::verb::get, {{"Login"}}},
1743             {boost::beast::http::verb::head, {{"Login"}}},
1744             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1745             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1746             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1747             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1748     }
1749 
1750   private:
1751     void doGet(crow::Response& res, const crow::Request&,
1752                const std::vector<std::string>&) override
1753     {
1754         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1755         asyncResp->res.jsonValue["@odata.type"] =
1756             "#LogService.v1_1_0.LogService";
1757         asyncResp->res.jsonValue["@odata.id"] =
1758             "/redfish/v1/Managers/bmc/LogServices/Journal";
1759         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
1760         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
1761         asyncResp->res.jsonValue["Id"] = "BMC Journal";
1762         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1763         asyncResp->res.jsonValue["Entries"] = {
1764             {"@odata.id",
1765              "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}};
1766     }
1767 };
1768 
1769 static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID,
1770                                       sd_journal* journal,
1771                                       nlohmann::json& bmcJournalLogEntryJson)
1772 {
1773     // Get the Log Entry contents
1774     int ret = 0;
1775 
1776     std::string_view msg;
1777     ret = getJournalMetadata(journal, "MESSAGE", msg);
1778     if (ret < 0)
1779     {
1780         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
1781         return 1;
1782     }
1783 
1784     // Get the severity from the PRIORITY field
1785     long int severity = 8; // Default to an invalid priority
1786     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
1787     if (ret < 0)
1788     {
1789         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
1790     }
1791 
1792     // Get the Created time from the timestamp
1793     std::string entryTimeStr;
1794     if (!getEntryTimestamp(journal, entryTimeStr))
1795     {
1796         return 1;
1797     }
1798 
1799     // Fill in the log entry with the gathered data
1800     bmcJournalLogEntryJson = {
1801         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1802         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
1803                           bmcJournalLogEntryID},
1804         {"Name", "BMC Journal Entry"},
1805         {"Id", bmcJournalLogEntryID},
1806         {"Message", msg},
1807         {"EntryType", "Oem"},
1808         {"Severity",
1809          severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"},
1810         {"OemRecordFormat", "BMC Journal Entry"},
1811         {"Created", std::move(entryTimeStr)}};
1812     return 0;
1813 }
1814 
1815 class BMCJournalLogEntryCollection : public Node
1816 {
1817   public:
1818     BMCJournalLogEntryCollection(App& app) :
1819         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
1820     {
1821         entityPrivileges = {
1822             {boost::beast::http::verb::get, {{"Login"}}},
1823             {boost::beast::http::verb::head, {{"Login"}}},
1824             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1825             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1826             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1827             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1828     }
1829 
1830   private:
1831     void doGet(crow::Response& res, const crow::Request& req,
1832                const std::vector<std::string>&) override
1833     {
1834         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1835         static constexpr const long maxEntriesPerPage = 1000;
1836         uint64_t skip = 0;
1837         uint64_t top = maxEntriesPerPage; // Show max entries by default
1838         if (!getSkipParam(asyncResp->res, req, skip))
1839         {
1840             return;
1841         }
1842         if (!getTopParam(asyncResp->res, req, top))
1843         {
1844             return;
1845         }
1846         // Collections don't include the static data added by SubRoute because
1847         // it has a duplicate entry for members
1848         asyncResp->res.jsonValue["@odata.type"] =
1849             "#LogEntryCollection.LogEntryCollection";
1850         asyncResp->res.jsonValue["@odata.id"] =
1851             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1852         asyncResp->res.jsonValue["@odata.id"] =
1853             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
1854         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
1855         asyncResp->res.jsonValue["Description"] =
1856             "Collection of BMC Journal Entries";
1857         asyncResp->res.jsonValue["@odata.id"] =
1858             "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries";
1859         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1860         logEntryArray = nlohmann::json::array();
1861 
1862         // Go through the journal and use the timestamp to create a unique ID
1863         // for each entry
1864         sd_journal* journalTmp = nullptr;
1865         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1866         if (ret < 0)
1867         {
1868             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1869             messages::internalError(asyncResp->res);
1870             return;
1871         }
1872         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1873             journalTmp, sd_journal_close);
1874         journalTmp = nullptr;
1875         uint64_t entryCount = 0;
1876         // Reset the unique ID on the first entry
1877         bool firstEntry = true;
1878         SD_JOURNAL_FOREACH(journal.get())
1879         {
1880             entryCount++;
1881             // Handle paging using skip (number of entries to skip from the
1882             // start) and top (number of entries to display)
1883             if (entryCount <= skip || entryCount > skip + top)
1884             {
1885                 continue;
1886             }
1887 
1888             std::string idStr;
1889             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1890             {
1891                 continue;
1892             }
1893 
1894             if (firstEntry)
1895             {
1896                 firstEntry = false;
1897             }
1898 
1899             logEntryArray.push_back({});
1900             nlohmann::json& bmcJournalLogEntry = logEntryArray.back();
1901             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
1902                                            bmcJournalLogEntry) != 0)
1903             {
1904                 messages::internalError(asyncResp->res);
1905                 return;
1906             }
1907         }
1908         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1909         if (skip + top < entryCount)
1910         {
1911             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1912                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
1913                 std::to_string(skip + top);
1914         }
1915     }
1916 };
1917 
1918 class BMCJournalLogEntry : public Node
1919 {
1920   public:
1921     BMCJournalLogEntry(App& app) :
1922         Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/",
1923              std::string())
1924     {
1925         entityPrivileges = {
1926             {boost::beast::http::verb::get, {{"Login"}}},
1927             {boost::beast::http::verb::head, {{"Login"}}},
1928             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
1929             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
1930             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
1931             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
1932     }
1933 
1934   private:
1935     void doGet(crow::Response& res, const crow::Request&,
1936                const std::vector<std::string>& params) override
1937     {
1938         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1939         if (params.size() != 1)
1940         {
1941             messages::internalError(asyncResp->res);
1942             return;
1943         }
1944         const std::string& entryID = params[0];
1945         // Convert the unique ID back to a timestamp to find the entry
1946         uint64_t ts = 0;
1947         uint64_t index = 0;
1948         if (!getTimestampFromID(asyncResp->res, entryID, ts, index))
1949         {
1950             return;
1951         }
1952 
1953         sd_journal* journalTmp = nullptr;
1954         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
1955         if (ret < 0)
1956         {
1957             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
1958             messages::internalError(asyncResp->res);
1959             return;
1960         }
1961         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
1962             journalTmp, sd_journal_close);
1963         journalTmp = nullptr;
1964         // Go to the timestamp in the log and move to the entry at the index
1965         // tracking the unique ID
1966         std::string idStr;
1967         bool firstEntry = true;
1968         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
1969         if (ret < 0)
1970         {
1971             BMCWEB_LOG_ERROR << "failed to seek to an entry in journal"
1972                              << strerror(-ret);
1973             messages::internalError(asyncResp->res);
1974             return;
1975         }
1976         for (uint64_t i = 0; i <= index; i++)
1977         {
1978             sd_journal_next(journal.get());
1979             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
1980             {
1981                 messages::internalError(asyncResp->res);
1982                 return;
1983             }
1984             if (firstEntry)
1985             {
1986                 firstEntry = false;
1987             }
1988         }
1989         // Confirm that the entry ID matches what was requested
1990         if (idStr != entryID)
1991         {
1992             messages::resourceMissingAtURI(asyncResp->res, entryID);
1993             return;
1994         }
1995 
1996         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
1997                                        asyncResp->res.jsonValue) != 0)
1998         {
1999             messages::internalError(asyncResp->res);
2000             return;
2001         }
2002     }
2003 };
2004 
2005 class BMCDumpService : public Node
2006 {
2007   public:
2008     BMCDumpService(App& app) :
2009         Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/")
2010     {
2011         entityPrivileges = {
2012             {boost::beast::http::verb::get, {{"Login"}}},
2013             {boost::beast::http::verb::head, {{"Login"}}},
2014             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2015             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2016             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2017             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2018     }
2019 
2020   private:
2021     void doGet(crow::Response& res, const crow::Request&,
2022                const std::vector<std::string>&) override
2023     {
2024         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2025 
2026         asyncResp->res.jsonValue["@odata.id"] =
2027             "/redfish/v1/Managers/bmc/LogServices/Dump";
2028         asyncResp->res.jsonValue["@odata.type"] =
2029             "#LogService.v1_2_0.LogService";
2030         asyncResp->res.jsonValue["Name"] = "Dump LogService";
2031         asyncResp->res.jsonValue["Description"] = "BMC Dump LogService";
2032         asyncResp->res.jsonValue["Id"] = "Dump";
2033         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2034         asyncResp->res.jsonValue["Entries"] = {
2035             {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}};
2036         asyncResp->res.jsonValue["Actions"] = {
2037             {"#LogService.ClearLog",
2038              {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/"
2039                          "Actions/LogService.ClearLog"}}},
2040             {"#LogService.CollectDiagnosticData",
2041              {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/"
2042                          "Actions/LogService.CollectDiagnosticData"}}}};
2043     }
2044 };
2045 
2046 class BMCDumpEntryCollection : public Node
2047 {
2048   public:
2049     BMCDumpEntryCollection(App& app) :
2050         Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/")
2051     {
2052         entityPrivileges = {
2053             {boost::beast::http::verb::get, {{"Login"}}},
2054             {boost::beast::http::verb::head, {{"Login"}}},
2055             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2056             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2057             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2058             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2059     }
2060 
2061   private:
2062     /**
2063      * Functions triggers appropriate requests on DBus
2064      */
2065     void doGet(crow::Response& res, const crow::Request&,
2066                const std::vector<std::string>&) override
2067     {
2068         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2069 
2070         asyncResp->res.jsonValue["@odata.type"] =
2071             "#LogEntryCollection.LogEntryCollection";
2072         asyncResp->res.jsonValue["@odata.id"] =
2073             "/redfish/v1/Managers/bmc/LogServices/Dump/Entries";
2074         asyncResp->res.jsonValue["Name"] = "BMC Dump Entries";
2075         asyncResp->res.jsonValue["Description"] =
2076             "Collection of BMC Dump Entries";
2077 
2078         getDumpEntryCollection(asyncResp, "BMC");
2079     }
2080 };
2081 
2082 class BMCDumpEntry : public Node
2083 {
2084   public:
2085     BMCDumpEntry(App& app) :
2086         Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/",
2087              std::string())
2088     {
2089         entityPrivileges = {
2090             {boost::beast::http::verb::get, {{"Login"}}},
2091             {boost::beast::http::verb::head, {{"Login"}}},
2092             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2093             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2094             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2095             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2096     }
2097 
2098   private:
2099     void doGet(crow::Response& res, const crow::Request&,
2100                const std::vector<std::string>& params) override
2101     {
2102         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2103         if (params.size() != 1)
2104         {
2105             messages::internalError(asyncResp->res);
2106             return;
2107         }
2108         getDumpEntryById(asyncResp, params[0], "BMC");
2109     }
2110 
2111     void doDelete(crow::Response& res, const crow::Request&,
2112                   const std::vector<std::string>& params) override
2113     {
2114         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2115         if (params.size() != 1)
2116         {
2117             messages::internalError(asyncResp->res);
2118             return;
2119         }
2120         deleteDumpEntry(asyncResp->res, params[0]);
2121     }
2122 };
2123 
2124 class BMCDumpCreate : public Node
2125 {
2126   public:
2127     BMCDumpCreate(App& app) :
2128         Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/"
2129                   "Actions/"
2130                   "LogService.CollectDiagnosticData/")
2131     {
2132         entityPrivileges = {
2133             {boost::beast::http::verb::get, {{"Login"}}},
2134             {boost::beast::http::verb::head, {{"Login"}}},
2135             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2136             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2137             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2138             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2139     }
2140 
2141   private:
2142     void doPost(crow::Response& res, const crow::Request& req,
2143                 const std::vector<std::string>&) override
2144     {
2145         createDump(res, req, "BMC");
2146     }
2147 };
2148 
2149 class BMCDumpClear : public Node
2150 {
2151   public:
2152     BMCDumpClear(App& app) :
2153         Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/"
2154                   "Actions/"
2155                   "LogService.ClearLog/")
2156     {
2157         entityPrivileges = {
2158             {boost::beast::http::verb::get, {{"Login"}}},
2159             {boost::beast::http::verb::head, {{"Login"}}},
2160             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2161             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2162             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2163             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2164     }
2165 
2166   private:
2167     void doPost(crow::Response& res, const crow::Request&,
2168                 const std::vector<std::string>&) override
2169     {
2170         clearDump(res, "xyz.openbmc_project.Dump.Entry.BMC");
2171     }
2172 };
2173 
2174 class SystemDumpService : public Node
2175 {
2176   public:
2177     SystemDumpService(App& app) :
2178         Node(app, "/redfish/v1/Systems/system/LogServices/Dump/")
2179     {
2180         entityPrivileges = {
2181             {boost::beast::http::verb::get, {{"Login"}}},
2182             {boost::beast::http::verb::head, {{"Login"}}},
2183             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2184             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2185             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2186             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2187     }
2188 
2189   private:
2190     void doGet(crow::Response& res, const crow::Request&,
2191                const std::vector<std::string>&) override
2192     {
2193         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2194 
2195         asyncResp->res.jsonValue["@odata.id"] =
2196             "/redfish/v1/Systems/system/LogServices/Dump";
2197         asyncResp->res.jsonValue["@odata.type"] =
2198             "#LogService.v1_2_0.LogService";
2199         asyncResp->res.jsonValue["Name"] = "Dump LogService";
2200         asyncResp->res.jsonValue["Description"] = "System Dump LogService";
2201         asyncResp->res.jsonValue["Id"] = "Dump";
2202         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2203         asyncResp->res.jsonValue["Entries"] = {
2204             {"@odata.id",
2205              "/redfish/v1/Systems/system/LogServices/Dump/Entries"}};
2206         asyncResp->res.jsonValue["Actions"] = {
2207             {"#LogService.ClearLog",
2208              {{"target", "/redfish/v1/Systems/system/LogServices/Dump/Actions/"
2209                          "LogService.ClearLog"}}},
2210             {"#LogService.CollectDiagnosticData",
2211              {{"target", "/redfish/v1/Systems/system/LogServices/Dump/Actions/"
2212                          "LogService.CollectDiagnosticData"}}}};
2213     }
2214 };
2215 
2216 class SystemDumpEntryCollection : public Node
2217 {
2218   public:
2219     SystemDumpEntryCollection(App& app) :
2220         Node(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/")
2221     {
2222         entityPrivileges = {
2223             {boost::beast::http::verb::get, {{"Login"}}},
2224             {boost::beast::http::verb::head, {{"Login"}}},
2225             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2226             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2227             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2228             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2229     }
2230 
2231   private:
2232     /**
2233      * Functions triggers appropriate requests on DBus
2234      */
2235     void doGet(crow::Response& res, const crow::Request&,
2236                const std::vector<std::string>&) override
2237     {
2238         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2239 
2240         asyncResp->res.jsonValue["@odata.type"] =
2241             "#LogEntryCollection.LogEntryCollection";
2242         asyncResp->res.jsonValue["@odata.id"] =
2243             "/redfish/v1/Systems/system/LogServices/Dump/Entries";
2244         asyncResp->res.jsonValue["Name"] = "System Dump Entries";
2245         asyncResp->res.jsonValue["Description"] =
2246             "Collection of System Dump Entries";
2247 
2248         getDumpEntryCollection(asyncResp, "System");
2249     }
2250 };
2251 
2252 class SystemDumpEntry : public Node
2253 {
2254   public:
2255     SystemDumpEntry(App& app) :
2256         Node(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/",
2257              std::string())
2258     {
2259         entityPrivileges = {
2260             {boost::beast::http::verb::get, {{"Login"}}},
2261             {boost::beast::http::verb::head, {{"Login"}}},
2262             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2263             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2264             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2265             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2266     }
2267 
2268   private:
2269     void doGet(crow::Response& res, const crow::Request&,
2270                const std::vector<std::string>& params) override
2271     {
2272         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2273         if (params.size() != 1)
2274         {
2275             messages::internalError(asyncResp->res);
2276             return;
2277         }
2278         getDumpEntryById(asyncResp, params[0], "System");
2279     }
2280 
2281     void doDelete(crow::Response& res, const crow::Request&,
2282                   const std::vector<std::string>& params) override
2283     {
2284         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2285         if (params.size() != 1)
2286         {
2287             messages::internalError(asyncResp->res);
2288             return;
2289         }
2290         deleteDumpEntry(asyncResp->res, params[0]);
2291     }
2292 };
2293 
2294 class SystemDumpCreate : public Node
2295 {
2296   public:
2297     SystemDumpCreate(App& app) :
2298         Node(app, "/redfish/v1/Systems/system/LogServices/Dump/"
2299                   "Actions/"
2300                   "LogService.CollectDiagnosticData/")
2301     {
2302         entityPrivileges = {
2303             {boost::beast::http::verb::get, {{"Login"}}},
2304             {boost::beast::http::verb::head, {{"Login"}}},
2305             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2306             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2307             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2308             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2309     }
2310 
2311   private:
2312     void doPost(crow::Response& res, const crow::Request& req,
2313                 const std::vector<std::string>&) override
2314     {
2315         createDump(res, req, "System");
2316     }
2317 };
2318 
2319 class SystemDumpClear : public Node
2320 {
2321   public:
2322     SystemDumpClear(App& app) :
2323         Node(app, "/redfish/v1/Systems/system/LogServices/Dump/"
2324                   "Actions/"
2325                   "LogService.ClearLog/")
2326     {
2327         entityPrivileges = {
2328             {boost::beast::http::verb::get, {{"Login"}}},
2329             {boost::beast::http::verb::head, {{"Login"}}},
2330             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2331             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2332             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2333             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2334     }
2335 
2336   private:
2337     void doPost(crow::Response& res, const crow::Request&,
2338                 const std::vector<std::string>&) override
2339     {
2340         clearDump(res, "xyz.openbmc_project.Dump.Entry.System");
2341     }
2342 };
2343 
2344 class CrashdumpService : public Node
2345 {
2346   public:
2347     CrashdumpService(App& app) :
2348         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
2349     {
2350         // Note: Deviated from redfish privilege registry for GET & HEAD
2351         // method for security reasons.
2352         entityPrivileges = {
2353             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2354             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2355             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2356             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2357             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2358             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2359     }
2360 
2361   private:
2362     /**
2363      * Functions triggers appropriate requests on DBus
2364      */
2365     void doGet(crow::Response& res, const crow::Request&,
2366                const std::vector<std::string>&) override
2367     {
2368         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2369         // Copy over the static data to include the entries added by SubRoute
2370         asyncResp->res.jsonValue["@odata.id"] =
2371             "/redfish/v1/Systems/system/LogServices/Crashdump";
2372         asyncResp->res.jsonValue["@odata.type"] =
2373             "#LogService.v1_1_0.LogService";
2374         asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
2375         asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
2376         asyncResp->res.jsonValue["Id"] = "Oem Crashdump";
2377         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2378         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
2379         asyncResp->res.jsonValue["Entries"] = {
2380             {"@odata.id",
2381              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}};
2382         asyncResp->res.jsonValue["Actions"] = {
2383             {"#LogService.ClearLog",
2384              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
2385                          "Actions/LogService.ClearLog"}}},
2386             {"Oem",
2387              {{"#Crashdump.OnDemand",
2388                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
2389                            "Actions/Oem/Crashdump.OnDemand"}}},
2390               {"#Crashdump.Telemetry",
2391                {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
2392                            "Actions/Oem/Crashdump.Telemetry"}}}}}};
2393 
2394 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI
2395         asyncResp->res.jsonValue["Actions"]["Oem"].push_back(
2396             {"#Crashdump.SendRawPeci",
2397              {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/"
2398                          "Actions/Oem/Crashdump.SendRawPeci"}}});
2399 #endif
2400     }
2401 };
2402 
2403 class CrashdumpClear : public Node
2404 {
2405   public:
2406     CrashdumpClear(App& app) :
2407         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/"
2408                   "LogService.ClearLog/")
2409     {
2410         // Note: Deviated from redfish privilege registry for GET & HEAD
2411         // method for security reasons.
2412         entityPrivileges = {
2413             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2414             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2415             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2416             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2417             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2418             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2419     }
2420 
2421   private:
2422     void doPost(crow::Response& res, const crow::Request&,
2423                 const std::vector<std::string>&) override
2424     {
2425         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2426 
2427         crow::connections::systemBus->async_method_call(
2428             [asyncResp](const boost::system::error_code ec,
2429                         const std::string&) {
2430                 if (ec)
2431                 {
2432                     messages::internalError(asyncResp->res);
2433                     return;
2434                 }
2435                 messages::success(asyncResp->res);
2436             },
2437             crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
2438     }
2439 };
2440 
2441 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp,
2442                               const std::string& logID,
2443                               nlohmann::json& logEntryJson)
2444 {
2445     auto getStoredLogCallback =
2446         [asyncResp, logID, &logEntryJson](
2447             const boost::system::error_code ec,
2448             const std::vector<std::pair<std::string, VariantType>>& params) {
2449             if (ec)
2450             {
2451                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
2452                 if (ec.value() ==
2453                     boost::system::linux_error::bad_request_descriptor)
2454                 {
2455                     messages::resourceNotFound(asyncResp->res, "LogEntry",
2456                                                logID);
2457                 }
2458                 else
2459                 {
2460                     messages::internalError(asyncResp->res);
2461                 }
2462                 return;
2463             }
2464 
2465             std::string timestamp{};
2466             std::string filename{};
2467             std::string logfile{};
2468             ParseCrashdumpParameters(params, filename, timestamp, logfile);
2469 
2470             if (filename.empty() || timestamp.empty())
2471             {
2472                 messages::resourceMissingAtURI(asyncResp->res, logID);
2473                 return;
2474             }
2475 
2476             std::string crashdumpURI =
2477                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
2478                 logID + "/" + filename;
2479             logEntryJson = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
2480                             {"@odata.id", "/redfish/v1/Systems/system/"
2481                                           "LogServices/Crashdump/Entries/" +
2482                                               logID},
2483                             {"Name", "CPU Crashdump"},
2484                             {"Id", logID},
2485                             {"EntryType", "Oem"},
2486                             {"OemRecordFormat", "Crashdump URI"},
2487                             {"Message", std::move(crashdumpURI)},
2488                             {"Created", std::move(timestamp)}};
2489         };
2490     crow::connections::systemBus->async_method_call(
2491         std::move(getStoredLogCallback), crashdumpObject,
2492         crashdumpPath + std::string("/") + logID,
2493         "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
2494 }
2495 
2496 class CrashdumpEntryCollection : public Node
2497 {
2498   public:
2499     CrashdumpEntryCollection(App& app) :
2500         Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
2501     {
2502         // Note: Deviated from redfish privilege registry for GET & HEAD
2503         // method for security reasons.
2504         entityPrivileges = {
2505             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2506             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2507             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2508             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2509             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2510             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2511     }
2512 
2513   private:
2514     /**
2515      * Functions triggers appropriate requests on DBus
2516      */
2517     void doGet(crow::Response& res, const crow::Request&,
2518                const std::vector<std::string>&) override
2519     {
2520         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2521         // Collections don't include the static data added by SubRoute because
2522         // it has a duplicate entry for members
2523         auto getLogEntriesCallback = [asyncResp](
2524                                          const boost::system::error_code ec,
2525                                          const std::vector<std::string>& resp) {
2526             if (ec)
2527             {
2528                 if (ec.value() !=
2529                     boost::system::errc::no_such_file_or_directory)
2530                 {
2531                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
2532                                      << ec.message();
2533                     messages::internalError(asyncResp->res);
2534                     return;
2535                 }
2536             }
2537             asyncResp->res.jsonValue["@odata.type"] =
2538                 "#LogEntryCollection.LogEntryCollection";
2539             asyncResp->res.jsonValue["@odata.id"] =
2540                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
2541             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
2542             asyncResp->res.jsonValue["Description"] =
2543                 "Collection of Crashdump Entries";
2544             nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
2545             logEntryArray = nlohmann::json::array();
2546             std::vector<std::string> logIDs;
2547             // Get the list of log entries and build up an empty array big
2548             // enough to hold them
2549             for (const std::string& objpath : resp)
2550             {
2551                 // Get the log ID
2552                 std::size_t lastPos = objpath.rfind("/");
2553                 if (lastPos == std::string::npos)
2554                 {
2555                     continue;
2556                 }
2557                 logIDs.emplace_back(objpath.substr(lastPos + 1));
2558 
2559                 // Add a space for the log entry to the array
2560                 logEntryArray.push_back({});
2561             }
2562             // Now go through and set up async calls to fill in the entries
2563             size_t index = 0;
2564             for (const std::string& logID : logIDs)
2565             {
2566                 // Add the log entry to the array
2567                 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]);
2568             }
2569             asyncResp->res.jsonValue["Members@odata.count"] =
2570                 logEntryArray.size();
2571         };
2572         crow::connections::systemBus->async_method_call(
2573             std::move(getLogEntriesCallback),
2574             "xyz.openbmc_project.ObjectMapper",
2575             "/xyz/openbmc_project/object_mapper",
2576             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
2577             std::array<const char*, 1>{crashdumpInterface});
2578     }
2579 };
2580 
2581 class CrashdumpEntry : public Node
2582 {
2583   public:
2584     CrashdumpEntry(App& app) :
2585         Node(app,
2586              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/",
2587              std::string())
2588     {
2589         // Note: Deviated from redfish privilege registry for GET & HEAD
2590         // method for security reasons.
2591         entityPrivileges = {
2592             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2593             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2594             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2595             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2596             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2597             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2598     }
2599 
2600   private:
2601     void doGet(crow::Response& res, const crow::Request&,
2602                const std::vector<std::string>& params) override
2603     {
2604         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2605         if (params.size() != 1)
2606         {
2607             messages::internalError(asyncResp->res);
2608             return;
2609         }
2610         const std::string& logID = params[0];
2611         logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
2612     }
2613 };
2614 
2615 class CrashdumpFile : public Node
2616 {
2617   public:
2618     CrashdumpFile(App& app) :
2619         Node(app,
2620              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/"
2621              "<str>/",
2622              std::string(), std::string())
2623     {
2624         // Note: Deviated from redfish privilege registry for GET & HEAD
2625         // method for security reasons.
2626         entityPrivileges = {
2627             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2628             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2629             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2630             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2631             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2632             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2633     }
2634 
2635   private:
2636     void doGet(crow::Response& res, const crow::Request&,
2637                const std::vector<std::string>& params) override
2638     {
2639         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2640         if (params.size() != 2)
2641         {
2642             messages::internalError(asyncResp->res);
2643             return;
2644         }
2645         const std::string& logID = params[0];
2646         const std::string& fileName = params[1];
2647 
2648         auto getStoredLogCallback =
2649             [asyncResp, logID, fileName](
2650                 const boost::system::error_code ec,
2651                 const std::vector<std::pair<std::string, VariantType>>& resp) {
2652                 if (ec)
2653                 {
2654                     BMCWEB_LOG_DEBUG << "failed to get log ec: "
2655                                      << ec.message();
2656                     messages::internalError(asyncResp->res);
2657                     return;
2658                 }
2659 
2660                 std::string dbusFilename{};
2661                 std::string dbusTimestamp{};
2662                 std::string dbusFilepath{};
2663 
2664                 ParseCrashdumpParameters(resp, dbusFilename, dbusTimestamp,
2665                                          dbusFilepath);
2666 
2667                 if (dbusFilename.empty() || dbusTimestamp.empty() ||
2668                     dbusFilepath.empty())
2669                 {
2670                     messages::resourceMissingAtURI(asyncResp->res, fileName);
2671                     return;
2672                 }
2673 
2674                 // Verify the file name parameter is correct
2675                 if (fileName != dbusFilename)
2676                 {
2677                     messages::resourceMissingAtURI(asyncResp->res, fileName);
2678                     return;
2679                 }
2680 
2681                 if (!std::filesystem::exists(dbusFilepath))
2682                 {
2683                     messages::resourceMissingAtURI(asyncResp->res, fileName);
2684                     return;
2685                 }
2686                 std::ifstream ifs(dbusFilepath, std::ios::in |
2687                                                     std::ios::binary |
2688                                                     std::ios::ate);
2689                 std::ifstream::pos_type fileSize = ifs.tellg();
2690                 if (fileSize < 0)
2691                 {
2692                     messages::generalError(asyncResp->res);
2693                     return;
2694                 }
2695                 ifs.seekg(0, std::ios::beg);
2696 
2697                 auto crashData = std::make_unique<char[]>(
2698                     static_cast<unsigned int>(fileSize));
2699 
2700                 ifs.read(crashData.get(), static_cast<int>(fileSize));
2701 
2702                 // The cast to std::string is intentional in order to use the
2703                 // assign() that applies move mechanics
2704                 asyncResp->res.body().assign(
2705                     static_cast<std::string>(crashData.get()));
2706 
2707                 // Configure this to be a file download when accessed from
2708                 // a browser
2709                 asyncResp->res.addHeader("Content-Disposition", "attachment");
2710             };
2711         crow::connections::systemBus->async_method_call(
2712             std::move(getStoredLogCallback), crashdumpObject,
2713             crashdumpPath + std::string("/") + logID,
2714             "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
2715     }
2716 };
2717 
2718 class OnDemandCrashdump : public Node
2719 {
2720   public:
2721     OnDemandCrashdump(App& app) :
2722         Node(app,
2723              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
2724              "Crashdump.OnDemand/")
2725     {
2726         // Note: Deviated from redfish privilege registry for GET & HEAD
2727         // method for security reasons.
2728         entityPrivileges = {
2729             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2730             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2731             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2732             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2733             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2734             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2735     }
2736 
2737   private:
2738     void doPost(crow::Response& res, const crow::Request& req,
2739                 const std::vector<std::string>&) override
2740     {
2741         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2742 
2743         auto generateonDemandLogCallback = [asyncResp,
2744                                             req](const boost::system::error_code
2745                                                      ec,
2746                                                  const std::string&) {
2747             if (ec)
2748             {
2749                 if (ec.value() == boost::system::errc::operation_not_supported)
2750                 {
2751                     messages::resourceInStandby(asyncResp->res);
2752                 }
2753                 else if (ec.value() ==
2754                          boost::system::errc::device_or_resource_busy)
2755                 {
2756                     messages::serviceTemporarilyUnavailable(asyncResp->res,
2757                                                             "60");
2758                 }
2759                 else
2760                 {
2761                     messages::internalError(asyncResp->res);
2762                 }
2763                 return;
2764             }
2765             std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
2766                 [](boost::system::error_code err, sdbusplus::message::message&,
2767                    const std::shared_ptr<task::TaskData>& taskData) {
2768                     if (!err)
2769                     {
2770                         taskData->messages.emplace_back(
2771                             messages::taskCompletedOK(
2772                                 std::to_string(taskData->index)));
2773                         taskData->state = "Completed";
2774                     }
2775                     return task::completed;
2776                 },
2777                 "type='signal',interface='org.freedesktop.DBus.Properties',"
2778                 "member='PropertiesChanged',arg0namespace='com.intel."
2779                 "crashdump'");
2780             task->startTimer(std::chrono::minutes(5));
2781             task->populateResp(asyncResp->res);
2782             task->payload.emplace(req);
2783         };
2784         crow::connections::systemBus->async_method_call(
2785             std::move(generateonDemandLogCallback), crashdumpObject,
2786             crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog");
2787     }
2788 };
2789 
2790 class TelemetryCrashdump : public Node
2791 {
2792   public:
2793     TelemetryCrashdump(App& app) :
2794         Node(app,
2795              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
2796              "Crashdump.Telemetry/")
2797     {
2798         // Note: Deviated from redfish privilege registry for GET & HEAD
2799         // method for security reasons.
2800         entityPrivileges = {
2801             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2802             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2803             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2804             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2805             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2806             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2807     }
2808 
2809   private:
2810     void doPost(crow::Response& res, const crow::Request& req,
2811                 const std::vector<std::string>&) override
2812     {
2813         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2814 
2815         auto generateTelemetryLogCallback = [asyncResp, req](
2816                                                 const boost::system::error_code
2817                                                     ec,
2818                                                 const std::string&) {
2819             if (ec)
2820             {
2821                 if (ec.value() == boost::system::errc::operation_not_supported)
2822                 {
2823                     messages::resourceInStandby(asyncResp->res);
2824                 }
2825                 else if (ec.value() ==
2826                          boost::system::errc::device_or_resource_busy)
2827                 {
2828                     messages::serviceTemporarilyUnavailable(asyncResp->res,
2829                                                             "60");
2830                 }
2831                 else
2832                 {
2833                     messages::internalError(asyncResp->res);
2834                 }
2835                 return;
2836             }
2837             std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
2838                 [](boost::system::error_code err, sdbusplus::message::message&,
2839                    const std::shared_ptr<task::TaskData>& taskData) {
2840                     if (!err)
2841                     {
2842                         taskData->messages.emplace_back(
2843                             messages::taskCompletedOK(
2844                                 std::to_string(taskData->index)));
2845                         taskData->state = "Completed";
2846                     }
2847                     return task::completed;
2848                 },
2849                 "type='signal',interface='org.freedesktop.DBus.Properties',"
2850                 "member='PropertiesChanged',arg0namespace='com.intel."
2851                 "crashdump'");
2852             task->startTimer(std::chrono::minutes(5));
2853             task->populateResp(asyncResp->res);
2854             task->payload.emplace(req);
2855         };
2856         crow::connections::systemBus->async_method_call(
2857             std::move(generateTelemetryLogCallback), crashdumpObject,
2858             crashdumpPath, crashdumpTelemetryInterface, "GenerateTelemetryLog");
2859     }
2860 };
2861 
2862 class SendRawPECI : public Node
2863 {
2864   public:
2865     SendRawPECI(App& app) :
2866         Node(app,
2867              "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/"
2868              "Crashdump.SendRawPeci/")
2869     {
2870         // Note: Deviated from redfish privilege registry for GET & HEAD
2871         // method for security reasons.
2872         entityPrivileges = {
2873             {boost::beast::http::verb::get, {{"ConfigureComponents"}}},
2874             {boost::beast::http::verb::head, {{"ConfigureComponents"}}},
2875             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2876             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2877             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2878             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2879     }
2880 
2881   private:
2882     void doPost(crow::Response& res, const crow::Request& req,
2883                 const std::vector<std::string>&) override
2884     {
2885         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2886         std::vector<std::vector<uint8_t>> peciCommands;
2887 
2888         if (!json_util::readJson(req, res, "PECICommands", peciCommands))
2889         {
2890             return;
2891         }
2892         uint32_t idx = 0;
2893         for (auto const& cmd : peciCommands)
2894         {
2895             if (cmd.size() < 3)
2896             {
2897                 std::string s("[");
2898                 for (auto const& val : cmd)
2899                 {
2900                     if (val != *cmd.begin())
2901                     {
2902                         s += ",";
2903                     }
2904                     s += std::to_string(val);
2905                 }
2906                 s += "]";
2907                 messages::actionParameterValueFormatError(
2908                     res, s, "PECICommands[" + std::to_string(idx) + "]",
2909                     "SendRawPeci");
2910                 return;
2911             }
2912             idx++;
2913         }
2914         // Callback to return the Raw PECI response
2915         auto sendRawPECICallback =
2916             [asyncResp](const boost::system::error_code ec,
2917                         const std::vector<std::vector<uint8_t>>& resp) {
2918                 if (ec)
2919                 {
2920                     BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: "
2921                                      << ec.message();
2922                     messages::internalError(asyncResp->res);
2923                     return;
2924                 }
2925                 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"},
2926                                             {"PECIResponse", resp}};
2927             };
2928         // Call the SendRawPECI command with the provided data
2929         crow::connections::systemBus->async_method_call(
2930             std::move(sendRawPECICallback), crashdumpObject, crashdumpPath,
2931             crashdumpRawPECIInterface, "SendRawPeci", peciCommands);
2932     }
2933 };
2934 
2935 /**
2936  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
2937  */
2938 class DBusLogServiceActionsClear : public Node
2939 {
2940   public:
2941     DBusLogServiceActionsClear(App& app) :
2942         Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/"
2943                   "LogService.ClearLog/")
2944     {
2945         entityPrivileges = {
2946             {boost::beast::http::verb::get, {{"Login"}}},
2947             {boost::beast::http::verb::head, {{"Login"}}},
2948             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2949             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2950             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2951             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2952     }
2953 
2954   private:
2955     /**
2956      * Function handles POST method request.
2957      * The Clear Log actions does not require any parameter.The action deletes
2958      * all entries found in the Entries collection for this Log Service.
2959      */
2960     void doPost(crow::Response& res, const crow::Request&,
2961                 const std::vector<std::string>&) override
2962     {
2963         BMCWEB_LOG_DEBUG << "Do delete all entries.";
2964 
2965         auto asyncResp = std::make_shared<AsyncResp>(res);
2966         // Process response from Logging service.
2967         auto resp_handler = [asyncResp](const boost::system::error_code ec) {
2968             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
2969             if (ec)
2970             {
2971                 // TODO Handle for specific error code
2972                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
2973                 asyncResp->res.result(
2974                     boost::beast::http::status::internal_server_error);
2975                 return;
2976             }
2977 
2978             asyncResp->res.result(boost::beast::http::status::no_content);
2979         };
2980 
2981         // Make call to Logging service to request Clear Log
2982         crow::connections::systemBus->async_method_call(
2983             resp_handler, "xyz.openbmc_project.Logging",
2984             "/xyz/openbmc_project/logging",
2985             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
2986     }
2987 };
2988 
2989 /****************************************************
2990  * Redfish PostCode interfaces
2991  * using DBUS interface: getPostCodesTS
2992  ******************************************************/
2993 class PostCodesLogService : public Node
2994 {
2995   public:
2996     PostCodesLogService(App& app) :
2997         Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/")
2998     {
2999         entityPrivileges = {
3000             {boost::beast::http::verb::get, {{"Login"}}},
3001             {boost::beast::http::verb::head, {{"Login"}}},
3002             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
3003             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
3004             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
3005             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3006     }
3007 
3008   private:
3009     void doGet(crow::Response& res, const crow::Request&,
3010                const std::vector<std::string>&) override
3011     {
3012         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
3013 
3014         asyncResp->res.jsonValue = {
3015             {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"},
3016             {"@odata.type", "#LogService.v1_1_0.LogService"},
3017             {"@odata.context", "/redfish/v1/$metadata#LogService.LogService"},
3018             {"Name", "POST Code Log Service"},
3019             {"Description", "POST Code Log Service"},
3020             {"Id", "BIOS POST Code Log"},
3021             {"OverWritePolicy", "WrapsWhenFull"},
3022             {"Entries",
3023              {{"@odata.id",
3024                "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}};
3025         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
3026             {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/"
3027                        "Actions/LogService.ClearLog"}};
3028     }
3029 };
3030 
3031 class PostCodesClear : public Node
3032 {
3033   public:
3034     PostCodesClear(App& app) :
3035         Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/"
3036                   "LogService.ClearLog/")
3037     {
3038         entityPrivileges = {
3039             {boost::beast::http::verb::get, {{"Login"}}},
3040             {boost::beast::http::verb::head, {{"Login"}}},
3041             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
3042             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
3043             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
3044             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
3045     }
3046 
3047   private:
3048     void doPost(crow::Response& res, const crow::Request&,
3049                 const std::vector<std::string>&) override
3050     {
3051         BMCWEB_LOG_DEBUG << "Do delete all postcodes entries.";
3052 
3053         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
3054         // Make call to post-code service to request clear all
3055         crow::connections::systemBus->async_method_call(
3056             [asyncResp](const boost::system::error_code ec) {
3057                 if (ec)
3058                 {
3059                     // TODO Handle for specific error code
3060                     BMCWEB_LOG_ERROR
3061                         << "doClearPostCodes resp_handler got error " << ec;
3062                     asyncResp->res.result(
3063                         boost::beast::http::status::internal_server_error);
3064                     messages::internalError(asyncResp->res);
3065                     return;
3066                 }
3067             },
3068             "xyz.openbmc_project.State.Boot.PostCode",
3069             "/xyz/openbmc_project/State/Boot/PostCode",
3070             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3071     }
3072 };
3073 
3074 static void fillPostCodeEntry(
3075     std::shared_ptr<AsyncResp> aResp,
3076     const boost::container::flat_map<uint64_t, uint64_t>& postcode,
3077     const uint16_t bootIndex, const uint64_t codeIndex = 0,
3078     const uint64_t skip = 0, const uint64_t top = 0)
3079 {
3080     // Get the Message from the MessageRegistry
3081     const message_registries::Message* message =
3082         message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode");
3083 
3084     uint64_t currentCodeIndex = 0;
3085     nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"];
3086 
3087     uint64_t firstCodeTimeUs = 0;
3088     for (const std::pair<uint64_t, uint64_t>& code : postcode)
3089     {
3090         currentCodeIndex++;
3091         std::string postcodeEntryID =
3092             "B" + std::to_string(bootIndex) + "-" +
3093             std::to_string(currentCodeIndex); // 1 based index in EntryID string
3094 
3095         uint64_t usecSinceEpoch = code.first;
3096         uint64_t usTimeOffset = 0;
3097 
3098         if (1 == currentCodeIndex)
3099         { // already incremented
3100             firstCodeTimeUs = code.first;
3101         }
3102         else
3103         {
3104             usTimeOffset = code.first - firstCodeTimeUs;
3105         }
3106 
3107         // skip if no specific codeIndex is specified and currentCodeIndex does
3108         // not fall between top and skip
3109         if ((codeIndex == 0) &&
3110             (currentCodeIndex <= skip || currentCodeIndex > top))
3111         {
3112             continue;
3113         }
3114 
3115         // skip if a specific codeIndex is specified and does not match the
3116         // currentIndex
3117         if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
3118         {
3119             // This is done for simplicity. 1st entry is needed to calculate
3120             // time offset. To improve efficiency, one can get to the entry
3121             // directly (possibly with flatmap's nth method)
3122             continue;
3123         }
3124 
3125         // currentCodeIndex is within top and skip or equal to specified code
3126         // index
3127 
3128         // Get the Created time from the timestamp
3129         std::string entryTimeStr;
3130         entryTimeStr = crow::utility::getDateTime(
3131             static_cast<std::time_t>(usecSinceEpoch / 1000 / 1000));
3132 
3133         // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
3134         std::ostringstream hexCode;
3135         hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
3136                 << code.second;
3137         std::ostringstream timeOffsetStr;
3138         // Set Fixed -Point Notation
3139         timeOffsetStr << std::fixed;
3140         // Set precision to 4 digits
3141         timeOffsetStr << std::setprecision(4);
3142         // Add double to stream
3143         timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
3144         std::vector<std::string> messageArgs = {
3145             std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()};
3146 
3147         // Get MessageArgs template from message registry
3148         std::string msg;
3149         if (message != nullptr)
3150         {
3151             msg = message->message;
3152 
3153             // fill in this post code value
3154             int i = 0;
3155             for (const std::string& messageArg : messageArgs)
3156             {
3157                 std::string argStr = "%" + std::to_string(++i);
3158                 size_t argPos = msg.find(argStr);
3159                 if (argPos != std::string::npos)
3160                 {
3161                     msg.replace(argPos, argStr.length(), messageArg);
3162                 }
3163             }
3164         }
3165 
3166         // Get Severity template from message registry
3167         std::string severity;
3168         if (message != nullptr)
3169         {
3170             severity = message->severity;
3171         }
3172 
3173         // add to AsyncResp
3174         logEntryArray.push_back({});
3175         nlohmann::json& bmcLogEntry = logEntryArray.back();
3176         bmcLogEntry = {
3177             {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
3178             {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"},
3179             {"@odata.id", "/redfish/v1/Systems/system/LogServices/"
3180                           "PostCodes/Entries/" +
3181                               postcodeEntryID},
3182             {"Name", "POST Code Log Entry"},
3183             {"Id", postcodeEntryID},
3184             {"Message", std::move(msg)},
3185             {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"},
3186             {"MessageArgs", std::move(messageArgs)},
3187             {"EntryType", "Event"},
3188             {"Severity", std::move(severity)},
3189             {"Created", entryTimeStr}};
3190     }
3191 }
3192 
3193 static void getPostCodeForEntry(std::shared_ptr<AsyncResp> aResp,
3194                                 const uint16_t bootIndex,
3195                                 const uint64_t codeIndex)
3196 {
3197     crow::connections::systemBus->async_method_call(
3198         [aResp, bootIndex, codeIndex](
3199             const boost::system::error_code ec,
3200             const boost::container::flat_map<uint64_t, uint64_t>& postcode) {
3201             if (ec)
3202             {
3203                 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
3204                 messages::internalError(aResp->res);
3205                 return;
3206             }
3207 
3208             // skip the empty postcode boots
3209             if (postcode.empty())
3210             {
3211                 return;
3212             }
3213 
3214             fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex);
3215 
3216             aResp->res.jsonValue["Members@odata.count"] =
3217                 aResp->res.jsonValue["Members"].size();
3218         },
3219         "xyz.openbmc_project.State.Boot.PostCode",
3220         "/xyz/openbmc_project/State/Boot/PostCode",
3221         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
3222         bootIndex);
3223 }
3224 
3225 static void getPostCodeForBoot(std::shared_ptr<AsyncResp> aResp,
3226                                const uint16_t bootIndex,
3227                                const uint16_t bootCount,
3228                                const uint64_t entryCount, const uint64_t skip,
3229                                const uint64_t top)
3230 {
3231     crow::connections::systemBus->async_method_call(
3232         [aResp, bootIndex, bootCount, entryCount, skip,
3233          top](const boost::system::error_code ec,
3234               const boost::container::flat_map<uint64_t, uint64_t>& postcode) {
3235             if (ec)
3236             {
3237                 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
3238                 messages::internalError(aResp->res);
3239                 return;
3240             }
3241 
3242             uint64_t endCount = entryCount;
3243             if (!postcode.empty())
3244             {
3245                 endCount = entryCount + postcode.size();
3246 
3247                 if ((skip < endCount) && ((top + skip) > entryCount))
3248                 {
3249                     uint64_t thisBootSkip =
3250                         std::max(skip, entryCount) - entryCount;
3251                     uint64_t thisBootTop =
3252                         std::min(top + skip, endCount) - entryCount;
3253 
3254                     fillPostCodeEntry(aResp, postcode, bootIndex, 0,
3255                                       thisBootSkip, thisBootTop);
3256                 }
3257                 aResp->res.jsonValue["Members@odata.count"] = endCount;
3258             }
3259 
3260             // continue to previous bootIndex
3261             if (bootIndex < bootCount)
3262             {
3263                 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1),
3264                                    bootCount, endCount, skip, top);
3265             }
3266             else
3267             {
3268                 aResp->res.jsonValue["Members@odata.nextLink"] =
3269                     "/redfish/v1/Systems/system/LogServices/PostCodes/"
3270                     "Entries?$skip=" +
3271                     std::to_string(skip + top);
3272             }
3273         },
3274         "xyz.openbmc_project.State.Boot.PostCode",
3275         "/xyz/openbmc_project/State/Boot/PostCode",
3276         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
3277         bootIndex);
3278 }
3279 
3280 static void getCurrentBootNumber(std::shared_ptr<AsyncResp> aResp,
3281                                  const uint64_t skip, const uint64_t top)
3282 {
3283     uint64_t entryCount = 0;
3284     crow::connections::systemBus->async_method_call(
3285         [aResp, entryCount, skip,
3286          top](const boost::system::error_code ec,
3287               const std::variant<uint16_t>& bootCount) {
3288             if (ec)
3289             {
3290                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
3291                 messages::internalError(aResp->res);
3292                 return;
3293             }
3294             auto pVal = std::get_if<uint16_t>(&bootCount);
3295             if (pVal)
3296             {
3297                 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top);
3298             }
3299             else
3300             {
3301                 BMCWEB_LOG_DEBUG << "Post code boot index failed.";
3302             }
3303         },
3304         "xyz.openbmc_project.State.Boot.PostCode",
3305         "/xyz/openbmc_project/State/Boot/PostCode",
3306         "org.freedesktop.DBus.Properties", "Get",
3307         "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount");
3308 }
3309 
3310 class PostCodesEntryCollection : public Node
3311 {
3312   public:
3313     PostCodesEntryCollection(App& app) :
3314         Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/")
3315     {
3316         entityPrivileges = {
3317             {boost::beast::http::verb::get, {{"Login"}}},
3318             {boost::beast::http::verb::head, {{"Login"}}},
3319             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
3320             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
3321             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
3322             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3323     }
3324 
3325   private:
3326     void doGet(crow::Response& res, const crow::Request& req,
3327                const std::vector<std::string>&) override
3328     {
3329         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
3330 
3331         asyncResp->res.jsonValue["@odata.type"] =
3332             "#LogEntryCollection.LogEntryCollection";
3333         asyncResp->res.jsonValue["@odata.context"] =
3334             "/redfish/v1/"
3335             "$metadata#LogEntryCollection.LogEntryCollection";
3336         asyncResp->res.jsonValue["@odata.id"] =
3337             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
3338         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
3339         asyncResp->res.jsonValue["Description"] =
3340             "Collection of POST Code Log Entries";
3341         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3342         asyncResp->res.jsonValue["Members@odata.count"] = 0;
3343 
3344         uint64_t skip = 0;
3345         uint64_t top = maxEntriesPerPage; // Show max entries by default
3346         if (!getSkipParam(asyncResp->res, req, skip))
3347         {
3348             return;
3349         }
3350         if (!getTopParam(asyncResp->res, req, top))
3351         {
3352             return;
3353         }
3354         getCurrentBootNumber(asyncResp, skip, top);
3355     }
3356 };
3357 
3358 class PostCodesEntry : public Node
3359 {
3360   public:
3361     PostCodesEntry(App& app) :
3362         Node(app,
3363              "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/",
3364              std::string())
3365     {
3366         entityPrivileges = {
3367             {boost::beast::http::verb::get, {{"Login"}}},
3368             {boost::beast::http::verb::head, {{"Login"}}},
3369             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
3370             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
3371             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
3372             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
3373     }
3374 
3375   private:
3376     void doGet(crow::Response& res, const crow::Request&,
3377                const std::vector<std::string>& params) override
3378     {
3379         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
3380         if (params.size() != 1)
3381         {
3382             messages::internalError(asyncResp->res);
3383             return;
3384         }
3385 
3386         const std::string& targetID = params[0];
3387 
3388         size_t bootPos = targetID.find('B');
3389         if (bootPos == std::string::npos)
3390         {
3391             // Requested ID was not found
3392             messages::resourceMissingAtURI(asyncResp->res, targetID);
3393             return;
3394         }
3395         std::string_view bootIndexStr(targetID);
3396         bootIndexStr.remove_prefix(bootPos + 1);
3397         uint16_t bootIndex = 0;
3398         uint64_t codeIndex = 0;
3399         size_t dashPos = bootIndexStr.find('-');
3400 
3401         if (dashPos == std::string::npos)
3402         {
3403             return;
3404         }
3405         std::string_view codeIndexStr(bootIndexStr);
3406         bootIndexStr.remove_suffix(dashPos);
3407         codeIndexStr.remove_prefix(dashPos + 1);
3408 
3409         bootIndex = static_cast<uint16_t>(
3410             strtoul(std::string(bootIndexStr).c_str(), nullptr, 0));
3411         codeIndex = strtoul(std::string(codeIndexStr).c_str(), nullptr, 0);
3412         if (bootIndex == 0 || codeIndex == 0)
3413         {
3414             BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string "
3415                              << params[0];
3416         }
3417 
3418         asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry";
3419         asyncResp->res.jsonValue["@odata.context"] =
3420             "/redfish/v1/$metadata#LogEntry.LogEntry";
3421         asyncResp->res.jsonValue["@odata.id"] =
3422             "/redfish/v1/Systems/system/LogServices/PostCodes/"
3423             "Entries";
3424         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
3425         asyncResp->res.jsonValue["Description"] =
3426             "Collection of POST Code Log Entries";
3427         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3428         asyncResp->res.jsonValue["Members@odata.count"] = 0;
3429 
3430         getPostCodeForEntry(asyncResp, bootIndex, codeIndex);
3431     }
3432 };
3433 
3434 } // namespace redfish
3435