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