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