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