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