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