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