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