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