xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision b90d14f220cba6de26dcf8749b2f8df062487d72)
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 "app.hpp"
19 #include "dbus_utility.hpp"
20 #include "error_messages.hpp"
21 #include "generated/enums/log_entry.hpp"
22 #include "gzfile.hpp"
23 #include "http_utility.hpp"
24 #include "human_sort.hpp"
25 #include "query.hpp"
26 #include "registries.hpp"
27 #include "registries/base_message_registry.hpp"
28 #include "registries/openbmc_message_registry.hpp"
29 #include "registries/privilege_registry.hpp"
30 #include "task.hpp"
31 #include "utils/dbus_utils.hpp"
32 #include "utils/time_utils.hpp"
33 
34 #include <systemd/sd-journal.h>
35 #include <tinyxml2.h>
36 #include <unistd.h>
37 
38 #include <boost/algorithm/string/case_conv.hpp>
39 #include <boost/algorithm/string/classification.hpp>
40 #include <boost/algorithm/string/replace.hpp>
41 #include <boost/algorithm/string/split.hpp>
42 #include <boost/beast/http/verb.hpp>
43 #include <boost/container/flat_map.hpp>
44 #include <boost/system/linux_error.hpp>
45 #include <boost/url/format.hpp>
46 #include <sdbusplus/asio/property.hpp>
47 #include <sdbusplus/unpack_properties.hpp>
48 
49 #include <array>
50 #include <charconv>
51 #include <filesystem>
52 #include <optional>
53 #include <span>
54 #include <string_view>
55 #include <variant>
56 
57 namespace redfish
58 {
59 
60 constexpr const char* crashdumpObject = "com.intel.crashdump";
61 constexpr const char* crashdumpPath = "/com/intel/crashdump";
62 constexpr const char* crashdumpInterface = "com.intel.crashdump";
63 constexpr const char* deleteAllInterface =
64     "xyz.openbmc_project.Collection.DeleteAll";
65 constexpr const char* crashdumpOnDemandInterface =
66     "com.intel.crashdump.OnDemand";
67 constexpr const char* crashdumpTelemetryInterface =
68     "com.intel.crashdump.Telemetry";
69 
70 enum class DumpCreationProgress
71 {
72     DUMP_CREATE_SUCCESS,
73     DUMP_CREATE_FAILED,
74     DUMP_CREATE_INPROGRESS
75 };
76 
77 namespace fs = std::filesystem;
78 
79 inline std::string translateSeverityDbusToRedfish(const std::string& s)
80 {
81     if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
82         (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
83         (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
84         (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
85     {
86         return "Critical";
87     }
88     if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
89         (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
90         (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
91     {
92         return "OK";
93     }
94     if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
95     {
96         return "Warning";
97     }
98     return "";
99 }
100 
101 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
102 {
103     std::optional<bool> notifyAction;
104     if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
105     {
106         notifyAction = true;
107     }
108     else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
109     {
110         notifyAction = false;
111     }
112 
113     return notifyAction;
114 }
115 
116 inline static int getJournalMetadata(sd_journal* journal,
117                                      std::string_view field,
118                                      std::string_view& contents)
119 {
120     const char* data = nullptr;
121     size_t length = 0;
122     int ret = 0;
123     // Get the metadata from the requested field of the journal entry
124     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
125     const void** dataVoid = reinterpret_cast<const void**>(&data);
126 
127     ret = sd_journal_get_data(journal, field.data(), dataVoid, &length);
128     if (ret < 0)
129     {
130         return ret;
131     }
132     contents = std::string_view(data, length);
133     // Only use the content after the "=" character.
134     contents.remove_prefix(std::min(contents.find('=') + 1, contents.size()));
135     return ret;
136 }
137 
138 inline static int getJournalMetadata(sd_journal* journal,
139                                      std::string_view field, const int& base,
140                                      long int& contents)
141 {
142     int ret = 0;
143     std::string_view metadata;
144     // Get the metadata from the requested field of the journal entry
145     ret = getJournalMetadata(journal, field, metadata);
146     if (ret < 0)
147     {
148         return ret;
149     }
150     contents = strtol(metadata.data(), nullptr, base);
151     return ret;
152 }
153 
154 inline static bool getEntryTimestamp(sd_journal* journal,
155                                      std::string& entryTimestamp)
156 {
157     int ret = 0;
158     uint64_t timestamp = 0;
159     ret = sd_journal_get_realtime_usec(journal, &timestamp);
160     if (ret < 0)
161     {
162         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
163                          << strerror(-ret);
164         return false;
165     }
166     entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp);
167     return true;
168 }
169 
170 inline static bool getUniqueEntryID(sd_journal* journal, std::string& entryID,
171                                     const bool firstEntry = true)
172 {
173     int ret = 0;
174     static uint64_t prevTs = 0;
175     static int index = 0;
176     if (firstEntry)
177     {
178         prevTs = 0;
179     }
180 
181     // Get the entry timestamp
182     uint64_t curTs = 0;
183     ret = sd_journal_get_realtime_usec(journal, &curTs);
184     if (ret < 0)
185     {
186         BMCWEB_LOG_ERROR << "Failed to read entry timestamp: "
187                          << strerror(-ret);
188         return false;
189     }
190     // If the timestamp isn't unique, increment the index
191     if (curTs == prevTs)
192     {
193         index++;
194     }
195     else
196     {
197         // Otherwise, reset it
198         index = 0;
199     }
200     // Save the timestamp
201     prevTs = curTs;
202 
203     entryID = std::to_string(curTs);
204     if (index > 0)
205     {
206         entryID += "_" + std::to_string(index);
207     }
208     return true;
209 }
210 
211 static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
212                              const bool firstEntry = true)
213 {
214     static time_t prevTs = 0;
215     static int index = 0;
216     if (firstEntry)
217     {
218         prevTs = 0;
219     }
220 
221     // Get the entry timestamp
222     std::time_t curTs = 0;
223     std::tm timeStruct = {};
224     std::istringstream entryStream(logEntry);
225     if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
226     {
227         curTs = std::mktime(&timeStruct);
228     }
229     // If the timestamp isn't unique, increment the index
230     if (curTs == prevTs)
231     {
232         index++;
233     }
234     else
235     {
236         // Otherwise, reset it
237         index = 0;
238     }
239     // Save the timestamp
240     prevTs = curTs;
241 
242     entryID = std::to_string(curTs);
243     if (index > 0)
244     {
245         entryID += "_" + std::to_string(index);
246     }
247     return true;
248 }
249 
250 inline static bool
251     getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
252                        const std::string& entryID, uint64_t& timestamp,
253                        uint64_t& index)
254 {
255     if (entryID.empty())
256     {
257         return false;
258     }
259     // Convert the unique ID back to a timestamp to find the entry
260     std::string_view tsStr(entryID);
261 
262     auto underscorePos = tsStr.find('_');
263     if (underscorePos != std::string_view::npos)
264     {
265         // Timestamp has an index
266         tsStr.remove_suffix(tsStr.size() - underscorePos);
267         std::string_view indexStr(entryID);
268         indexStr.remove_prefix(underscorePos + 1);
269         auto [ptr, ec] = std::from_chars(indexStr.begin(), indexStr.end(),
270                                          index);
271         if (ec != std::errc())
272         {
273             messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
274             return false;
275         }
276     }
277     // Timestamp has no index
278     auto [ptr, ec] = std::from_chars(tsStr.begin(), tsStr.end(), timestamp);
279     if (ec != std::errc())
280     {
281         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
282         return false;
283     }
284     return true;
285 }
286 
287 static bool
288     getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles)
289 {
290     static const std::filesystem::path redfishLogDir = "/var/log";
291     static const std::string redfishLogFilename = "redfish";
292 
293     // Loop through the directory looking for redfish log files
294     for (const std::filesystem::directory_entry& dirEnt :
295          std::filesystem::directory_iterator(redfishLogDir))
296     {
297         // If we find a redfish log file, save the path
298         std::string filename = dirEnt.path().filename();
299         if (filename.starts_with(redfishLogFilename))
300         {
301             redfishLogFiles.emplace_back(redfishLogDir / filename);
302         }
303     }
304     // As the log files rotate, they are appended with a ".#" that is higher for
305     // the older logs. Since we don't expect more than 10 log files, we
306     // can just sort the list to get them in order from newest to oldest
307     std::sort(redfishLogFiles.begin(), redfishLogFiles.end());
308 
309     return !redfishLogFiles.empty();
310 }
311 
312 inline log_entry::OriginatorTypes
313     mapDbusOriginatorTypeToRedfish(const std::string& originatorType)
314 {
315     if (originatorType ==
316         "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client")
317     {
318         return log_entry::OriginatorTypes::Client;
319     }
320     if (originatorType ==
321         "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Internal")
322     {
323         return log_entry::OriginatorTypes::Internal;
324     }
325     if (originatorType ==
326         "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.SupportingService")
327     {
328         return log_entry::OriginatorTypes::SupportingService;
329     }
330     return log_entry::OriginatorTypes::Invalid;
331 }
332 
333 inline void parseDumpEntryFromDbusObject(
334     const dbus::utility::ManagedObjectType::value_type& object,
335     std::string& dumpStatus, uint64_t& size, uint64_t& timestampUs,
336     std::string& originatorId, log_entry::OriginatorTypes& originatorType,
337     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
338 {
339     for (const auto& interfaceMap : object.second)
340     {
341         if (interfaceMap.first == "xyz.openbmc_project.Common.Progress")
342         {
343             for (const auto& propertyMap : interfaceMap.second)
344             {
345                 if (propertyMap.first == "Status")
346                 {
347                     const auto* status =
348                         std::get_if<std::string>(&propertyMap.second);
349                     if (status == nullptr)
350                     {
351                         messages::internalError(asyncResp->res);
352                         break;
353                     }
354                     dumpStatus = *status;
355                 }
356             }
357         }
358         else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry")
359         {
360             for (const auto& propertyMap : interfaceMap.second)
361             {
362                 if (propertyMap.first == "Size")
363                 {
364                     const auto* sizePtr =
365                         std::get_if<uint64_t>(&propertyMap.second);
366                     if (sizePtr == nullptr)
367                     {
368                         messages::internalError(asyncResp->res);
369                         break;
370                     }
371                     size = *sizePtr;
372                     break;
373                 }
374             }
375         }
376         else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime")
377         {
378             for (const auto& propertyMap : interfaceMap.second)
379             {
380                 if (propertyMap.first == "Elapsed")
381                 {
382                     const uint64_t* usecsTimeStamp =
383                         std::get_if<uint64_t>(&propertyMap.second);
384                     if (usecsTimeStamp == nullptr)
385                     {
386                         messages::internalError(asyncResp->res);
387                         break;
388                     }
389                     timestampUs = *usecsTimeStamp;
390                     break;
391                 }
392             }
393         }
394         else if (interfaceMap.first ==
395                  "xyz.openbmc_project.Common.OriginatedBy")
396         {
397             for (const auto& propertyMap : interfaceMap.second)
398             {
399                 if (propertyMap.first == "OriginatorId")
400                 {
401                     const std::string* id =
402                         std::get_if<std::string>(&propertyMap.second);
403                     if (id == nullptr)
404                     {
405                         messages::internalError(asyncResp->res);
406                         break;
407                     }
408                     originatorId = *id;
409                 }
410 
411                 if (propertyMap.first == "OriginatorType")
412                 {
413                     const std::string* type =
414                         std::get_if<std::string>(&propertyMap.second);
415                     if (type == nullptr)
416                     {
417                         messages::internalError(asyncResp->res);
418                         break;
419                     }
420 
421                     originatorType = mapDbusOriginatorTypeToRedfish(*type);
422                     if (originatorType == log_entry::OriginatorTypes::Invalid)
423                     {
424                         messages::internalError(asyncResp->res);
425                         break;
426                     }
427                 }
428             }
429         }
430     }
431 }
432 
433 static std::string getDumpEntriesPath(const std::string& dumpType)
434 {
435     std::string entriesPath;
436 
437     if (dumpType == "BMC")
438     {
439         entriesPath = "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
440     }
441     else if (dumpType == "FaultLog")
442     {
443         entriesPath = "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/";
444     }
445     else if (dumpType == "System")
446     {
447         entriesPath = "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
448     }
449     else
450     {
451         BMCWEB_LOG_ERROR << "getDumpEntriesPath() invalid dump type: "
452                          << dumpType;
453     }
454 
455     // Returns empty string on error
456     return entriesPath;
457 }
458 
459 inline void
460     getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
461                            const std::string& dumpType)
462 {
463     std::string entriesPath = getDumpEntriesPath(dumpType);
464     if (entriesPath.empty())
465     {
466         messages::internalError(asyncResp->res);
467         return;
468     }
469 
470     crow::connections::systemBus->async_method_call(
471         [asyncResp, entriesPath,
472          dumpType](const boost::system::error_code& ec,
473                    dbus::utility::ManagedObjectType& resp) {
474         if (ec)
475         {
476             BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
477             messages::internalError(asyncResp->res);
478             return;
479         }
480 
481         // Remove ending slash
482         std::string odataIdStr = entriesPath;
483         if (!odataIdStr.empty())
484         {
485             odataIdStr.pop_back();
486         }
487 
488         asyncResp->res.jsonValue["@odata.type"] =
489             "#LogEntryCollection.LogEntryCollection";
490         asyncResp->res.jsonValue["@odata.id"] = std::move(odataIdStr);
491         asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entries";
492         asyncResp->res.jsonValue["Description"] = "Collection of " + dumpType +
493                                                   " Dump Entries";
494 
495         nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
496         entriesArray = nlohmann::json::array();
497         std::string dumpEntryPath =
498             "/xyz/openbmc_project/dump/" +
499             std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/";
500 
501         std::sort(resp.begin(), resp.end(), [](const auto& l, const auto& r) {
502             return AlphanumLess<std::string>()(l.first.filename(),
503                                                r.first.filename());
504         });
505 
506         for (auto& object : resp)
507         {
508             if (object.first.str.find(dumpEntryPath) == std::string::npos)
509             {
510                 continue;
511             }
512             uint64_t timestampUs = 0;
513             uint64_t size = 0;
514             std::string dumpStatus;
515             std::string originatorId;
516             log_entry::OriginatorTypes originatorType =
517                 log_entry::OriginatorTypes::Internal;
518             nlohmann::json::object_t thisEntry;
519 
520             std::string entryID = object.first.filename();
521             if (entryID.empty())
522             {
523                 continue;
524             }
525 
526             parseDumpEntryFromDbusObject(object, dumpStatus, size, timestampUs,
527                                          originatorId, originatorType,
528                                          asyncResp);
529 
530             if (dumpStatus !=
531                     "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
532                 !dumpStatus.empty())
533             {
534                 // Dump status is not Complete, no need to enumerate
535                 continue;
536             }
537 
538             thisEntry["@odata.type"] = "#LogEntry.v1_11_0.LogEntry";
539             thisEntry["@odata.id"] = entriesPath + entryID;
540             thisEntry["Id"] = entryID;
541             thisEntry["EntryType"] = "Event";
542             thisEntry["Name"] = dumpType + " Dump Entry";
543             thisEntry["Created"] =
544                 redfish::time_utils::getDateTimeUintUs(timestampUs);
545 
546             if (!originatorId.empty())
547             {
548                 thisEntry["Originator"] = originatorId;
549                 thisEntry["OriginatorType"] = originatorType;
550             }
551 
552             if (dumpType == "BMC")
553             {
554                 thisEntry["DiagnosticDataType"] = "Manager";
555                 thisEntry["AdditionalDataURI"] = entriesPath + entryID +
556                                                  "/attachment";
557                 thisEntry["AdditionalDataSizeBytes"] = size;
558             }
559             else if (dumpType == "System")
560             {
561                 thisEntry["DiagnosticDataType"] = "OEM";
562                 thisEntry["OEMDiagnosticDataType"] = "System";
563                 thisEntry["AdditionalDataURI"] = entriesPath + entryID +
564                                                  "/attachment";
565                 thisEntry["AdditionalDataSizeBytes"] = size;
566             }
567             entriesArray.emplace_back(std::move(thisEntry));
568         }
569         asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
570         },
571         "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
572         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
573 }
574 
575 inline void
576     getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
577                      const std::string& entryID, const std::string& dumpType)
578 {
579     std::string entriesPath = getDumpEntriesPath(dumpType);
580     if (entriesPath.empty())
581     {
582         messages::internalError(asyncResp->res);
583         return;
584     }
585 
586     crow::connections::systemBus->async_method_call(
587         [asyncResp, entryID, dumpType,
588          entriesPath](const boost::system::error_code& ec,
589                       const dbus::utility::ManagedObjectType& resp) {
590         if (ec)
591         {
592             BMCWEB_LOG_ERROR << "DumpEntry resp_handler got error " << ec;
593             messages::internalError(asyncResp->res);
594             return;
595         }
596 
597         bool foundDumpEntry = false;
598         std::string dumpEntryPath =
599             "/xyz/openbmc_project/dump/" +
600             std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/";
601 
602         for (const auto& objectPath : resp)
603         {
604             if (objectPath.first.str != dumpEntryPath + entryID)
605             {
606                 continue;
607             }
608 
609             foundDumpEntry = true;
610             uint64_t timestampUs = 0;
611             uint64_t size = 0;
612             std::string dumpStatus;
613             std::string originatorId;
614             log_entry::OriginatorTypes originatorType =
615                 log_entry::OriginatorTypes::Internal;
616 
617             parseDumpEntryFromDbusObject(objectPath, dumpStatus, size,
618                                          timestampUs, originatorId,
619                                          originatorType, asyncResp);
620 
621             if (dumpStatus !=
622                     "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
623                 !dumpStatus.empty())
624             {
625                 // Dump status is not Complete
626                 // return not found until status is changed to Completed
627                 messages::resourceNotFound(asyncResp->res, dumpType + " dump",
628                                            entryID);
629                 return;
630             }
631 
632             asyncResp->res.jsonValue["@odata.type"] =
633                 "#LogEntry.v1_11_0.LogEntry";
634             asyncResp->res.jsonValue["@odata.id"] = entriesPath + entryID;
635             asyncResp->res.jsonValue["Id"] = entryID;
636             asyncResp->res.jsonValue["EntryType"] = "Event";
637             asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry";
638             asyncResp->res.jsonValue["Created"] =
639                 redfish::time_utils::getDateTimeUintUs(timestampUs);
640 
641             if (!originatorId.empty())
642             {
643                 asyncResp->res.jsonValue["Originator"] = originatorId;
644                 asyncResp->res.jsonValue["OriginatorType"] = originatorType;
645             }
646 
647             if (dumpType == "BMC")
648             {
649                 asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager";
650                 asyncResp->res.jsonValue["AdditionalDataURI"] =
651                     entriesPath + entryID + "/attachment";
652                 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
653             }
654             else if (dumpType == "System")
655             {
656                 asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
657                 asyncResp->res.jsonValue["OEMDiagnosticDataType"] = "System";
658                 asyncResp->res.jsonValue["AdditionalDataURI"] =
659                     entriesPath + entryID + "/attachment";
660                 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
661             }
662         }
663         if (!foundDumpEntry)
664         {
665             BMCWEB_LOG_WARNING << "Can't find Dump Entry " << entryID;
666             messages::resourceNotFound(asyncResp->res, dumpType + " dump",
667                                        entryID);
668             return;
669         }
670         },
671         "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump",
672         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
673 }
674 
675 inline void deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
676                             const std::string& entryID,
677                             const std::string& dumpType)
678 {
679     auto respHandler =
680         [asyncResp, entryID](const boost::system::error_code& ec) {
681         BMCWEB_LOG_DEBUG << "Dump Entry doDelete callback: Done";
682         if (ec)
683         {
684             if (ec.value() == EBADR)
685             {
686                 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
687                 return;
688             }
689             BMCWEB_LOG_ERROR << "Dump (DBus) doDelete respHandler got error "
690                              << ec << " entryID=" << entryID;
691             messages::internalError(asyncResp->res);
692             return;
693         }
694     };
695     crow::connections::systemBus->async_method_call(
696         respHandler, "xyz.openbmc_project.Dump.Manager",
697         "/xyz/openbmc_project/dump/" +
698             std::string(boost::algorithm::to_lower_copy(dumpType)) + "/entry/" +
699             entryID,
700         "xyz.openbmc_project.Object.Delete", "Delete");
701 }
702 
703 inline DumpCreationProgress
704     mapDbusStatusToDumpProgress(const std::string& status)
705 {
706     if (status ==
707             "xyz.openbmc_project.Common.Progress.OperationStatus.Failed" ||
708         status == "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted")
709     {
710         return DumpCreationProgress::DUMP_CREATE_FAILED;
711     }
712     if (status ==
713         "xyz.openbmc_project.Common.Progress.OperationStatus.Completed")
714     {
715         return DumpCreationProgress::DUMP_CREATE_SUCCESS;
716     }
717     return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
718 }
719 
720 inline DumpCreationProgress
721     getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap& values)
722 {
723     for (const auto& [key, val] : values)
724     {
725         if (key == "Status")
726         {
727             const std::string* value = std::get_if<std::string>(&val);
728             if (value == nullptr)
729             {
730                 BMCWEB_LOG_ERROR << "Status property value is null";
731                 return DumpCreationProgress::DUMP_CREATE_FAILED;
732             }
733             return mapDbusStatusToDumpProgress(*value);
734         }
735     }
736     return DumpCreationProgress::DUMP_CREATE_INPROGRESS;
737 }
738 
739 inline std::string getDumpEntryPath(const std::string& dumpPath)
740 {
741     if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry")
742     {
743         return "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/";
744     }
745     if (dumpPath == "/xyz/openbmc_project/dump/system/entry")
746     {
747         return "/redfish/v1/Systems/system/LogServices/Dump/Entries/";
748     }
749     return "";
750 }
751 
752 inline void createDumpTaskCallback(
753     task::Payload&& payload,
754     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
755     const sdbusplus::message::object_path& createdObjPath)
756 {
757     const std::string dumpPath = createdObjPath.parent_path().str;
758     const std::string dumpId = createdObjPath.filename();
759 
760     std::string dumpEntryPath = getDumpEntryPath(dumpPath);
761 
762     if (dumpEntryPath.empty())
763     {
764         BMCWEB_LOG_ERROR << "Invalid dump type received";
765         messages::internalError(asyncResp->res);
766         return;
767     }
768 
769     crow::connections::systemBus->async_method_call(
770         [asyncResp, payload, createdObjPath,
771          dumpEntryPath{std::move(dumpEntryPath)},
772          dumpId](const boost::system::error_code& ec,
773                  const std::string& introspectXml) {
774         if (ec)
775         {
776             BMCWEB_LOG_ERROR << "Introspect call failed with error: "
777                              << ec.message();
778             messages::internalError(asyncResp->res);
779             return;
780         }
781 
782         // Check if the created dump object has implemented Progress
783         // interface to track dump completion. If yes, fetch the "Status"
784         // property of the interface, modify the task state accordingly.
785         // Else, return task completed.
786         tinyxml2::XMLDocument doc;
787 
788         doc.Parse(introspectXml.data(), introspectXml.size());
789         tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
790         if (pRoot == nullptr)
791         {
792             BMCWEB_LOG_ERROR << "XML document failed to parse";
793             messages::internalError(asyncResp->res);
794             return;
795         }
796         tinyxml2::XMLElement* interfaceNode =
797             pRoot->FirstChildElement("interface");
798 
799         bool isProgressIntfPresent = false;
800         while (interfaceNode != nullptr)
801         {
802             const char* thisInterfaceName = interfaceNode->Attribute("name");
803             if (thisInterfaceName != nullptr)
804             {
805                 if (thisInterfaceName ==
806                     std::string_view("xyz.openbmc_project.Common.Progress"))
807                 {
808                     interfaceNode =
809                         interfaceNode->NextSiblingElement("interface");
810                     continue;
811                 }
812                 isProgressIntfPresent = true;
813                 break;
814             }
815             interfaceNode = interfaceNode->NextSiblingElement("interface");
816         }
817 
818         std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
819             [createdObjPath, dumpEntryPath, dumpId, isProgressIntfPresent](
820                 const boost::system::error_code& err, sdbusplus::message_t& msg,
821                 const std::shared_ptr<task::TaskData>& taskData) {
822             if (err)
823             {
824                 BMCWEB_LOG_ERROR << createdObjPath.str
825                                  << ": Error in creating dump";
826                 taskData->messages.emplace_back(messages::internalError());
827                 taskData->state = "Cancelled";
828                 return task::completed;
829             }
830 
831             if (isProgressIntfPresent)
832             {
833                 dbus::utility::DBusPropertiesMap values;
834                 std::string prop;
835                 msg.read(prop, values);
836 
837                 DumpCreationProgress dumpStatus =
838                     getDumpCompletionStatus(values);
839                 if (dumpStatus == DumpCreationProgress::DUMP_CREATE_FAILED)
840                 {
841                     BMCWEB_LOG_ERROR << createdObjPath.str
842                                      << ": Error in creating dump";
843                     taskData->state = "Cancelled";
844                     return task::completed;
845                 }
846 
847                 if (dumpStatus == DumpCreationProgress::DUMP_CREATE_INPROGRESS)
848                 {
849                     BMCWEB_LOG_DEBUG << createdObjPath.str
850                                      << ": Dump creation task is in progress";
851                     return !task::completed;
852                 }
853             }
854 
855             nlohmann::json retMessage = messages::success();
856             taskData->messages.emplace_back(retMessage);
857 
858             boost::urls::url url = boost::urls::format(
859                 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/{}", dumpId);
860 
861             std::string headerLoc = "Location: ";
862             headerLoc += url.buffer();
863 
864             taskData->payload->httpHeaders.emplace_back(std::move(headerLoc));
865 
866             BMCWEB_LOG_DEBUG << createdObjPath.str
867                              << ": Dump creation task completed";
868             taskData->state = "Completed";
869             return task::completed;
870             },
871             "type='signal',interface='org.freedesktop.DBus.Properties',"
872             "member='PropertiesChanged',path='" +
873                 createdObjPath.str + "'");
874 
875         // The task timer is set to max time limit within which the
876         // requested dump will be collected.
877         task->startTimer(std::chrono::minutes(6));
878         task->populateResp(asyncResp->res);
879         task->payload.emplace(payload);
880         },
881         "xyz.openbmc_project.Dump.Manager", createdObjPath,
882         "org.freedesktop.DBus.Introspectable", "Introspect");
883 }
884 
885 inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
886                        const crow::Request& req, const std::string& dumpType)
887 {
888     std::string dumpPath = getDumpEntriesPath(dumpType);
889     if (dumpPath.empty())
890     {
891         messages::internalError(asyncResp->res);
892         return;
893     }
894 
895     std::optional<std::string> diagnosticDataType;
896     std::optional<std::string> oemDiagnosticDataType;
897 
898     if (!redfish::json_util::readJsonAction(
899             req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
900             "OEMDiagnosticDataType", oemDiagnosticDataType))
901     {
902         return;
903     }
904 
905     if (dumpType == "System")
906     {
907         if (!oemDiagnosticDataType || !diagnosticDataType)
908         {
909             BMCWEB_LOG_ERROR
910                 << "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!";
911             messages::actionParameterMissing(
912                 asyncResp->res, "CollectDiagnosticData",
913                 "DiagnosticDataType & OEMDiagnosticDataType");
914             return;
915         }
916         if ((*oemDiagnosticDataType != "System") ||
917             (*diagnosticDataType != "OEM"))
918         {
919             BMCWEB_LOG_ERROR << "Wrong parameter values passed";
920             messages::internalError(asyncResp->res);
921             return;
922         }
923         dumpPath = "/redfish/v1/Systems/system/LogServices/Dump/";
924     }
925     else if (dumpType == "BMC")
926     {
927         if (!diagnosticDataType)
928         {
929             BMCWEB_LOG_ERROR
930                 << "CreateDump action parameter 'DiagnosticDataType' not found!";
931             messages::actionParameterMissing(
932                 asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType");
933             return;
934         }
935         if (*diagnosticDataType != "Manager")
936         {
937             BMCWEB_LOG_ERROR
938                 << "Wrong parameter value passed for 'DiagnosticDataType'";
939             messages::internalError(asyncResp->res);
940             return;
941         }
942         dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump/";
943     }
944     else
945     {
946         BMCWEB_LOG_ERROR << "CreateDump failed. Unknown dump type";
947         messages::internalError(asyncResp->res);
948         return;
949     }
950 
951     std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>>
952         createDumpParamVec;
953 
954     if (req.session != nullptr)
955     {
956         createDumpParamVec.emplace_back(
957             "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorId",
958             req.session->clientIp);
959         createDumpParamVec.emplace_back(
960             "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorType",
961             "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client");
962     }
963 
964     crow::connections::systemBus->async_method_call(
965         [asyncResp, payload(task::Payload(req)),
966          dumpPath](const boost::system::error_code& ec,
967                    const sdbusplus::message_t& msg,
968                    const sdbusplus::message::object_path& objPath) mutable {
969         if (ec)
970         {
971             BMCWEB_LOG_ERROR << "CreateDump resp_handler got error " << ec;
972             const sd_bus_error* dbusError = msg.get_error();
973             if (dbusError == nullptr)
974             {
975                 messages::internalError(asyncResp->res);
976                 return;
977             }
978 
979             BMCWEB_LOG_ERROR << "CreateDump DBus error: " << dbusError->name
980                              << " and error msg: " << dbusError->message;
981             if (std::string_view(
982                     "xyz.openbmc_project.Common.Error.NotAllowed") ==
983                 dbusError->name)
984             {
985                 messages::resourceInStandby(asyncResp->res);
986                 return;
987             }
988             if (std::string_view(
989                     "xyz.openbmc_project.Dump.Create.Error.Disabled") ==
990                 dbusError->name)
991             {
992                 messages::serviceDisabled(asyncResp->res, dumpPath);
993                 return;
994             }
995             if (std::string_view(
996                     "xyz.openbmc_project.Common.Error.Unavailable") ==
997                 dbusError->name)
998             {
999                 messages::resourceInUse(asyncResp->res);
1000                 return;
1001             }
1002             // Other Dbus errors such as:
1003             // xyz.openbmc_project.Common.Error.InvalidArgument &
1004             // org.freedesktop.DBus.Error.InvalidArgs are all related to
1005             // the dbus call that is made here in the bmcweb
1006             // implementation and has nothing to do with the client's
1007             // input in the request. Hence, returning internal error
1008             // back to the client.
1009             messages::internalError(asyncResp->res);
1010             return;
1011         }
1012         BMCWEB_LOG_DEBUG << "Dump Created. Path: " << objPath.str;
1013         createDumpTaskCallback(std::move(payload), asyncResp, objPath);
1014         },
1015         "xyz.openbmc_project.Dump.Manager",
1016         "/xyz/openbmc_project/dump/" +
1017             std::string(boost::algorithm::to_lower_copy(dumpType)),
1018         "xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec);
1019 }
1020 
1021 inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1022                       const std::string& dumpType)
1023 {
1024     std::string dumpTypeLowerCopy =
1025         std::string(boost::algorithm::to_lower_copy(dumpType));
1026 
1027     crow::connections::systemBus->async_method_call(
1028         [asyncResp](const boost::system::error_code& ec) {
1029         if (ec)
1030         {
1031             BMCWEB_LOG_ERROR << "clearDump resp_handler got error " << ec;
1032             messages::internalError(asyncResp->res);
1033             return;
1034         }
1035         },
1036         "xyz.openbmc_project.Dump.Manager",
1037         "/xyz/openbmc_project/dump/" + dumpTypeLowerCopy,
1038         "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
1039 }
1040 
1041 inline static void
1042     parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap& params,
1043                              std::string& filename, std::string& timestamp,
1044                              std::string& logfile)
1045 {
1046     const std::string* filenamePtr = nullptr;
1047     const std::string* timestampPtr = nullptr;
1048     const std::string* logfilePtr = nullptr;
1049 
1050     const bool success = sdbusplus::unpackPropertiesNoThrow(
1051         dbus_utils::UnpackErrorPrinter(), params, "Timestamp", timestampPtr,
1052         "Filename", filenamePtr, "Log", logfilePtr);
1053 
1054     if (!success)
1055     {
1056         return;
1057     }
1058 
1059     if (filenamePtr != nullptr)
1060     {
1061         filename = *filenamePtr;
1062     }
1063 
1064     if (timestampPtr != nullptr)
1065     {
1066         timestamp = *timestampPtr;
1067     }
1068 
1069     if (logfilePtr != nullptr)
1070     {
1071         logfile = *logfilePtr;
1072     }
1073 }
1074 
1075 inline void requestRoutesSystemLogServiceCollection(App& app)
1076 {
1077     /**
1078      * Functions triggers appropriate requests on DBus
1079      */
1080     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/")
1081         .privileges(redfish::privileges::getLogServiceCollection)
1082         .methods(boost::beast::http::verb::get)(
1083             [&app](const crow::Request& req,
1084                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1085                    const std::string& systemName) {
1086         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1087         {
1088             return;
1089         }
1090         if (systemName != "system")
1091         {
1092             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1093                                        systemName);
1094             return;
1095         }
1096 
1097         // Collections don't include the static data added by SubRoute
1098         // because it has a duplicate entry for members
1099         asyncResp->res.jsonValue["@odata.type"] =
1100             "#LogServiceCollection.LogServiceCollection";
1101         asyncResp->res.jsonValue["@odata.id"] =
1102             "/redfish/v1/Systems/system/LogServices";
1103         asyncResp->res.jsonValue["Name"] = "System Log Services Collection";
1104         asyncResp->res.jsonValue["Description"] =
1105             "Collection of LogServices for this Computer System";
1106         nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
1107         logServiceArray = nlohmann::json::array();
1108         nlohmann::json::object_t eventLog;
1109         eventLog["@odata.id"] =
1110             "/redfish/v1/Systems/system/LogServices/EventLog";
1111         logServiceArray.emplace_back(std::move(eventLog));
1112 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
1113         nlohmann::json::object_t dumpLog;
1114         dumpLog["@odata.id"] = "/redfish/v1/Systems/system/LogServices/Dump";
1115         logServiceArray.emplace_back(std::move(dumpLog));
1116 #endif
1117 
1118 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG
1119         nlohmann::json::object_t crashdump;
1120         crashdump["@odata.id"] =
1121             "/redfish/v1/Systems/system/LogServices/Crashdump";
1122         logServiceArray.emplace_back(std::move(crashdump));
1123 #endif
1124 
1125 #ifdef BMCWEB_ENABLE_REDFISH_HOST_LOGGER
1126         nlohmann::json::object_t hostlogger;
1127         hostlogger["@odata.id"] =
1128             "/redfish/v1/Systems/system/LogServices/HostLogger";
1129         logServiceArray.emplace_back(std::move(hostlogger));
1130 #endif
1131         asyncResp->res.jsonValue["Members@odata.count"] =
1132             logServiceArray.size();
1133 
1134         constexpr std::array<std::string_view, 1> interfaces = {
1135             "xyz.openbmc_project.State.Boot.PostCode"};
1136         dbus::utility::getSubTreePaths(
1137             "/", 0, interfaces,
1138             [asyncResp](const boost::system::error_code& ec,
1139                         const dbus::utility::MapperGetSubTreePathsResponse&
1140                             subtreePath) {
1141             if (ec)
1142             {
1143                 BMCWEB_LOG_ERROR << ec;
1144                 return;
1145             }
1146 
1147             for (const auto& pathStr : subtreePath)
1148             {
1149                 if (pathStr.find("PostCode") != std::string::npos)
1150                 {
1151                     nlohmann::json& logServiceArrayLocal =
1152                         asyncResp->res.jsonValue["Members"];
1153                     nlohmann::json::object_t member;
1154                     member["@odata.id"] =
1155                         "/redfish/v1/Systems/system/LogServices/PostCodes";
1156 
1157                     logServiceArrayLocal.emplace_back(std::move(member));
1158 
1159                     asyncResp->res.jsonValue["Members@odata.count"] =
1160                         logServiceArrayLocal.size();
1161                     return;
1162                 }
1163             }
1164             });
1165         });
1166 }
1167 
1168 inline void requestRoutesEventLogService(App& app)
1169 {
1170     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/")
1171         .privileges(redfish::privileges::getLogService)
1172         .methods(boost::beast::http::verb::get)(
1173             [&app](const crow::Request& req,
1174                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1175                    const std::string& systemName) {
1176         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1177         {
1178             return;
1179         }
1180         if (systemName != "system")
1181         {
1182             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1183                                        systemName);
1184             return;
1185         }
1186         asyncResp->res.jsonValue["@odata.id"] =
1187             "/redfish/v1/Systems/system/LogServices/EventLog";
1188         asyncResp->res.jsonValue["@odata.type"] =
1189             "#LogService.v1_1_0.LogService";
1190         asyncResp->res.jsonValue["Name"] = "Event Log Service";
1191         asyncResp->res.jsonValue["Description"] = "System Event Log Service";
1192         asyncResp->res.jsonValue["Id"] = "EventLog";
1193         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
1194 
1195         std::pair<std::string, std::string> redfishDateTimeOffset =
1196             redfish::time_utils::getDateTimeOffsetNow();
1197 
1198         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
1199         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
1200             redfishDateTimeOffset.second;
1201 
1202         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
1203             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
1204         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
1205 
1206             {"target",
1207              "/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog"}};
1208         });
1209 }
1210 
1211 inline void requestRoutesJournalEventLogClear(App& app)
1212 {
1213     BMCWEB_ROUTE(
1214         app,
1215         "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
1216         .privileges({{"ConfigureComponents"}})
1217         .methods(boost::beast::http::verb::post)(
1218             [&app](const crow::Request& req,
1219                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1220                    const std::string& systemName) {
1221         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1222         {
1223             return;
1224         }
1225         if (systemName != "system")
1226         {
1227             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1228                                        systemName);
1229             return;
1230         }
1231         // Clear the EventLog by deleting the log files
1232         std::vector<std::filesystem::path> redfishLogFiles;
1233         if (getRedfishLogFiles(redfishLogFiles))
1234         {
1235             for (const std::filesystem::path& file : redfishLogFiles)
1236             {
1237                 std::error_code ec;
1238                 std::filesystem::remove(file, ec);
1239             }
1240         }
1241 
1242         // Reload rsyslog so it knows to start new log files
1243         crow::connections::systemBus->async_method_call(
1244             [asyncResp](const boost::system::error_code& ec) {
1245             if (ec)
1246             {
1247                 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec;
1248                 messages::internalError(asyncResp->res);
1249                 return;
1250             }
1251 
1252             messages::success(asyncResp->res);
1253             },
1254             "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
1255             "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
1256             "replace");
1257         });
1258 }
1259 
1260 enum class LogParseError
1261 {
1262     success,
1263     parseFailed,
1264     messageIdNotInRegistry,
1265 };
1266 
1267 static LogParseError
1268     fillEventLogEntryJson(const std::string& logEntryID,
1269                           const std::string& logEntry,
1270                           nlohmann::json::object_t& logEntryJson)
1271 {
1272     // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
1273     // First get the Timestamp
1274     size_t space = logEntry.find_first_of(' ');
1275     if (space == std::string::npos)
1276     {
1277         return LogParseError::parseFailed;
1278     }
1279     std::string timestamp = logEntry.substr(0, space);
1280     // Then get the log contents
1281     size_t entryStart = logEntry.find_first_not_of(' ', space);
1282     if (entryStart == std::string::npos)
1283     {
1284         return LogParseError::parseFailed;
1285     }
1286     std::string_view entry(logEntry);
1287     entry.remove_prefix(entryStart);
1288     // Use split to separate the entry into its fields
1289     std::vector<std::string> logEntryFields;
1290     bmcweb::split(logEntryFields, entry, ',');
1291     // We need at least a MessageId to be valid
1292     if (logEntryFields.empty())
1293     {
1294         return LogParseError::parseFailed;
1295     }
1296     std::string& messageID = logEntryFields[0];
1297 
1298     // Get the Message from the MessageRegistry
1299     const registries::Message* message = registries::getMessage(messageID);
1300 
1301     if (message == nullptr)
1302     {
1303         BMCWEB_LOG_WARNING << "Log entry not found in registry: " << logEntry;
1304         return LogParseError::messageIdNotInRegistry;
1305     }
1306 
1307     std::string msg = message->message;
1308 
1309     // Get the MessageArgs from the log if there are any
1310     std::span<std::string> messageArgs;
1311     if (logEntryFields.size() > 1)
1312     {
1313         std::string& messageArgsStart = logEntryFields[1];
1314         // If the first string is empty, assume there are no MessageArgs
1315         std::size_t messageArgsSize = 0;
1316         if (!messageArgsStart.empty())
1317         {
1318             messageArgsSize = logEntryFields.size() - 1;
1319         }
1320 
1321         messageArgs = {&messageArgsStart, messageArgsSize};
1322 
1323         // Fill the MessageArgs into the Message
1324         int i = 0;
1325         for (const std::string& messageArg : messageArgs)
1326         {
1327             std::string argStr = "%" + std::to_string(++i);
1328             size_t argPos = msg.find(argStr);
1329             if (argPos != std::string::npos)
1330             {
1331                 msg.replace(argPos, argStr.length(), messageArg);
1332             }
1333         }
1334     }
1335 
1336     // Get the Created time from the timestamp. The log timestamp is in RFC3339
1337     // format which matches the Redfish format except for the fractional seconds
1338     // between the '.' and the '+', so just remove them.
1339     std::size_t dot = timestamp.find_first_of('.');
1340     std::size_t plus = timestamp.find_first_of('+');
1341     if (dot != std::string::npos && plus != std::string::npos)
1342     {
1343         timestamp.erase(dot, plus - dot);
1344     }
1345 
1346     // Fill in the log entry with the gathered data
1347     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1348     logEntryJson["@odata.id"] = boost::urls::format(
1349         "/redfish/v1/Systems/system/LogServices/EventLog/Entries/{}",
1350         logEntryID);
1351     logEntryJson["Name"] = "System Event Log Entry";
1352     logEntryJson["Id"] = logEntryID;
1353     logEntryJson["Message"] = std::move(msg);
1354     logEntryJson["MessageId"] = std::move(messageID);
1355     logEntryJson["MessageArgs"] = messageArgs;
1356     logEntryJson["EntryType"] = "Event";
1357     logEntryJson["Severity"] = message->messageSeverity;
1358     logEntryJson["Created"] = std::move(timestamp);
1359     return LogParseError::success;
1360 }
1361 
1362 inline void requestRoutesJournalEventLogEntryCollection(App& app)
1363 {
1364     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1365         .privileges(redfish::privileges::getLogEntryCollection)
1366         .methods(boost::beast::http::verb::get)(
1367             [&app](const crow::Request& req,
1368                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1369                    const std::string& systemName) {
1370         query_param::QueryCapabilities capabilities = {
1371             .canDelegateTop = true,
1372             .canDelegateSkip = true,
1373         };
1374         query_param::Query delegatedQuery;
1375         if (!redfish::setUpRedfishRouteWithDelegation(
1376                 app, req, asyncResp, delegatedQuery, capabilities))
1377         {
1378             return;
1379         }
1380         if (systemName != "system")
1381         {
1382             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1383                                        systemName);
1384             return;
1385         }
1386 
1387         size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
1388         size_t skip = delegatedQuery.skip.value_or(0);
1389 
1390         // Collections don't include the static data added by SubRoute
1391         // because it has a duplicate entry for members
1392         asyncResp->res.jsonValue["@odata.type"] =
1393             "#LogEntryCollection.LogEntryCollection";
1394         asyncResp->res.jsonValue["@odata.id"] =
1395             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
1396         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1397         asyncResp->res.jsonValue["Description"] =
1398             "Collection of System Event Log Entries";
1399 
1400         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1401         logEntryArray = nlohmann::json::array();
1402         // Go through the log files and create a unique ID for each
1403         // entry
1404         std::vector<std::filesystem::path> redfishLogFiles;
1405         getRedfishLogFiles(redfishLogFiles);
1406         uint64_t entryCount = 0;
1407         std::string logEntry;
1408 
1409         // Oldest logs are in the last file, so start there and loop
1410         // backwards
1411         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1412              it++)
1413         {
1414             std::ifstream logStream(*it);
1415             if (!logStream.is_open())
1416             {
1417                 continue;
1418             }
1419 
1420             // Reset the unique ID on the first entry
1421             bool firstEntry = true;
1422             while (std::getline(logStream, logEntry))
1423             {
1424                 std::string idStr;
1425                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1426                 {
1427                     continue;
1428                 }
1429                 firstEntry = false;
1430 
1431                 nlohmann::json::object_t bmcLogEntry;
1432                 LogParseError status = fillEventLogEntryJson(idStr, logEntry,
1433                                                              bmcLogEntry);
1434                 if (status == LogParseError::messageIdNotInRegistry)
1435                 {
1436                     continue;
1437                 }
1438                 if (status != LogParseError::success)
1439                 {
1440                     messages::internalError(asyncResp->res);
1441                     return;
1442                 }
1443 
1444                 entryCount++;
1445                 // Handle paging using skip (number of entries to skip from the
1446                 // start) and top (number of entries to display)
1447                 if (entryCount <= skip || entryCount > skip + top)
1448                 {
1449                     continue;
1450                 }
1451 
1452                 logEntryArray.emplace_back(std::move(bmcLogEntry));
1453             }
1454         }
1455         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1456         if (skip + top < entryCount)
1457         {
1458             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1459                 "/redfish/v1/Systems/system/LogServices/EventLog/Entries?$skip=" +
1460                 std::to_string(skip + top);
1461         }
1462         });
1463 }
1464 
1465 inline void requestRoutesJournalEventLogEntry(App& app)
1466 {
1467     BMCWEB_ROUTE(
1468         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1469         .privileges(redfish::privileges::getLogEntry)
1470         .methods(boost::beast::http::verb::get)(
1471             [&app](const crow::Request& req,
1472                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1473                    const std::string& systemName, const std::string& param) {
1474         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1475         {
1476             return;
1477         }
1478 
1479         if (systemName != "system")
1480         {
1481             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1482                                        systemName);
1483             return;
1484         }
1485 
1486         const std::string& targetID = param;
1487 
1488         // Go through the log files and check the unique ID for each
1489         // entry to find the target entry
1490         std::vector<std::filesystem::path> redfishLogFiles;
1491         getRedfishLogFiles(redfishLogFiles);
1492         std::string logEntry;
1493 
1494         // Oldest logs are in the last file, so start there and loop
1495         // backwards
1496         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1497              it++)
1498         {
1499             std::ifstream logStream(*it);
1500             if (!logStream.is_open())
1501             {
1502                 continue;
1503             }
1504 
1505             // Reset the unique ID on the first entry
1506             bool firstEntry = true;
1507             while (std::getline(logStream, logEntry))
1508             {
1509                 std::string idStr;
1510                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1511                 {
1512                     continue;
1513                 }
1514                 firstEntry = false;
1515 
1516                 if (idStr == targetID)
1517                 {
1518                     nlohmann::json::object_t bmcLogEntry;
1519                     LogParseError status =
1520                         fillEventLogEntryJson(idStr, logEntry, bmcLogEntry);
1521                     if (status != LogParseError::success)
1522                     {
1523                         messages::internalError(asyncResp->res);
1524                         return;
1525                     }
1526                     asyncResp->res.jsonValue.update(bmcLogEntry);
1527                     return;
1528                 }
1529             }
1530         }
1531         // Requested ID was not found
1532         messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
1533         });
1534 }
1535 
1536 inline void requestRoutesDBusEventLogEntryCollection(App& app)
1537 {
1538     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
1539         .privileges(redfish::privileges::getLogEntryCollection)
1540         .methods(boost::beast::http::verb::get)(
1541             [&app](const crow::Request& req,
1542                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1543                    const std::string& systemName) {
1544         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1545         {
1546             return;
1547         }
1548         if (systemName != "system")
1549         {
1550             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1551                                        systemName);
1552             return;
1553         }
1554 
1555         // Collections don't include the static data added by SubRoute
1556         // because it has a duplicate entry for members
1557         asyncResp->res.jsonValue["@odata.type"] =
1558             "#LogEntryCollection.LogEntryCollection";
1559         asyncResp->res.jsonValue["@odata.id"] =
1560             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
1561         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1562         asyncResp->res.jsonValue["Description"] =
1563             "Collection of System Event Log Entries";
1564 
1565         // DBus implementation of EventLog/Entries
1566         // Make call to Logging Service to find all log entry objects
1567         crow::connections::systemBus->async_method_call(
1568             [asyncResp](const boost::system::error_code& ec,
1569                         const dbus::utility::ManagedObjectType& resp) {
1570             if (ec)
1571             {
1572                 // TODO Handle for specific error code
1573                 BMCWEB_LOG_ERROR
1574                     << "getLogEntriesIfaceData resp_handler got error " << ec;
1575                 messages::internalError(asyncResp->res);
1576                 return;
1577             }
1578             nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
1579             entriesArray = nlohmann::json::array();
1580             for (const auto& objectPath : resp)
1581             {
1582                 const uint32_t* id = nullptr;
1583                 const uint64_t* timestamp = nullptr;
1584                 const uint64_t* updateTimestamp = nullptr;
1585                 const std::string* severity = nullptr;
1586                 const std::string* message = nullptr;
1587                 const std::string* filePath = nullptr;
1588                 const std::string* resolution = nullptr;
1589                 bool resolved = false;
1590                 const std::string* notify = nullptr;
1591 
1592                 for (const auto& interfaceMap : objectPath.second)
1593                 {
1594                     if (interfaceMap.first ==
1595                         "xyz.openbmc_project.Logging.Entry")
1596                     {
1597                         for (const auto& propertyMap : interfaceMap.second)
1598                         {
1599                             if (propertyMap.first == "Id")
1600                             {
1601                                 id = std::get_if<uint32_t>(&propertyMap.second);
1602                             }
1603                             else if (propertyMap.first == "Timestamp")
1604                             {
1605                                 timestamp =
1606                                     std::get_if<uint64_t>(&propertyMap.second);
1607                             }
1608                             else if (propertyMap.first == "UpdateTimestamp")
1609                             {
1610                                 updateTimestamp =
1611                                     std::get_if<uint64_t>(&propertyMap.second);
1612                             }
1613                             else if (propertyMap.first == "Severity")
1614                             {
1615                                 severity = std::get_if<std::string>(
1616                                     &propertyMap.second);
1617                             }
1618                             else if (propertyMap.first == "Resolution")
1619                             {
1620                                 resolution = std::get_if<std::string>(
1621                                     &propertyMap.second);
1622                             }
1623                             else if (propertyMap.first == "Message")
1624                             {
1625                                 message = std::get_if<std::string>(
1626                                     &propertyMap.second);
1627                             }
1628                             else if (propertyMap.first == "Resolved")
1629                             {
1630                                 const bool* resolveptr =
1631                                     std::get_if<bool>(&propertyMap.second);
1632                                 if (resolveptr == nullptr)
1633                                 {
1634                                     messages::internalError(asyncResp->res);
1635                                     return;
1636                                 }
1637                                 resolved = *resolveptr;
1638                             }
1639                             else if (propertyMap.first ==
1640                                      "ServiceProviderNotify")
1641                             {
1642                                 notify = std::get_if<std::string>(
1643                                     &propertyMap.second);
1644                                 if (notify == nullptr)
1645                                 {
1646                                     messages::internalError(asyncResp->res);
1647                                     return;
1648                                 }
1649                             }
1650                         }
1651                         if (id == nullptr || message == nullptr ||
1652                             severity == nullptr)
1653                         {
1654                             messages::internalError(asyncResp->res);
1655                             return;
1656                         }
1657                     }
1658                     else if (interfaceMap.first ==
1659                              "xyz.openbmc_project.Common.FilePath")
1660                     {
1661                         for (const auto& propertyMap : interfaceMap.second)
1662                         {
1663                             if (propertyMap.first == "Path")
1664                             {
1665                                 filePath = std::get_if<std::string>(
1666                                     &propertyMap.second);
1667                             }
1668                         }
1669                     }
1670                 }
1671                 // Object path without the
1672                 // xyz.openbmc_project.Logging.Entry interface, ignore
1673                 // and continue.
1674                 if (id == nullptr || message == nullptr ||
1675                     severity == nullptr || timestamp == nullptr ||
1676                     updateTimestamp == nullptr)
1677                 {
1678                     continue;
1679                 }
1680                 entriesArray.push_back({});
1681                 nlohmann::json& thisEntry = entriesArray.back();
1682                 thisEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
1683                 thisEntry["@odata.id"] = boost::urls::format(
1684                     "/redfish/v1/Systems/system/LogServices/EventLog/Entries/{}",
1685                     std::to_string(*id));
1686                 thisEntry["Name"] = "System Event Log Entry";
1687                 thisEntry["Id"] = std::to_string(*id);
1688                 thisEntry["Message"] = *message;
1689                 thisEntry["Resolved"] = resolved;
1690                 if ((resolution != nullptr) && (!(*resolution).empty()))
1691                 {
1692                     thisEntry["Resolution"] = *resolution;
1693                 }
1694                 std::optional<bool> notifyAction =
1695                     getProviderNotifyAction(*notify);
1696                 if (notifyAction)
1697                 {
1698                     thisEntry["ServiceProviderNotified"] = *notifyAction;
1699                 }
1700                 thisEntry["EntryType"] = "Event";
1701                 thisEntry["Severity"] =
1702                     translateSeverityDbusToRedfish(*severity);
1703                 thisEntry["Created"] =
1704                     redfish::time_utils::getDateTimeUintMs(*timestamp);
1705                 thisEntry["Modified"] =
1706                     redfish::time_utils::getDateTimeUintMs(*updateTimestamp);
1707                 if (filePath != nullptr)
1708                 {
1709                     thisEntry["AdditionalDataURI"] =
1710                         "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1711                         std::to_string(*id) + "/attachment";
1712                 }
1713             }
1714             std::sort(
1715                 entriesArray.begin(), entriesArray.end(),
1716                 [](const nlohmann::json& left, const nlohmann::json& right) {
1717                 return (left["Id"] <= right["Id"]);
1718                 });
1719             asyncResp->res.jsonValue["Members@odata.count"] =
1720                 entriesArray.size();
1721             },
1722             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
1723             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1724         });
1725 }
1726 
1727 inline void requestRoutesDBusEventLogEntry(App& app)
1728 {
1729     BMCWEB_ROUTE(
1730         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1731         .privileges(redfish::privileges::getLogEntry)
1732         .methods(boost::beast::http::verb::get)(
1733             [&app](const crow::Request& req,
1734                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1735                    const std::string& systemName, const std::string& param) {
1736         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1737         {
1738             return;
1739         }
1740         if (systemName != "system")
1741         {
1742             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1743                                        systemName);
1744             return;
1745         }
1746 
1747         std::string entryID = param;
1748         dbus::utility::escapePathForDbus(entryID);
1749 
1750         // DBus implementation of EventLog/Entries
1751         // Make call to Logging Service to find all log entry objects
1752         sdbusplus::asio::getAllProperties(
1753             *crow::connections::systemBus, "xyz.openbmc_project.Logging",
1754             "/xyz/openbmc_project/logging/entry/" + entryID, "",
1755             [asyncResp, entryID](const boost::system::error_code& ec,
1756                                  const dbus::utility::DBusPropertiesMap& resp) {
1757             if (ec.value() == EBADR)
1758             {
1759                 messages::resourceNotFound(asyncResp->res, "EventLogEntry",
1760                                            entryID);
1761                 return;
1762             }
1763             if (ec)
1764             {
1765                 BMCWEB_LOG_ERROR
1766                     << "EventLogEntry (DBus) resp_handler got error " << ec;
1767                 messages::internalError(asyncResp->res);
1768                 return;
1769             }
1770             const uint32_t* id = nullptr;
1771             const uint64_t* timestamp = nullptr;
1772             const uint64_t* updateTimestamp = nullptr;
1773             const std::string* severity = nullptr;
1774             const std::string* message = nullptr;
1775             const std::string* filePath = nullptr;
1776             const std::string* resolution = nullptr;
1777             bool resolved = false;
1778             const std::string* notify = nullptr;
1779 
1780             const bool success = sdbusplus::unpackPropertiesNoThrow(
1781                 dbus_utils::UnpackErrorPrinter(), resp, "Id", id, "Timestamp",
1782                 timestamp, "UpdateTimestamp", updateTimestamp, "Severity",
1783                 severity, "Message", message, "Resolved", resolved,
1784                 "Resolution", resolution, "Path", filePath,
1785                 "ServiceProviderNotify", notify);
1786 
1787             if (!success)
1788             {
1789                 messages::internalError(asyncResp->res);
1790                 return;
1791             }
1792 
1793             if (id == nullptr || message == nullptr || severity == nullptr ||
1794                 timestamp == nullptr || updateTimestamp == nullptr ||
1795                 notify == nullptr)
1796             {
1797                 messages::internalError(asyncResp->res);
1798                 return;
1799             }
1800 
1801             asyncResp->res.jsonValue["@odata.type"] =
1802                 "#LogEntry.v1_9_0.LogEntry";
1803             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
1804                 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/{}",
1805                 std::to_string(*id));
1806             asyncResp->res.jsonValue["Name"] = "System Event Log Entry";
1807             asyncResp->res.jsonValue["Id"] = std::to_string(*id);
1808             asyncResp->res.jsonValue["Message"] = *message;
1809             asyncResp->res.jsonValue["Resolved"] = resolved;
1810             std::optional<bool> notifyAction = getProviderNotifyAction(*notify);
1811             if (notifyAction)
1812             {
1813                 asyncResp->res.jsonValue["ServiceProviderNotified"] =
1814                     *notifyAction;
1815             }
1816             if ((resolution != nullptr) && (!(*resolution).empty()))
1817             {
1818                 asyncResp->res.jsonValue["Resolution"] = *resolution;
1819             }
1820             asyncResp->res.jsonValue["EntryType"] = "Event";
1821             asyncResp->res.jsonValue["Severity"] =
1822                 translateSeverityDbusToRedfish(*severity);
1823             asyncResp->res.jsonValue["Created"] =
1824                 redfish::time_utils::getDateTimeUintMs(*timestamp);
1825             asyncResp->res.jsonValue["Modified"] =
1826                 redfish::time_utils::getDateTimeUintMs(*updateTimestamp);
1827             if (filePath != nullptr)
1828             {
1829                 asyncResp->res.jsonValue["AdditionalDataURI"] =
1830                     "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1831                     std::to_string(*id) + "/attachment";
1832             }
1833             });
1834         });
1835 
1836     BMCWEB_ROUTE(
1837         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1838         .privileges(redfish::privileges::patchLogEntry)
1839         .methods(boost::beast::http::verb::patch)(
1840             [&app](const crow::Request& req,
1841                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1842                    const std::string& systemName, const std::string& entryId) {
1843         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1844         {
1845             return;
1846         }
1847         if (systemName != "system")
1848         {
1849             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1850                                        systemName);
1851             return;
1852         }
1853         std::optional<bool> resolved;
1854 
1855         if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved",
1856                                       resolved))
1857         {
1858             return;
1859         }
1860         BMCWEB_LOG_DEBUG << "Set Resolved";
1861 
1862         crow::connections::systemBus->async_method_call(
1863             [asyncResp, entryId](const boost::system::error_code& ec) {
1864             if (ec)
1865             {
1866                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
1867                 messages::internalError(asyncResp->res);
1868                 return;
1869             }
1870             },
1871             "xyz.openbmc_project.Logging",
1872             "/xyz/openbmc_project/logging/entry/" + entryId,
1873             "org.freedesktop.DBus.Properties", "Set",
1874             "xyz.openbmc_project.Logging.Entry", "Resolved",
1875             dbus::utility::DbusVariantType(*resolved));
1876         });
1877 
1878     BMCWEB_ROUTE(
1879         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
1880         .privileges(redfish::privileges::deleteLogEntry)
1881 
1882         .methods(boost::beast::http::verb::delete_)(
1883             [&app](const crow::Request& req,
1884                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1885                    const std::string& systemName, const std::string& param) {
1886         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1887         {
1888             return;
1889         }
1890         if (systemName != "system")
1891         {
1892             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1893                                        systemName);
1894             return;
1895         }
1896         BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1897 
1898         std::string entryID = param;
1899 
1900         dbus::utility::escapePathForDbus(entryID);
1901 
1902         // Process response from Logging service.
1903         auto respHandler =
1904             [asyncResp, entryID](const boost::system::error_code& ec) {
1905             BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1906             if (ec)
1907             {
1908                 if (ec.value() == EBADR)
1909                 {
1910                     messages::resourceNotFound(asyncResp->res, "LogEntry",
1911                                                entryID);
1912                     return;
1913                 }
1914                 // TODO Handle for specific error code
1915                 BMCWEB_LOG_ERROR
1916                     << "EventLogEntry (DBus) doDelete respHandler got error "
1917                     << ec;
1918                 asyncResp->res.result(
1919                     boost::beast::http::status::internal_server_error);
1920                 return;
1921             }
1922 
1923             asyncResp->res.result(boost::beast::http::status::ok);
1924         };
1925 
1926         // Make call to Logging service to request Delete Log
1927         crow::connections::systemBus->async_method_call(
1928             respHandler, "xyz.openbmc_project.Logging",
1929             "/xyz/openbmc_project/logging/entry/" + entryID,
1930             "xyz.openbmc_project.Object.Delete", "Delete");
1931         });
1932 }
1933 
1934 inline void requestRoutesDBusEventLogEntryDownload(App& app)
1935 {
1936     BMCWEB_ROUTE(
1937         app,
1938         "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment")
1939         .privileges(redfish::privileges::getLogEntry)
1940         .methods(boost::beast::http::verb::get)(
1941             [&app](const crow::Request& req,
1942                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1943                    const std::string& systemName, const std::string& param) {
1944         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1945         {
1946             return;
1947         }
1948         if (!http_helpers::isContentTypeAllowed(
1949                 req.getHeaderValue("Accept"),
1950                 http_helpers::ContentType::OctetStream, true))
1951         {
1952             asyncResp->res.result(boost::beast::http::status::bad_request);
1953             return;
1954         }
1955         if (systemName != "system")
1956         {
1957             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
1958                                        systemName);
1959             return;
1960         }
1961 
1962         std::string entryID = param;
1963         dbus::utility::escapePathForDbus(entryID);
1964 
1965         crow::connections::systemBus->async_method_call(
1966             [asyncResp, entryID](const boost::system::error_code& ec,
1967                                  const sdbusplus::message::unix_fd& unixfd) {
1968             if (ec.value() == EBADR)
1969             {
1970                 messages::resourceNotFound(asyncResp->res, "EventLogAttachment",
1971                                            entryID);
1972                 return;
1973             }
1974             if (ec)
1975             {
1976                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
1977                 messages::internalError(asyncResp->res);
1978                 return;
1979             }
1980 
1981             int fd = -1;
1982             fd = dup(unixfd);
1983             if (fd == -1)
1984             {
1985                 messages::internalError(asyncResp->res);
1986                 return;
1987             }
1988 
1989             long long int size = lseek(fd, 0, SEEK_END);
1990             if (size == -1)
1991             {
1992                 messages::internalError(asyncResp->res);
1993                 return;
1994             }
1995 
1996             // Arbitrary max size of 64kb
1997             constexpr int maxFileSize = 65536;
1998             if (size > maxFileSize)
1999             {
2000                 BMCWEB_LOG_ERROR << "File size exceeds maximum allowed size of "
2001                                  << maxFileSize;
2002                 messages::internalError(asyncResp->res);
2003                 return;
2004             }
2005             std::vector<char> data(static_cast<size_t>(size));
2006             long long int rc = lseek(fd, 0, SEEK_SET);
2007             if (rc == -1)
2008             {
2009                 messages::internalError(asyncResp->res);
2010                 return;
2011             }
2012             rc = read(fd, data.data(), data.size());
2013             if ((rc == -1) || (rc != size))
2014             {
2015                 messages::internalError(asyncResp->res);
2016                 return;
2017             }
2018             close(fd);
2019 
2020             std::string_view strData(data.data(), data.size());
2021             std::string output = crow::utility::base64encode(strData);
2022 
2023             asyncResp->res.addHeader(boost::beast::http::field::content_type,
2024                                      "application/octet-stream");
2025             asyncResp->res.addHeader(
2026                 boost::beast::http::field::content_transfer_encoding, "Base64");
2027             asyncResp->res.body() = std::move(output);
2028             },
2029             "xyz.openbmc_project.Logging",
2030             "/xyz/openbmc_project/logging/entry/" + entryID,
2031             "xyz.openbmc_project.Logging.Entry", "GetEntry");
2032         });
2033 }
2034 
2035 constexpr const char* hostLoggerFolderPath = "/var/log/console";
2036 
2037 inline bool
2038     getHostLoggerFiles(const std::string& hostLoggerFilePath,
2039                        std::vector<std::filesystem::path>& hostLoggerFiles)
2040 {
2041     std::error_code ec;
2042     std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
2043     if (ec)
2044     {
2045         BMCWEB_LOG_ERROR << ec.message();
2046         return false;
2047     }
2048     for (const std::filesystem::directory_entry& it : logPath)
2049     {
2050         std::string filename = it.path().filename();
2051         // Prefix of each log files is "log". Find the file and save the
2052         // path
2053         if (filename.starts_with("log"))
2054         {
2055             hostLoggerFiles.emplace_back(it.path());
2056         }
2057     }
2058     // As the log files rotate, they are appended with a ".#" that is higher for
2059     // the older logs. Since we start from oldest logs, sort the name in
2060     // descending order.
2061     std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
2062               AlphanumLess<std::string>());
2063 
2064     return true;
2065 }
2066 
2067 inline bool getHostLoggerEntries(
2068     const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip,
2069     uint64_t top, std::vector<std::string>& logEntries, size_t& logCount)
2070 {
2071     GzFileReader logFile;
2072 
2073     // Go though all log files and expose host logs.
2074     for (const std::filesystem::path& it : hostLoggerFiles)
2075     {
2076         if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
2077         {
2078             BMCWEB_LOG_ERROR << "fail to expose host logs";
2079             return false;
2080         }
2081     }
2082     // Get lastMessage from constructor by getter
2083     std::string lastMessage = logFile.getLastMessage();
2084     if (!lastMessage.empty())
2085     {
2086         logCount++;
2087         if (logCount > skip && logCount <= (skip + top))
2088         {
2089             logEntries.push_back(lastMessage);
2090         }
2091     }
2092     return true;
2093 }
2094 
2095 inline void fillHostLoggerEntryJson(const std::string& logEntryID,
2096                                     const std::string& msg,
2097                                     nlohmann::json::object_t& logEntryJson)
2098 {
2099     // Fill in the log entry with the gathered data.
2100     logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
2101     logEntryJson["@odata.id"] = boost::urls::format(
2102         "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/{}",
2103         logEntryID);
2104     logEntryJson["Name"] = "Host Logger Entry";
2105     logEntryJson["Id"] = logEntryID;
2106     logEntryJson["Message"] = msg;
2107     logEntryJson["EntryType"] = "Oem";
2108     logEntryJson["Severity"] = "OK";
2109     logEntryJson["OemRecordFormat"] = "Host Logger Entry";
2110 }
2111 
2112 inline void requestRoutesSystemHostLogger(App& app)
2113 {
2114     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/")
2115         .privileges(redfish::privileges::getLogService)
2116         .methods(boost::beast::http::verb::get)(
2117             [&app](const crow::Request& req,
2118                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2119                    const std::string& systemName) {
2120         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2121         {
2122             return;
2123         }
2124         if (systemName != "system")
2125         {
2126             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2127                                        systemName);
2128             return;
2129         }
2130         asyncResp->res.jsonValue["@odata.id"] =
2131             "/redfish/v1/Systems/system/LogServices/HostLogger";
2132         asyncResp->res.jsonValue["@odata.type"] =
2133             "#LogService.v1_1_0.LogService";
2134         asyncResp->res.jsonValue["Name"] = "Host Logger Service";
2135         asyncResp->res.jsonValue["Description"] = "Host Logger Service";
2136         asyncResp->res.jsonValue["Id"] = "HostLogger";
2137         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
2138             "/redfish/v1/Systems/system/LogServices/HostLogger/Entries";
2139         });
2140 }
2141 
2142 inline void requestRoutesSystemHostLoggerCollection(App& app)
2143 {
2144     BMCWEB_ROUTE(app,
2145                  "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/")
2146         .privileges(redfish::privileges::getLogEntry)
2147         .methods(boost::beast::http::verb::get)(
2148             [&app](const crow::Request& req,
2149                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2150                    const std::string& systemName) {
2151         query_param::QueryCapabilities capabilities = {
2152             .canDelegateTop = true,
2153             .canDelegateSkip = true,
2154         };
2155         query_param::Query delegatedQuery;
2156         if (!redfish::setUpRedfishRouteWithDelegation(
2157                 app, req, asyncResp, delegatedQuery, capabilities))
2158         {
2159             return;
2160         }
2161         if (systemName != "system")
2162         {
2163             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2164                                        systemName);
2165             return;
2166         }
2167         asyncResp->res.jsonValue["@odata.id"] =
2168             "/redfish/v1/Systems/system/LogServices/HostLogger/Entries";
2169         asyncResp->res.jsonValue["@odata.type"] =
2170             "#LogEntryCollection.LogEntryCollection";
2171         asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
2172         asyncResp->res.jsonValue["Description"] =
2173             "Collection of HostLogger Entries";
2174         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
2175         logEntryArray = nlohmann::json::array();
2176         asyncResp->res.jsonValue["Members@odata.count"] = 0;
2177 
2178         std::vector<std::filesystem::path> hostLoggerFiles;
2179         if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
2180         {
2181             BMCWEB_LOG_ERROR << "fail to get host log file path";
2182             return;
2183         }
2184         // If we weren't provided top and skip limits, use the defaults.
2185         size_t skip = delegatedQuery.skip.value_or(0);
2186         size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
2187         size_t logCount = 0;
2188         // This vector only store the entries we want to expose that
2189         // control by skip and top.
2190         std::vector<std::string> logEntries;
2191         if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries,
2192                                   logCount))
2193         {
2194             messages::internalError(asyncResp->res);
2195             return;
2196         }
2197         // If vector is empty, that means skip value larger than total
2198         // log count
2199         if (logEntries.empty())
2200         {
2201             asyncResp->res.jsonValue["Members@odata.count"] = logCount;
2202             return;
2203         }
2204         if (!logEntries.empty())
2205         {
2206             for (size_t i = 0; i < logEntries.size(); i++)
2207             {
2208                 nlohmann::json::object_t hostLogEntry;
2209                 fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i],
2210                                         hostLogEntry);
2211                 logEntryArray.emplace_back(std::move(hostLogEntry));
2212             }
2213 
2214             asyncResp->res.jsonValue["Members@odata.count"] = logCount;
2215             if (skip + top < logCount)
2216             {
2217                 asyncResp->res.jsonValue["Members@odata.nextLink"] =
2218                     "/redfish/v1/Systems/system/LogServices/HostLogger/Entries?$skip=" +
2219                     std::to_string(skip + top);
2220             }
2221         }
2222         });
2223 }
2224 
2225 inline void requestRoutesSystemHostLoggerLogEntry(App& app)
2226 {
2227     BMCWEB_ROUTE(
2228         app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/")
2229         .privileges(redfish::privileges::getLogEntry)
2230         .methods(boost::beast::http::verb::get)(
2231             [&app](const crow::Request& req,
2232                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2233                    const std::string& systemName, const std::string& param) {
2234         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2235         {
2236             return;
2237         }
2238         if (systemName != "system")
2239         {
2240             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
2241                                        systemName);
2242             return;
2243         }
2244         const std::string& targetID = param;
2245 
2246         uint64_t idInt = 0;
2247 
2248         auto [ptr, ec] = std::from_chars(&*targetID.begin(), &*targetID.end(),
2249                                          idInt);
2250         if (ec == std::errc::invalid_argument ||
2251             ec == std::errc::result_out_of_range)
2252         {
2253             messages::resourceNotFound(asyncResp->res, "LogEntry", param);
2254             return;
2255         }
2256 
2257         std::vector<std::filesystem::path> hostLoggerFiles;
2258         if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
2259         {
2260             BMCWEB_LOG_ERROR << "fail to get host log file path";
2261             return;
2262         }
2263 
2264         size_t logCount = 0;
2265         size_t top = 1;
2266         std::vector<std::string> logEntries;
2267         // We can get specific entry by skip and top. For example, if we
2268         // want to get nth entry, we can set skip = n-1 and top = 1 to
2269         // get that entry
2270         if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
2271                                   logCount))
2272         {
2273             messages::internalError(asyncResp->res);
2274             return;
2275         }
2276 
2277         if (!logEntries.empty())
2278         {
2279             nlohmann::json::object_t hostLogEntry;
2280             fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry);
2281             asyncResp->res.jsonValue.update(hostLogEntry);
2282             return;
2283         }
2284 
2285         // Requested ID was not found
2286         messages::resourceNotFound(asyncResp->res, "LogEntry", param);
2287         });
2288 }
2289 
2290 inline void handleBMCLogServicesCollectionGet(
2291     crow::App& app, const crow::Request& req,
2292     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2293 {
2294     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2295     {
2296         return;
2297     }
2298     // Collections don't include the static data added by SubRoute
2299     // because it has a duplicate entry for members
2300     asyncResp->res.jsonValue["@odata.type"] =
2301         "#LogServiceCollection.LogServiceCollection";
2302     asyncResp->res.jsonValue["@odata.id"] =
2303         "/redfish/v1/Managers/bmc/LogServices";
2304     asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
2305     asyncResp->res.jsonValue["Description"] =
2306         "Collection of LogServices for this Manager";
2307     nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
2308     logServiceArray = nlohmann::json::array();
2309 
2310 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
2311     nlohmann::json::object_t journal;
2312     journal["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal";
2313     logServiceArray.emplace_back(std::move(journal));
2314 #endif
2315 
2316     asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
2317 
2318 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
2319     constexpr std::array<std::string_view, 1> interfaces = {
2320         "xyz.openbmc_project.Collection.DeleteAll"};
2321     dbus::utility::getSubTreePaths(
2322         "/xyz/openbmc_project/dump", 0, interfaces,
2323         [asyncResp](
2324             const boost::system::error_code& ec,
2325             const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
2326         if (ec)
2327         {
2328             BMCWEB_LOG_ERROR
2329                 << "handleBMCLogServicesCollectionGet respHandler got error "
2330                 << ec;
2331             // Assume that getting an error simply means there are no dump
2332             // LogServices. Return without adding any error response.
2333             return;
2334         }
2335 
2336         nlohmann::json& logServiceArrayLocal =
2337             asyncResp->res.jsonValue["Members"];
2338 
2339         for (const std::string& path : subTreePaths)
2340         {
2341             if (path == "/xyz/openbmc_project/dump/bmc")
2342             {
2343                 nlohmann::json::object_t member;
2344                 member["@odata.id"] =
2345                     "/redfish/v1/Managers/bmc/LogServices/Dump";
2346                 logServiceArrayLocal.emplace_back(std::move(member));
2347             }
2348             else if (path == "/xyz/openbmc_project/dump/faultlog")
2349             {
2350                 nlohmann::json::object_t member;
2351                 member["@odata.id"] =
2352                     "/redfish/v1/Managers/bmc/LogServices/FaultLog";
2353                 logServiceArrayLocal.emplace_back(std::move(member));
2354             }
2355         }
2356 
2357         asyncResp->res.jsonValue["Members@odata.count"] =
2358             logServiceArrayLocal.size();
2359         });
2360 #endif
2361 }
2362 
2363 inline void requestRoutesBMCLogServiceCollection(App& app)
2364 {
2365     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/")
2366         .privileges(redfish::privileges::getLogServiceCollection)
2367         .methods(boost::beast::http::verb::get)(
2368             std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app)));
2369 }
2370 
2371 inline void requestRoutesBMCJournalLogService(App& app)
2372 {
2373     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
2374         .privileges(redfish::privileges::getLogService)
2375         .methods(boost::beast::http::verb::get)(
2376             [&app](const crow::Request& req,
2377                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2378         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2379         {
2380             return;
2381         }
2382         asyncResp->res.jsonValue["@odata.type"] =
2383             "#LogService.v1_1_0.LogService";
2384         asyncResp->res.jsonValue["@odata.id"] =
2385             "/redfish/v1/Managers/bmc/LogServices/Journal";
2386         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
2387         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
2388         asyncResp->res.jsonValue["Id"] = "Journal";
2389         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2390 
2391         std::pair<std::string, std::string> redfishDateTimeOffset =
2392             redfish::time_utils::getDateTimeOffsetNow();
2393         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2394         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2395             redfishDateTimeOffset.second;
2396 
2397         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
2398             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
2399         });
2400 }
2401 
2402 static int
2403     fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID,
2404                                sd_journal* journal,
2405                                nlohmann::json::object_t& bmcJournalLogEntryJson)
2406 {
2407     // Get the Log Entry contents
2408     int ret = 0;
2409 
2410     std::string message;
2411     std::string_view syslogID;
2412     ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID);
2413     if (ret < 0)
2414     {
2415         BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: "
2416                          << strerror(-ret);
2417     }
2418     if (!syslogID.empty())
2419     {
2420         message += std::string(syslogID) + ": ";
2421     }
2422 
2423     std::string_view msg;
2424     ret = getJournalMetadata(journal, "MESSAGE", msg);
2425     if (ret < 0)
2426     {
2427         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
2428         return 1;
2429     }
2430     message += std::string(msg);
2431 
2432     // Get the severity from the PRIORITY field
2433     long int severity = 8; // Default to an invalid priority
2434     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
2435     if (ret < 0)
2436     {
2437         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
2438     }
2439 
2440     // Get the Created time from the timestamp
2441     std::string entryTimeStr;
2442     if (!getEntryTimestamp(journal, entryTimeStr))
2443     {
2444         return 1;
2445     }
2446 
2447     // Fill in the log entry with the gathered data
2448     bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
2449     bmcJournalLogEntryJson["@odata.id"] = boost::urls::format(
2450         "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/{}",
2451         bmcJournalLogEntryID);
2452     bmcJournalLogEntryJson["Name"] = "BMC Journal Entry";
2453     bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID;
2454     bmcJournalLogEntryJson["Message"] = std::move(message);
2455     bmcJournalLogEntryJson["EntryType"] = "Oem";
2456     bmcJournalLogEntryJson["Severity"] = severity <= 2   ? "Critical"
2457                                          : severity <= 4 ? "Warning"
2458                                                          : "OK";
2459     bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry";
2460     bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr);
2461     return 0;
2462 }
2463 
2464 inline void requestRoutesBMCJournalLogEntryCollection(App& app)
2465 {
2466     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
2467         .privileges(redfish::privileges::getLogEntryCollection)
2468         .methods(boost::beast::http::verb::get)(
2469             [&app](const crow::Request& req,
2470                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2471         query_param::QueryCapabilities capabilities = {
2472             .canDelegateTop = true,
2473             .canDelegateSkip = true,
2474         };
2475         query_param::Query delegatedQuery;
2476         if (!redfish::setUpRedfishRouteWithDelegation(
2477                 app, req, asyncResp, delegatedQuery, capabilities))
2478         {
2479             return;
2480         }
2481 
2482         size_t skip = delegatedQuery.skip.value_or(0);
2483         size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
2484 
2485         // Collections don't include the static data added by SubRoute
2486         // because it has a duplicate entry for members
2487         asyncResp->res.jsonValue["@odata.type"] =
2488             "#LogEntryCollection.LogEntryCollection";
2489         asyncResp->res.jsonValue["@odata.id"] =
2490             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
2491         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
2492         asyncResp->res.jsonValue["Description"] =
2493             "Collection of BMC Journal Entries";
2494         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
2495         logEntryArray = nlohmann::json::array();
2496 
2497         // Go through the journal and use the timestamp to create a
2498         // unique ID for each entry
2499         sd_journal* journalTmp = nullptr;
2500         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
2501         if (ret < 0)
2502         {
2503             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
2504             messages::internalError(asyncResp->res);
2505             return;
2506         }
2507         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
2508             journalTmp, sd_journal_close);
2509         journalTmp = nullptr;
2510         uint64_t entryCount = 0;
2511         // Reset the unique ID on the first entry
2512         bool firstEntry = true;
2513         SD_JOURNAL_FOREACH(journal.get())
2514         {
2515             entryCount++;
2516             // Handle paging using skip (number of entries to skip from
2517             // the start) and top (number of entries to display)
2518             if (entryCount <= skip || entryCount > skip + top)
2519             {
2520                 continue;
2521             }
2522 
2523             std::string idStr;
2524             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
2525             {
2526                 continue;
2527             }
2528             firstEntry = false;
2529 
2530             nlohmann::json::object_t bmcJournalLogEntry;
2531             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
2532                                            bmcJournalLogEntry) != 0)
2533             {
2534                 messages::internalError(asyncResp->res);
2535                 return;
2536             }
2537             logEntryArray.emplace_back(std::move(bmcJournalLogEntry));
2538         }
2539         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
2540         if (skip + top < entryCount)
2541         {
2542             asyncResp->res.jsonValue["Members@odata.nextLink"] =
2543                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
2544                 std::to_string(skip + top);
2545         }
2546         });
2547 }
2548 
2549 inline void requestRoutesBMCJournalLogEntry(App& app)
2550 {
2551     BMCWEB_ROUTE(app,
2552                  "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/")
2553         .privileges(redfish::privileges::getLogEntry)
2554         .methods(boost::beast::http::verb::get)(
2555             [&app](const crow::Request& req,
2556                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2557                    const std::string& entryID) {
2558         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2559         {
2560             return;
2561         }
2562         // Convert the unique ID back to a timestamp to find the entry
2563         uint64_t ts = 0;
2564         uint64_t index = 0;
2565         if (!getTimestampFromID(asyncResp, entryID, ts, index))
2566         {
2567             return;
2568         }
2569 
2570         sd_journal* journalTmp = nullptr;
2571         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
2572         if (ret < 0)
2573         {
2574             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
2575             messages::internalError(asyncResp->res);
2576             return;
2577         }
2578         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
2579             journalTmp, sd_journal_close);
2580         journalTmp = nullptr;
2581         // Go to the timestamp in the log and move to the entry at the
2582         // index tracking the unique ID
2583         std::string idStr;
2584         bool firstEntry = true;
2585         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
2586         if (ret < 0)
2587         {
2588             BMCWEB_LOG_ERROR << "failed to seek to an entry in journal"
2589                              << strerror(-ret);
2590             messages::internalError(asyncResp->res);
2591             return;
2592         }
2593         for (uint64_t i = 0; i <= index; i++)
2594         {
2595             sd_journal_next(journal.get());
2596             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
2597             {
2598                 messages::internalError(asyncResp->res);
2599                 return;
2600             }
2601             firstEntry = false;
2602         }
2603         // Confirm that the entry ID matches what was requested
2604         if (idStr != entryID)
2605         {
2606             messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
2607             return;
2608         }
2609 
2610         nlohmann::json::object_t bmcJournalLogEntry;
2611         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
2612                                        bmcJournalLogEntry) != 0)
2613         {
2614             messages::internalError(asyncResp->res);
2615             return;
2616         }
2617         asyncResp->res.jsonValue.update(bmcJournalLogEntry);
2618         });
2619 }
2620 
2621 inline void
2622     getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2623                        const std::string& dumpType)
2624 {
2625     std::string dumpPath;
2626     std::string overWritePolicy;
2627     bool collectDiagnosticDataSupported = false;
2628 
2629     if (dumpType == "BMC")
2630     {
2631         dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump";
2632         overWritePolicy = "WrapsWhenFull";
2633         collectDiagnosticDataSupported = true;
2634     }
2635     else if (dumpType == "FaultLog")
2636     {
2637         dumpPath = "/redfish/v1/Managers/bmc/LogServices/FaultLog";
2638         overWritePolicy = "Unknown";
2639         collectDiagnosticDataSupported = false;
2640     }
2641     else if (dumpType == "System")
2642     {
2643         dumpPath = "/redfish/v1/Systems/system/LogServices/Dump";
2644         overWritePolicy = "WrapsWhenFull";
2645         collectDiagnosticDataSupported = true;
2646     }
2647     else
2648     {
2649         BMCWEB_LOG_ERROR << "getDumpServiceInfo() invalid dump type: "
2650                          << dumpType;
2651         messages::internalError(asyncResp->res);
2652         return;
2653     }
2654 
2655     asyncResp->res.jsonValue["@odata.id"] = dumpPath;
2656     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
2657     asyncResp->res.jsonValue["Name"] = "Dump LogService";
2658     asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService";
2659     asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
2660     asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy);
2661 
2662     std::pair<std::string, std::string> redfishDateTimeOffset =
2663         redfish::time_utils::getDateTimeOffsetNow();
2664     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2665     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2666         redfishDateTimeOffset.second;
2667 
2668     asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries";
2669 
2670     if (collectDiagnosticDataSupported)
2671     {
2672         asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2673                                 ["target"] =
2674             dumpPath + "/Actions/LogService.CollectDiagnosticData";
2675     }
2676 
2677     constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface};
2678     dbus::utility::getSubTreePaths(
2679         "/xyz/openbmc_project/dump", 0, interfaces,
2680         [asyncResp, dumpType, dumpPath](
2681             const boost::system::error_code& ec,
2682             const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
2683         if (ec)
2684         {
2685             BMCWEB_LOG_ERROR << "getDumpServiceInfo respHandler got error "
2686                              << ec;
2687             // Assume that getting an error simply means there are no dump
2688             // LogServices. Return without adding any error response.
2689             return;
2690         }
2691 
2692         const std::string dbusDumpPath =
2693             "/xyz/openbmc_project/dump/" +
2694             boost::algorithm::to_lower_copy(dumpType);
2695 
2696         for (const std::string& path : subTreePaths)
2697         {
2698             if (path == dbusDumpPath)
2699             {
2700                 asyncResp->res
2701                     .jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
2702                     dumpPath + "/Actions/LogService.ClearLog";
2703                 break;
2704             }
2705         }
2706         });
2707 }
2708 
2709 inline void handleLogServicesDumpServiceGet(
2710     crow::App& app, const std::string& dumpType, const crow::Request& req,
2711     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2712 {
2713     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2714     {
2715         return;
2716     }
2717     getDumpServiceInfo(asyncResp, dumpType);
2718 }
2719 
2720 inline void handleLogServicesDumpServiceComputerSystemGet(
2721     crow::App& app, const crow::Request& req,
2722     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2723     const std::string& chassisId)
2724 {
2725     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2726     {
2727         return;
2728     }
2729     if (chassisId != "system")
2730     {
2731         messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2732         return;
2733     }
2734     getDumpServiceInfo(asyncResp, "System");
2735 }
2736 
2737 inline void handleLogServicesDumpEntriesCollectionGet(
2738     crow::App& app, const std::string& dumpType, const crow::Request& req,
2739     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2740 {
2741     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2742     {
2743         return;
2744     }
2745     getDumpEntryCollection(asyncResp, dumpType);
2746 }
2747 
2748 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet(
2749     crow::App& app, const crow::Request& req,
2750     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2751     const std::string& chassisId)
2752 {
2753     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2754     {
2755         return;
2756     }
2757     if (chassisId != "system")
2758     {
2759         messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2760         return;
2761     }
2762     getDumpEntryCollection(asyncResp, "System");
2763 }
2764 
2765 inline void handleLogServicesDumpEntryGet(
2766     crow::App& app, const std::string& dumpType, const crow::Request& req,
2767     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2768     const std::string& dumpId)
2769 {
2770     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2771     {
2772         return;
2773     }
2774     getDumpEntryById(asyncResp, dumpId, dumpType);
2775 }
2776 inline void handleLogServicesDumpEntryComputerSystemGet(
2777     crow::App& app, const crow::Request& req,
2778     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2779     const std::string& chassisId, const std::string& dumpId)
2780 {
2781     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2782     {
2783         return;
2784     }
2785     if (chassisId != "system")
2786     {
2787         messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2788         return;
2789     }
2790     getDumpEntryById(asyncResp, dumpId, "System");
2791 }
2792 
2793 inline void handleLogServicesDumpEntryDelete(
2794     crow::App& app, const std::string& dumpType, const crow::Request& req,
2795     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2796     const std::string& dumpId)
2797 {
2798     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2799     {
2800         return;
2801     }
2802     deleteDumpEntry(asyncResp, dumpId, dumpType);
2803 }
2804 
2805 inline void handleLogServicesDumpEntryComputerSystemDelete(
2806     crow::App& app, const crow::Request& req,
2807     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2808     const std::string& chassisId, const std::string& dumpId)
2809 {
2810     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2811     {
2812         return;
2813     }
2814     if (chassisId != "system")
2815     {
2816         messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2817         return;
2818     }
2819     deleteDumpEntry(asyncResp, dumpId, "System");
2820 }
2821 
2822 inline void handleLogServicesDumpCollectDiagnosticDataPost(
2823     crow::App& app, const std::string& dumpType, const crow::Request& req,
2824     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2825 {
2826     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2827     {
2828         return;
2829     }
2830     createDump(asyncResp, req, dumpType);
2831 }
2832 
2833 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost(
2834     crow::App& app, const crow::Request& req,
2835     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2836     const std::string& chassisId)
2837 {
2838     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2839     {
2840         return;
2841     }
2842     if (chassisId != "system")
2843     {
2844         messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2845         return;
2846     }
2847     createDump(asyncResp, req, "System");
2848 }
2849 
2850 inline void handleLogServicesDumpClearLogPost(
2851     crow::App& app, const std::string& dumpType, const crow::Request& req,
2852     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2853 {
2854     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2855     {
2856         return;
2857     }
2858     clearDump(asyncResp, dumpType);
2859 }
2860 
2861 inline void handleLogServicesDumpClearLogComputerSystemPost(
2862     crow::App& app, const crow::Request& req,
2863     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2864     const std::string& chassisId)
2865 {
2866     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2867     {
2868         return;
2869     }
2870     if (chassisId != "system")
2871     {
2872         messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId);
2873         return;
2874     }
2875     clearDump(asyncResp, "System");
2876 }
2877 
2878 inline void requestRoutesBMCDumpService(App& app)
2879 {
2880     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/")
2881         .privileges(redfish::privileges::getLogService)
2882         .methods(boost::beast::http::verb::get)(std::bind_front(
2883             handleLogServicesDumpServiceGet, std::ref(app), "BMC"));
2884 }
2885 
2886 inline void requestRoutesBMCDumpEntryCollection(App& app)
2887 {
2888     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/")
2889         .privileges(redfish::privileges::getLogEntryCollection)
2890         .methods(boost::beast::http::verb::get)(std::bind_front(
2891             handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC"));
2892 }
2893 
2894 inline void requestRoutesBMCDumpEntry(App& app)
2895 {
2896     BMCWEB_ROUTE(app,
2897                  "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
2898         .privileges(redfish::privileges::getLogEntry)
2899         .methods(boost::beast::http::verb::get)(std::bind_front(
2900             handleLogServicesDumpEntryGet, std::ref(app), "BMC"));
2901 
2902     BMCWEB_ROUTE(app,
2903                  "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
2904         .privileges(redfish::privileges::deleteLogEntry)
2905         .methods(boost::beast::http::verb::delete_)(std::bind_front(
2906             handleLogServicesDumpEntryDelete, std::ref(app), "BMC"));
2907 }
2908 
2909 inline void requestRoutesBMCDumpCreate(App& app)
2910 {
2911     BMCWEB_ROUTE(
2912         app,
2913         "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2914         .privileges(redfish::privileges::postLogService)
2915         .methods(boost::beast::http::verb::post)(
2916             std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost,
2917                             std::ref(app), "BMC"));
2918 }
2919 
2920 inline void requestRoutesBMCDumpClear(App& app)
2921 {
2922     BMCWEB_ROUTE(
2923         app,
2924         "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/")
2925         .privileges(redfish::privileges::postLogService)
2926         .methods(boost::beast::http::verb::post)(std::bind_front(
2927             handleLogServicesDumpClearLogPost, std::ref(app), "BMC"));
2928 }
2929 
2930 inline void requestRoutesFaultLogDumpService(App& app)
2931 {
2932     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/")
2933         .privileges(redfish::privileges::getLogService)
2934         .methods(boost::beast::http::verb::get)(std::bind_front(
2935             handleLogServicesDumpServiceGet, std::ref(app), "FaultLog"));
2936 }
2937 
2938 inline void requestRoutesFaultLogDumpEntryCollection(App& app)
2939 {
2940     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/")
2941         .privileges(redfish::privileges::getLogEntryCollection)
2942         .methods(boost::beast::http::verb::get)(
2943             std::bind_front(handleLogServicesDumpEntriesCollectionGet,
2944                             std::ref(app), "FaultLog"));
2945 }
2946 
2947 inline void requestRoutesFaultLogDumpEntry(App& app)
2948 {
2949     BMCWEB_ROUTE(app,
2950                  "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/")
2951         .privileges(redfish::privileges::getLogEntry)
2952         .methods(boost::beast::http::verb::get)(std::bind_front(
2953             handleLogServicesDumpEntryGet, std::ref(app), "FaultLog"));
2954 
2955     BMCWEB_ROUTE(app,
2956                  "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/")
2957         .privileges(redfish::privileges::deleteLogEntry)
2958         .methods(boost::beast::http::verb::delete_)(std::bind_front(
2959             handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog"));
2960 }
2961 
2962 inline void requestRoutesFaultLogDumpClear(App& app)
2963 {
2964     BMCWEB_ROUTE(
2965         app,
2966         "/redfish/v1/Managers/bmc/LogServices/FaultLog/Actions/LogService.ClearLog/")
2967         .privileges(redfish::privileges::postLogService)
2968         .methods(boost::beast::http::verb::post)(std::bind_front(
2969             handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog"));
2970 }
2971 
2972 inline void requestRoutesSystemDumpService(App& app)
2973 {
2974     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/")
2975         .privileges(redfish::privileges::getLogService)
2976         .methods(boost::beast::http::verb::get)(std::bind_front(
2977             handleLogServicesDumpServiceComputerSystemGet, std::ref(app)));
2978 }
2979 
2980 inline void requestRoutesSystemDumpEntryCollection(App& app)
2981 {
2982     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/")
2983         .privileges(redfish::privileges::getLogEntryCollection)
2984         .methods(boost::beast::http::verb::get)(std::bind_front(
2985             handleLogServicesDumpEntriesCollectionComputerSystemGet,
2986             std::ref(app)));
2987 }
2988 
2989 inline void requestRoutesSystemDumpEntry(App& app)
2990 {
2991     BMCWEB_ROUTE(app,
2992                  "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
2993         .privileges(redfish::privileges::getLogEntry)
2994         .methods(boost::beast::http::verb::get)(std::bind_front(
2995             handleLogServicesDumpEntryComputerSystemGet, std::ref(app)));
2996 
2997     BMCWEB_ROUTE(app,
2998                  "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/")
2999         .privileges(redfish::privileges::deleteLogEntry)
3000         .methods(boost::beast::http::verb::delete_)(std::bind_front(
3001             handleLogServicesDumpEntryComputerSystemDelete, std::ref(app)));
3002 }
3003 
3004 inline void requestRoutesSystemDumpCreate(App& app)
3005 {
3006     BMCWEB_ROUTE(
3007         app,
3008         "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
3009         .privileges(redfish::privileges::postLogService)
3010         .methods(boost::beast::http::verb::post)(std::bind_front(
3011             handleLogServicesDumpCollectDiagnosticDataComputerSystemPost,
3012             std::ref(app)));
3013 }
3014 
3015 inline void requestRoutesSystemDumpClear(App& app)
3016 {
3017     BMCWEB_ROUTE(
3018         app,
3019         "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/")
3020         .privileges(redfish::privileges::postLogService)
3021         .methods(boost::beast::http::verb::post)(std::bind_front(
3022             handleLogServicesDumpClearLogComputerSystemPost, std::ref(app)));
3023 }
3024 
3025 inline void requestRoutesCrashdumpService(App& app)
3026 {
3027     // Note: Deviated from redfish privilege registry for GET & HEAD
3028     // method for security reasons.
3029     /**
3030      * Functions triggers appropriate requests on DBus
3031      */
3032     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/")
3033         // This is incorrect, should be:
3034         //.privileges(redfish::privileges::getLogService)
3035         .privileges({{"ConfigureManager"}})
3036         .methods(boost::beast::http::verb::get)(
3037             [&app](const crow::Request& req,
3038                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3039                    const std::string& systemName) {
3040         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3041         {
3042             return;
3043         }
3044         if (systemName != "system")
3045         {
3046             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3047                                        systemName);
3048             return;
3049         }
3050 
3051         // Copy over the static data to include the entries added by
3052         // SubRoute
3053         asyncResp->res.jsonValue["@odata.id"] =
3054             "/redfish/v1/Systems/system/LogServices/Crashdump";
3055         asyncResp->res.jsonValue["@odata.type"] =
3056             "#LogService.v1_2_0.LogService";
3057         asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
3058         asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
3059         asyncResp->res.jsonValue["Id"] = "Crashdump";
3060         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
3061         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
3062 
3063         std::pair<std::string, std::string> redfishDateTimeOffset =
3064             redfish::time_utils::getDateTimeOffsetNow();
3065         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
3066         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
3067             redfishDateTimeOffset.second;
3068 
3069         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
3070             "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
3071         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
3072             "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog";
3073         asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
3074                                 ["target"] =
3075             "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData";
3076         });
3077 }
3078 
3079 void inline requestRoutesCrashdumpClear(App& app)
3080 {
3081     BMCWEB_ROUTE(
3082         app,
3083         "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/")
3084         // This is incorrect, should be:
3085         //.privileges(redfish::privileges::postLogService)
3086         .privileges({{"ConfigureComponents"}})
3087         .methods(boost::beast::http::verb::post)(
3088             [&app](const crow::Request& req,
3089                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3090                    const std::string& systemName) {
3091         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3092         {
3093             return;
3094         }
3095         if (systemName != "system")
3096         {
3097             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3098                                        systemName);
3099             return;
3100         }
3101         crow::connections::systemBus->async_method_call(
3102             [asyncResp](const boost::system::error_code& ec,
3103                         const std::string&) {
3104             if (ec)
3105             {
3106                 messages::internalError(asyncResp->res);
3107                 return;
3108             }
3109             messages::success(asyncResp->res);
3110             },
3111             crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
3112         });
3113 }
3114 
3115 static void
3116     logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3117                       const std::string& logID, nlohmann::json& logEntryJson)
3118 {
3119     auto getStoredLogCallback =
3120         [asyncResp, logID,
3121          &logEntryJson](const boost::system::error_code& ec,
3122                         const dbus::utility::DBusPropertiesMap& params) {
3123         if (ec)
3124         {
3125             BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
3126             if (ec.value() ==
3127                 boost::system::linux_error::bad_request_descriptor)
3128             {
3129                 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3130             }
3131             else
3132             {
3133                 messages::internalError(asyncResp->res);
3134             }
3135             return;
3136         }
3137 
3138         std::string timestamp{};
3139         std::string filename{};
3140         std::string logfile{};
3141         parseCrashdumpParameters(params, filename, timestamp, logfile);
3142 
3143         if (filename.empty() || timestamp.empty())
3144         {
3145             messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3146             return;
3147         }
3148 
3149         std::string crashdumpURI =
3150             "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
3151             logID + "/" + filename;
3152         nlohmann::json::object_t logEntry;
3153         logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
3154         logEntry["@odata.id"] = boost::urls::format(
3155             "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/{}",
3156             logID);
3157         logEntry["Name"] = "CPU Crashdump";
3158         logEntry["Id"] = logID;
3159         logEntry["EntryType"] = "Oem";
3160         logEntry["AdditionalDataURI"] = std::move(crashdumpURI);
3161         logEntry["DiagnosticDataType"] = "OEM";
3162         logEntry["OEMDiagnosticDataType"] = "PECICrashdump";
3163         logEntry["Created"] = std::move(timestamp);
3164 
3165         // If logEntryJson references an array of LogEntry resources
3166         // ('Members' list), then push this as a new entry, otherwise set it
3167         // directly
3168         if (logEntryJson.is_array())
3169         {
3170             logEntryJson.push_back(logEntry);
3171             asyncResp->res.jsonValue["Members@odata.count"] =
3172                 logEntryJson.size();
3173         }
3174         else
3175         {
3176             logEntryJson.update(logEntry);
3177         }
3178     };
3179     sdbusplus::asio::getAllProperties(
3180         *crow::connections::systemBus, crashdumpObject,
3181         crashdumpPath + std::string("/") + logID, crashdumpInterface,
3182         std::move(getStoredLogCallback));
3183 }
3184 
3185 inline void requestRoutesCrashdumpEntryCollection(App& app)
3186 {
3187     // Note: Deviated from redfish privilege registry for GET & HEAD
3188     // method for security reasons.
3189     /**
3190      * Functions triggers appropriate requests on DBus
3191      */
3192     BMCWEB_ROUTE(app,
3193                  "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/")
3194         // This is incorrect, should be.
3195         //.privileges(redfish::privileges::postLogEntryCollection)
3196         .privileges({{"ConfigureComponents"}})
3197         .methods(boost::beast::http::verb::get)(
3198             [&app](const crow::Request& req,
3199                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3200                    const std::string& systemName) {
3201         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3202         {
3203             return;
3204         }
3205         if (systemName != "system")
3206         {
3207             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3208                                        systemName);
3209             return;
3210         }
3211 
3212         constexpr std::array<std::string_view, 1> interfaces = {
3213             crashdumpInterface};
3214         dbus::utility::getSubTreePaths(
3215             "/", 0, interfaces,
3216             [asyncResp](const boost::system::error_code& ec,
3217                         const std::vector<std::string>& resp) {
3218             if (ec)
3219             {
3220                 if (ec.value() !=
3221                     boost::system::errc::no_such_file_or_directory)
3222                 {
3223                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
3224                                      << ec.message();
3225                     messages::internalError(asyncResp->res);
3226                     return;
3227                 }
3228             }
3229             asyncResp->res.jsonValue["@odata.type"] =
3230                 "#LogEntryCollection.LogEntryCollection";
3231             asyncResp->res.jsonValue["@odata.id"] =
3232                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
3233             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
3234             asyncResp->res.jsonValue["Description"] =
3235                 "Collection of Crashdump Entries";
3236             asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3237             asyncResp->res.jsonValue["Members@odata.count"] = 0;
3238 
3239             for (const std::string& path : resp)
3240             {
3241                 const sdbusplus::message::object_path objPath(path);
3242                 // Get the log ID
3243                 std::string logID = objPath.filename();
3244                 if (logID.empty())
3245                 {
3246                     continue;
3247                 }
3248                 // Add the log entry to the array
3249                 logCrashdumpEntry(asyncResp, logID,
3250                                   asyncResp->res.jsonValue["Members"]);
3251             }
3252             });
3253         });
3254 }
3255 
3256 inline void requestRoutesCrashdumpEntry(App& app)
3257 {
3258     // Note: Deviated from redfish privilege registry for GET & HEAD
3259     // method for security reasons.
3260 
3261     BMCWEB_ROUTE(
3262         app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/")
3263         // this is incorrect, should be
3264         // .privileges(redfish::privileges::getLogEntry)
3265         .privileges({{"ConfigureComponents"}})
3266         .methods(boost::beast::http::verb::get)(
3267             [&app](const crow::Request& req,
3268                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3269                    const std::string& systemName, const std::string& param) {
3270         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3271         {
3272             return;
3273         }
3274         if (systemName != "system")
3275         {
3276             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3277                                        systemName);
3278             return;
3279         }
3280         const std::string& logID = param;
3281         logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
3282         });
3283 }
3284 
3285 inline void requestRoutesCrashdumpFile(App& app)
3286 {
3287     // Note: Deviated from redfish privilege registry for GET & HEAD
3288     // method for security reasons.
3289     BMCWEB_ROUTE(
3290         app,
3291         "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/")
3292         .privileges(redfish::privileges::getLogEntry)
3293         .methods(boost::beast::http::verb::get)(
3294             [](const crow::Request& req,
3295                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3296                const std::string& systemName, const std::string& logID,
3297                const std::string& fileName) {
3298         // Do not call getRedfishRoute here since the crashdump file is not a
3299         // Redfish resource.
3300 
3301         if (systemName != "system")
3302         {
3303             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3304                                        systemName);
3305             return;
3306         }
3307 
3308         auto getStoredLogCallback =
3309             [asyncResp, logID, fileName, url(boost::urls::url(req.url()))](
3310                 const boost::system::error_code& ec,
3311                 const std::vector<
3312                     std::pair<std::string, dbus::utility::DbusVariantType>>&
3313                     resp) {
3314             if (ec)
3315             {
3316                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
3317                 messages::internalError(asyncResp->res);
3318                 return;
3319             }
3320 
3321             std::string dbusFilename{};
3322             std::string dbusTimestamp{};
3323             std::string dbusFilepath{};
3324 
3325             parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp,
3326                                      dbusFilepath);
3327 
3328             if (dbusFilename.empty() || dbusTimestamp.empty() ||
3329                 dbusFilepath.empty())
3330             {
3331                 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3332                 return;
3333             }
3334 
3335             // Verify the file name parameter is correct
3336             if (fileName != dbusFilename)
3337             {
3338                 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3339                 return;
3340             }
3341 
3342             if (!std::filesystem::exists(dbusFilepath))
3343             {
3344                 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
3345                 return;
3346             }
3347             std::ifstream ifs(dbusFilepath, std::ios::in | std::ios::binary);
3348             asyncResp->res.body() =
3349                 std::string(std::istreambuf_iterator<char>{ifs}, {});
3350 
3351             // Configure this to be a file download when accessed
3352             // from a browser
3353             asyncResp->res.addHeader(
3354                 boost::beast::http::field::content_disposition, "attachment");
3355         };
3356         sdbusplus::asio::getAllProperties(
3357             *crow::connections::systemBus, crashdumpObject,
3358             crashdumpPath + std::string("/") + logID, crashdumpInterface,
3359             std::move(getStoredLogCallback));
3360         });
3361 }
3362 
3363 enum class OEMDiagnosticType
3364 {
3365     onDemand,
3366     telemetry,
3367     invalid,
3368 };
3369 
3370 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr)
3371 {
3372     if (oemDiagStr == "OnDemand")
3373     {
3374         return OEMDiagnosticType::onDemand;
3375     }
3376     if (oemDiagStr == "Telemetry")
3377     {
3378         return OEMDiagnosticType::telemetry;
3379     }
3380 
3381     return OEMDiagnosticType::invalid;
3382 }
3383 
3384 inline void requestRoutesCrashdumpCollect(App& app)
3385 {
3386     // Note: Deviated from redfish privilege registry for GET & HEAD
3387     // method for security reasons.
3388     BMCWEB_ROUTE(
3389         app,
3390         "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/")
3391         // The below is incorrect;  Should be ConfigureManager
3392         //.privileges(redfish::privileges::postLogService)
3393         .privileges({{"ConfigureComponents"}})
3394         .methods(boost::beast::http::verb::post)(
3395             [&app](const crow::Request& req,
3396                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3397                    const std::string& systemName) {
3398         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3399         {
3400             return;
3401         }
3402 
3403         if (systemName != "system")
3404         {
3405             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3406                                        systemName);
3407             return;
3408         }
3409 
3410         std::string diagnosticDataType;
3411         std::string oemDiagnosticDataType;
3412         if (!redfish::json_util::readJsonAction(
3413                 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
3414                 "OEMDiagnosticDataType", oemDiagnosticDataType))
3415         {
3416             return;
3417         }
3418 
3419         if (diagnosticDataType != "OEM")
3420         {
3421             BMCWEB_LOG_ERROR
3422                 << "Only OEM DiagnosticDataType supported for Crashdump";
3423             messages::actionParameterValueFormatError(
3424                 asyncResp->res, diagnosticDataType, "DiagnosticDataType",
3425                 "CollectDiagnosticData");
3426             return;
3427         }
3428 
3429         OEMDiagnosticType oemDiagType =
3430             getOEMDiagnosticType(oemDiagnosticDataType);
3431 
3432         std::string iface;
3433         std::string method;
3434         std::string taskMatchStr;
3435         if (oemDiagType == OEMDiagnosticType::onDemand)
3436         {
3437             iface = crashdumpOnDemandInterface;
3438             method = "GenerateOnDemandLog";
3439             taskMatchStr = "type='signal',"
3440                            "interface='org.freedesktop.DBus.Properties',"
3441                            "member='PropertiesChanged',"
3442                            "arg0namespace='com.intel.crashdump'";
3443         }
3444         else if (oemDiagType == OEMDiagnosticType::telemetry)
3445         {
3446             iface = crashdumpTelemetryInterface;
3447             method = "GenerateTelemetryLog";
3448             taskMatchStr = "type='signal',"
3449                            "interface='org.freedesktop.DBus.Properties',"
3450                            "member='PropertiesChanged',"
3451                            "arg0namespace='com.intel.crashdump'";
3452         }
3453         else
3454         {
3455             BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: "
3456                              << oemDiagnosticDataType;
3457             messages::actionParameterValueFormatError(
3458                 asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType",
3459                 "CollectDiagnosticData");
3460             return;
3461         }
3462 
3463         auto collectCrashdumpCallback =
3464             [asyncResp, payload(task::Payload(req)),
3465              taskMatchStr](const boost::system::error_code& ec,
3466                            const std::string&) mutable {
3467             if (ec)
3468             {
3469                 if (ec.value() == boost::system::errc::operation_not_supported)
3470                 {
3471                     messages::resourceInStandby(asyncResp->res);
3472                 }
3473                 else if (ec.value() ==
3474                          boost::system::errc::device_or_resource_busy)
3475                 {
3476                     messages::serviceTemporarilyUnavailable(asyncResp->res,
3477                                                             "60");
3478                 }
3479                 else
3480                 {
3481                     messages::internalError(asyncResp->res);
3482                 }
3483                 return;
3484             }
3485             std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
3486                 [](const boost::system::error_code& err, sdbusplus::message_t&,
3487                    const std::shared_ptr<task::TaskData>& taskData) {
3488                 if (!err)
3489                 {
3490                     taskData->messages.emplace_back(messages::taskCompletedOK(
3491                         std::to_string(taskData->index)));
3492                     taskData->state = "Completed";
3493                 }
3494                 return task::completed;
3495                 },
3496                 taskMatchStr);
3497 
3498             task->startTimer(std::chrono::minutes(5));
3499             task->populateResp(asyncResp->res);
3500             task->payload.emplace(std::move(payload));
3501         };
3502 
3503         crow::connections::systemBus->async_method_call(
3504             std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath,
3505             iface, method);
3506         });
3507 }
3508 
3509 /**
3510  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
3511  */
3512 inline void requestRoutesDBusLogServiceActionsClear(App& app)
3513 {
3514     /**
3515      * Function handles POST method request.
3516      * The Clear Log actions does not require any parameter.The action deletes
3517      * all entries found in the Entries collection for this Log Service.
3518      */
3519 
3520     BMCWEB_ROUTE(
3521         app,
3522         "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
3523         .privileges(redfish::privileges::postLogService)
3524         .methods(boost::beast::http::verb::post)(
3525             [&app](const crow::Request& req,
3526                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3527                    const std::string& systemName) {
3528         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3529         {
3530             return;
3531         }
3532         if (systemName != "system")
3533         {
3534             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3535                                        systemName);
3536             return;
3537         }
3538         BMCWEB_LOG_DEBUG << "Do delete all entries.";
3539 
3540         // Process response from Logging service.
3541         auto respHandler = [asyncResp](const boost::system::error_code& ec) {
3542             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
3543             if (ec)
3544             {
3545                 // TODO Handle for specific error code
3546                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
3547                 asyncResp->res.result(
3548                     boost::beast::http::status::internal_server_error);
3549                 return;
3550             }
3551 
3552             asyncResp->res.result(boost::beast::http::status::no_content);
3553         };
3554 
3555         // Make call to Logging service to request Clear Log
3556         crow::connections::systemBus->async_method_call(
3557             respHandler, "xyz.openbmc_project.Logging",
3558             "/xyz/openbmc_project/logging",
3559             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3560         });
3561 }
3562 
3563 /****************************************************
3564  * Redfish PostCode interfaces
3565  * using DBUS interface: getPostCodesTS
3566  ******************************************************/
3567 inline void requestRoutesPostCodesLogService(App& app)
3568 {
3569     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/")
3570         .privileges(redfish::privileges::getLogService)
3571         .methods(boost::beast::http::verb::get)(
3572             [&app](const crow::Request& req,
3573                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3574                    const std::string& systemName) {
3575         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3576         {
3577             return;
3578         }
3579         if (systemName != "system")
3580         {
3581             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3582                                        systemName);
3583             return;
3584         }
3585         asyncResp->res.jsonValue["@odata.id"] =
3586             "/redfish/v1/Systems/system/LogServices/PostCodes";
3587         asyncResp->res.jsonValue["@odata.type"] =
3588             "#LogService.v1_1_0.LogService";
3589         asyncResp->res.jsonValue["Name"] = "POST Code Log Service";
3590         asyncResp->res.jsonValue["Description"] = "POST Code Log Service";
3591         asyncResp->res.jsonValue["Id"] = "PostCodes";
3592         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
3593         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
3594             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
3595 
3596         std::pair<std::string, std::string> redfishDateTimeOffset =
3597             redfish::time_utils::getDateTimeOffsetNow();
3598         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
3599         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
3600             redfishDateTimeOffset.second;
3601 
3602         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
3603             {"target",
3604              "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}};
3605         });
3606 }
3607 
3608 inline void requestRoutesPostCodesClear(App& app)
3609 {
3610     BMCWEB_ROUTE(
3611         app,
3612         "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/")
3613         // The following privilege is incorrect;  It should be ConfigureManager
3614         //.privileges(redfish::privileges::postLogService)
3615         .privileges({{"ConfigureComponents"}})
3616         .methods(boost::beast::http::verb::post)(
3617             [&app](const crow::Request& req,
3618                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3619                    const std::string& systemName) {
3620         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3621         {
3622             return;
3623         }
3624         if (systemName != "system")
3625         {
3626             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3627                                        systemName);
3628             return;
3629         }
3630         BMCWEB_LOG_DEBUG << "Do delete all postcodes entries.";
3631 
3632         // Make call to post-code service to request clear all
3633         crow::connections::systemBus->async_method_call(
3634             [asyncResp](const boost::system::error_code& ec) {
3635             if (ec)
3636             {
3637                 // TODO Handle for specific error code
3638                 BMCWEB_LOG_ERROR << "doClearPostCodes resp_handler got error "
3639                                  << ec;
3640                 asyncResp->res.result(
3641                     boost::beast::http::status::internal_server_error);
3642                 messages::internalError(asyncResp->res);
3643                 return;
3644             }
3645             },
3646             "xyz.openbmc_project.State.Boot.PostCode0",
3647             "/xyz/openbmc_project/State/Boot/PostCode0",
3648             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3649         });
3650 }
3651 
3652 /**
3653  * @brief Parse post code ID and get the current value and index value
3654  *        eg: postCodeID=B1-2, currentValue=1, index=2
3655  *
3656  * @param[in]  postCodeID     Post Code ID
3657  * @param[out] currentValue   Current value
3658  * @param[out] index          Index value
3659  *
3660  * @return bool true if the parsing is successful, false the parsing fails
3661  */
3662 inline static bool parsePostCode(const std::string& postCodeID,
3663                                  uint64_t& currentValue, uint16_t& index)
3664 {
3665     std::vector<std::string> split;
3666     bmcweb::split(split, postCodeID, '-');
3667     if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B')
3668     {
3669         return false;
3670     }
3671 
3672     auto start = std::next(split[0].begin());
3673     auto end = split[0].end();
3674     auto [ptrIndex, ecIndex] = std::from_chars(&*start, &*end, index);
3675 
3676     if (ptrIndex != &*end || ecIndex != std::errc())
3677     {
3678         return false;
3679     }
3680 
3681     start = split[1].begin();
3682     end = split[1].end();
3683 
3684     auto [ptrValue, ecValue] = std::from_chars(&*start, &*end, currentValue);
3685 
3686     return ptrValue == &*end && ecValue == std::errc();
3687 }
3688 
3689 static bool fillPostCodeEntry(
3690     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3691     const boost::container::flat_map<
3692         uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode,
3693     const uint16_t bootIndex, const uint64_t codeIndex = 0,
3694     const uint64_t skip = 0, const uint64_t top = 0)
3695 {
3696     // Get the Message from the MessageRegistry
3697     const registries::Message* message =
3698         registries::getMessage("OpenBMC.0.2.BIOSPOSTCode");
3699 
3700     uint64_t currentCodeIndex = 0;
3701     uint64_t firstCodeTimeUs = 0;
3702     for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
3703              code : postcode)
3704     {
3705         currentCodeIndex++;
3706         std::string postcodeEntryID =
3707             "B" + std::to_string(bootIndex) + "-" +
3708             std::to_string(currentCodeIndex); // 1 based index in EntryID string
3709 
3710         uint64_t usecSinceEpoch = code.first;
3711         uint64_t usTimeOffset = 0;
3712 
3713         if (1 == currentCodeIndex)
3714         { // already incremented
3715             firstCodeTimeUs = code.first;
3716         }
3717         else
3718         {
3719             usTimeOffset = code.first - firstCodeTimeUs;
3720         }
3721 
3722         // skip if no specific codeIndex is specified and currentCodeIndex does
3723         // not fall between top and skip
3724         if ((codeIndex == 0) &&
3725             (currentCodeIndex <= skip || currentCodeIndex > top))
3726         {
3727             continue;
3728         }
3729 
3730         // skip if a specific codeIndex is specified and does not match the
3731         // currentIndex
3732         if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
3733         {
3734             // This is done for simplicity. 1st entry is needed to calculate
3735             // time offset. To improve efficiency, one can get to the entry
3736             // directly (possibly with flatmap's nth method)
3737             continue;
3738         }
3739 
3740         // currentCodeIndex is within top and skip or equal to specified code
3741         // index
3742 
3743         // Get the Created time from the timestamp
3744         std::string entryTimeStr;
3745         entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch);
3746 
3747         // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
3748         std::ostringstream hexCode;
3749         hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
3750                 << std::get<0>(code.second);
3751         std::ostringstream timeOffsetStr;
3752         // Set Fixed -Point Notation
3753         timeOffsetStr << std::fixed;
3754         // Set precision to 4 digits
3755         timeOffsetStr << std::setprecision(4);
3756         // Add double to stream
3757         timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
3758         std::vector<std::string> messageArgs = {
3759             std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()};
3760 
3761         // Get MessageArgs template from message registry
3762         std::string msg;
3763         if (message != nullptr)
3764         {
3765             msg = message->message;
3766 
3767             // fill in this post code value
3768             int i = 0;
3769             for (const std::string& messageArg : messageArgs)
3770             {
3771                 std::string argStr = "%" + std::to_string(++i);
3772                 size_t argPos = msg.find(argStr);
3773                 if (argPos != std::string::npos)
3774                 {
3775                     msg.replace(argPos, argStr.length(), messageArg);
3776                 }
3777             }
3778         }
3779 
3780         // Get Severity template from message registry
3781         std::string severity;
3782         if (message != nullptr)
3783         {
3784             severity = message->messageSeverity;
3785         }
3786 
3787         // Format entry
3788         nlohmann::json::object_t bmcLogEntry;
3789         bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
3790         bmcLogEntry["@odata.id"] = boost::urls::format(
3791             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/{}",
3792             postcodeEntryID);
3793         bmcLogEntry["Name"] = "POST Code Log Entry";
3794         bmcLogEntry["Id"] = postcodeEntryID;
3795         bmcLogEntry["Message"] = std::move(msg);
3796         bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode";
3797         bmcLogEntry["MessageArgs"] = std::move(messageArgs);
3798         bmcLogEntry["EntryType"] = "Event";
3799         bmcLogEntry["Severity"] = std::move(severity);
3800         bmcLogEntry["Created"] = entryTimeStr;
3801         if (!std::get<std::vector<uint8_t>>(code.second).empty())
3802         {
3803             bmcLogEntry["AdditionalDataURI"] =
3804                 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" +
3805                 postcodeEntryID + "/attachment";
3806         }
3807 
3808         // codeIndex is only specified when querying single entry, return only
3809         // that entry in this case
3810         if (codeIndex != 0)
3811         {
3812             asyncResp->res.jsonValue.update(bmcLogEntry);
3813             return true;
3814         }
3815 
3816         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
3817         logEntryArray.emplace_back(std::move(bmcLogEntry));
3818     }
3819 
3820     // Return value is always false when querying multiple entries
3821     return false;
3822 }
3823 
3824 static void
3825     getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3826                         const std::string& entryId)
3827 {
3828     uint16_t bootIndex = 0;
3829     uint64_t codeIndex = 0;
3830     if (!parsePostCode(entryId, codeIndex, bootIndex))
3831     {
3832         // Requested ID was not found
3833         messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
3834         return;
3835     }
3836 
3837     if (bootIndex == 0 || codeIndex == 0)
3838     {
3839         // 0 is an invalid index
3840         messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
3841         return;
3842     }
3843 
3844     crow::connections::systemBus->async_method_call(
3845         [asyncResp, entryId, bootIndex,
3846          codeIndex](const boost::system::error_code& ec,
3847                     const boost::container::flat_map<
3848                         uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
3849                         postcode) {
3850         if (ec)
3851         {
3852             BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
3853             messages::internalError(asyncResp->res);
3854             return;
3855         }
3856 
3857         if (postcode.empty())
3858         {
3859             messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
3860             return;
3861         }
3862 
3863         if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex))
3864         {
3865             messages::resourceNotFound(asyncResp->res, "LogEntry", entryId);
3866             return;
3867         }
3868         },
3869         "xyz.openbmc_project.State.Boot.PostCode0",
3870         "/xyz/openbmc_project/State/Boot/PostCode0",
3871         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
3872         bootIndex);
3873 }
3874 
3875 static void
3876     getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3877                        const uint16_t bootIndex, const uint16_t bootCount,
3878                        const uint64_t entryCount, size_t skip, size_t top)
3879 {
3880     crow::connections::systemBus->async_method_call(
3881         [asyncResp, bootIndex, bootCount, entryCount, skip,
3882          top](const boost::system::error_code& ec,
3883               const boost::container::flat_map<
3884                   uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
3885                   postcode) {
3886         if (ec)
3887         {
3888             BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
3889             messages::internalError(asyncResp->res);
3890             return;
3891         }
3892 
3893         uint64_t endCount = entryCount;
3894         if (!postcode.empty())
3895         {
3896             endCount = entryCount + postcode.size();
3897             if (skip < endCount && (top + skip) > entryCount)
3898             {
3899                 uint64_t thisBootSkip = std::max(static_cast<uint64_t>(skip),
3900                                                  entryCount) -
3901                                         entryCount;
3902                 uint64_t thisBootTop =
3903                     std::min(static_cast<uint64_t>(top + skip), endCount) -
3904                     entryCount;
3905 
3906                 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0,
3907                                   thisBootSkip, thisBootTop);
3908             }
3909             asyncResp->res.jsonValue["Members@odata.count"] = endCount;
3910         }
3911 
3912         // continue to previous bootIndex
3913         if (bootIndex < bootCount)
3914         {
3915             getPostCodeForBoot(asyncResp, static_cast<uint16_t>(bootIndex + 1),
3916                                bootCount, endCount, skip, top);
3917         }
3918         else if (skip + top < endCount)
3919         {
3920             asyncResp->res.jsonValue["Members@odata.nextLink"] =
3921                 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" +
3922                 std::to_string(skip + top);
3923         }
3924         },
3925         "xyz.openbmc_project.State.Boot.PostCode0",
3926         "/xyz/openbmc_project/State/Boot/PostCode0",
3927         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
3928         bootIndex);
3929 }
3930 
3931 static void
3932     getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3933                          size_t skip, size_t top)
3934 {
3935     uint64_t entryCount = 0;
3936     sdbusplus::asio::getProperty<uint16_t>(
3937         *crow::connections::systemBus,
3938         "xyz.openbmc_project.State.Boot.PostCode0",
3939         "/xyz/openbmc_project/State/Boot/PostCode0",
3940         "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount",
3941         [asyncResp, entryCount, skip, top](const boost::system::error_code& ec,
3942                                            const uint16_t bootCount) {
3943         if (ec)
3944         {
3945             BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
3946             messages::internalError(asyncResp->res);
3947             return;
3948         }
3949         getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top);
3950         });
3951 }
3952 
3953 inline void requestRoutesPostCodesEntryCollection(App& app)
3954 {
3955     BMCWEB_ROUTE(app,
3956                  "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/")
3957         .privileges(redfish::privileges::getLogEntryCollection)
3958         .methods(boost::beast::http::verb::get)(
3959             [&app](const crow::Request& req,
3960                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3961                    const std::string& systemName) {
3962         query_param::QueryCapabilities capabilities = {
3963             .canDelegateTop = true,
3964             .canDelegateSkip = true,
3965         };
3966         query_param::Query delegatedQuery;
3967         if (!redfish::setUpRedfishRouteWithDelegation(
3968                 app, req, asyncResp, delegatedQuery, capabilities))
3969         {
3970             return;
3971         }
3972 
3973         if (systemName != "system")
3974         {
3975             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
3976                                        systemName);
3977             return;
3978         }
3979         asyncResp->res.jsonValue["@odata.type"] =
3980             "#LogEntryCollection.LogEntryCollection";
3981         asyncResp->res.jsonValue["@odata.id"] =
3982             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
3983         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
3984         asyncResp->res.jsonValue["Description"] =
3985             "Collection of POST Code Log Entries";
3986         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3987         asyncResp->res.jsonValue["Members@odata.count"] = 0;
3988         size_t skip = delegatedQuery.skip.value_or(0);
3989         size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
3990         getCurrentBootNumber(asyncResp, skip, top);
3991         });
3992 }
3993 
3994 inline void requestRoutesPostCodesEntryAdditionalData(App& app)
3995 {
3996     BMCWEB_ROUTE(
3997         app,
3998         "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/")
3999         .privileges(redfish::privileges::getLogEntry)
4000         .methods(boost::beast::http::verb::get)(
4001             [&app](const crow::Request& req,
4002                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4003                    const std::string& systemName,
4004                    const std::string& postCodeID) {
4005         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
4006         {
4007             return;
4008         }
4009         if (!http_helpers::isContentTypeAllowed(
4010                 req.getHeaderValue("Accept"),
4011                 http_helpers::ContentType::OctetStream, true))
4012         {
4013             asyncResp->res.result(boost::beast::http::status::bad_request);
4014             return;
4015         }
4016         if (systemName != "system")
4017         {
4018             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4019                                        systemName);
4020             return;
4021         }
4022 
4023         uint64_t currentValue = 0;
4024         uint16_t index = 0;
4025         if (!parsePostCode(postCodeID, currentValue, index))
4026         {
4027             messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
4028             return;
4029         }
4030 
4031         crow::connections::systemBus->async_method_call(
4032             [asyncResp, postCodeID, currentValue](
4033                 const boost::system::error_code& ec,
4034                 const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>&
4035                     postcodes) {
4036             if (ec.value() == EBADR)
4037             {
4038                 messages::resourceNotFound(asyncResp->res, "LogEntry",
4039                                            postCodeID);
4040                 return;
4041             }
4042             if (ec)
4043             {
4044                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
4045                 messages::internalError(asyncResp->res);
4046                 return;
4047             }
4048 
4049             size_t value = static_cast<size_t>(currentValue) - 1;
4050             if (value == std::string::npos || postcodes.size() < currentValue)
4051             {
4052                 BMCWEB_LOG_WARNING << "Wrong currentValue value";
4053                 messages::resourceNotFound(asyncResp->res, "LogEntry",
4054                                            postCodeID);
4055                 return;
4056             }
4057 
4058             const auto& [tID, c] = postcodes[value];
4059             if (c.empty())
4060             {
4061                 BMCWEB_LOG_WARNING << "No found post code data";
4062                 messages::resourceNotFound(asyncResp->res, "LogEntry",
4063                                            postCodeID);
4064                 return;
4065             }
4066             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
4067             const char* d = reinterpret_cast<const char*>(c.data());
4068             std::string_view strData(d, c.size());
4069 
4070             asyncResp->res.addHeader(boost::beast::http::field::content_type,
4071                                      "application/octet-stream");
4072             asyncResp->res.addHeader(
4073                 boost::beast::http::field::content_transfer_encoding, "Base64");
4074             asyncResp->res.body() = crow::utility::base64encode(strData);
4075             },
4076             "xyz.openbmc_project.State.Boot.PostCode0",
4077             "/xyz/openbmc_project/State/Boot/PostCode0",
4078             "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index);
4079         });
4080 }
4081 
4082 inline void requestRoutesPostCodesEntry(App& app)
4083 {
4084     BMCWEB_ROUTE(
4085         app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/")
4086         .privileges(redfish::privileges::getLogEntry)
4087         .methods(boost::beast::http::verb::get)(
4088             [&app](const crow::Request& req,
4089                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4090                    const std::string& systemName, const std::string& targetID) {
4091         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
4092         {
4093             return;
4094         }
4095         if (systemName != "system")
4096         {
4097             messages::resourceNotFound(asyncResp->res, "ComputerSystem",
4098                                        systemName);
4099             return;
4100         }
4101 
4102         getPostCodeForEntry(asyncResp, targetID);
4103         });
4104 }
4105 
4106 } // namespace redfish
4107