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