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