xref: /openbmc/bmcweb/features/redfish/lib/log_services.hpp (revision d405bb51fb420ee9318b1cdc13c791db2f56b99c)
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::object_t& 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                 std::string idStr;
1200                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1201                 {
1202                     continue;
1203                 }
1204                 firstEntry = false;
1205 
1206                 nlohmann::json::object_t bmcLogEntry;
1207                 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0)
1208                 {
1209                     messages::internalError(asyncResp->res);
1210                     return;
1211                 }
1212 
1213                 if (bmcLogEntry.empty())
1214                 {
1215                     continue;
1216                 }
1217 
1218                 entryCount++;
1219                 // Handle paging using skip (number of entries to skip from the
1220                 // start) and top (number of entries to display)
1221                 if (entryCount <= delegatedQuery.skip ||
1222                     entryCount > delegatedQuery.skip + delegatedQuery.top)
1223                 {
1224                     continue;
1225                 }
1226 
1227                 logEntryArray.push_back(std::move(bmcLogEntry));
1228             }
1229         }
1230         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
1231         if (delegatedQuery.skip + delegatedQuery.top < entryCount)
1232         {
1233             asyncResp->res.jsonValue["Members@odata.nextLink"] =
1234                 "/redfish/v1/Systems/system/LogServices/EventLog/Entries?$skip=" +
1235                 std::to_string(delegatedQuery.skip + delegatedQuery.top);
1236         }
1237         });
1238 }
1239 
1240 inline void requestRoutesJournalEventLogEntry(App& app)
1241 {
1242     BMCWEB_ROUTE(
1243         app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
1244         .privileges(redfish::privileges::getLogEntry)
1245         .methods(boost::beast::http::verb::get)(
1246             [&app](const crow::Request& req,
1247                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1248                    const std::string& param) {
1249         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1250         {
1251             return;
1252         }
1253         const std::string& targetID = param;
1254 
1255         // Go through the log files and check the unique ID for each
1256         // entry to find the target entry
1257         std::vector<std::filesystem::path> redfishLogFiles;
1258         getRedfishLogFiles(redfishLogFiles);
1259         std::string logEntry;
1260 
1261         // Oldest logs are in the last file, so start there and loop
1262         // backwards
1263         for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend();
1264              it++)
1265         {
1266             std::ifstream logStream(*it);
1267             if (!logStream.is_open())
1268             {
1269                 continue;
1270             }
1271 
1272             // Reset the unique ID on the first entry
1273             bool firstEntry = true;
1274             while (std::getline(logStream, logEntry))
1275             {
1276                 std::string idStr;
1277                 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
1278                 {
1279                     continue;
1280                 }
1281                 firstEntry = false;
1282 
1283                 if (idStr == targetID)
1284                 {
1285                     nlohmann::json::object_t bmcLogEntry;
1286                     if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) !=
1287                         0)
1288                     {
1289                         messages::internalError(asyncResp->res);
1290                         return;
1291                     }
1292                     asyncResp->res.jsonValue.update(bmcLogEntry);
1293                     return;
1294                 }
1295             }
1296         }
1297         // Requested ID was not found
1298         messages::resourceMissingAtURI(asyncResp->res,
1299                                        crow::utility::urlFromPieces(targetID));
1300         });
1301 }
1302 
1303 inline void requestRoutesDBusEventLogEntryCollection(App& app)
1304 {
1305     BMCWEB_ROUTE(app,
1306                  "/redfish/v1/Systems/system/LogServices/EventLog/Entries/")
1307         .privileges(redfish::privileges::getLogEntryCollection)
1308         .methods(boost::beast::http::verb::get)(
1309             [&app](const crow::Request& req,
1310                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1311         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1312         {
1313             return;
1314         }
1315         // Collections don't include the static data added by SubRoute
1316         // because it has a duplicate entry for members
1317         asyncResp->res.jsonValue["@odata.type"] =
1318             "#LogEntryCollection.LogEntryCollection";
1319         asyncResp->res.jsonValue["@odata.id"] =
1320             "/redfish/v1/Systems/system/LogServices/EventLog/Entries";
1321         asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
1322         asyncResp->res.jsonValue["Description"] =
1323             "Collection of System Event Log Entries";
1324 
1325         // DBus implementation of EventLog/Entries
1326         // Make call to Logging Service to find all log entry objects
1327         crow::connections::systemBus->async_method_call(
1328             [asyncResp](const boost::system::error_code ec,
1329                         const dbus::utility::ManagedObjectType& resp) {
1330             if (ec)
1331             {
1332                 // TODO Handle for specific error code
1333                 BMCWEB_LOG_ERROR
1334                     << "getLogEntriesIfaceData resp_handler got error " << ec;
1335                 messages::internalError(asyncResp->res);
1336                 return;
1337             }
1338             nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
1339             entriesArray = nlohmann::json::array();
1340             for (const auto& objectPath : resp)
1341             {
1342                 const uint32_t* id = nullptr;
1343                 const uint64_t* timestamp = nullptr;
1344                 const uint64_t* updateTimestamp = nullptr;
1345                 const std::string* severity = nullptr;
1346                 const std::string* message = nullptr;
1347                 const std::string* filePath = nullptr;
1348                 bool resolved = false;
1349                 for (const auto& interfaceMap : objectPath.second)
1350                 {
1351                     if (interfaceMap.first ==
1352                         "xyz.openbmc_project.Logging.Entry")
1353                     {
1354                         for (const auto& propertyMap : interfaceMap.second)
1355                         {
1356                             if (propertyMap.first == "Id")
1357                             {
1358                                 id = std::get_if<uint32_t>(&propertyMap.second);
1359                             }
1360                             else if (propertyMap.first == "Timestamp")
1361                             {
1362                                 timestamp =
1363                                     std::get_if<uint64_t>(&propertyMap.second);
1364                             }
1365                             else if (propertyMap.first == "UpdateTimestamp")
1366                             {
1367                                 updateTimestamp =
1368                                     std::get_if<uint64_t>(&propertyMap.second);
1369                             }
1370                             else if (propertyMap.first == "Severity")
1371                             {
1372                                 severity = std::get_if<std::string>(
1373                                     &propertyMap.second);
1374                             }
1375                             else if (propertyMap.first == "Message")
1376                             {
1377                                 message = std::get_if<std::string>(
1378                                     &propertyMap.second);
1379                             }
1380                             else if (propertyMap.first == "Resolved")
1381                             {
1382                                 const bool* resolveptr =
1383                                     std::get_if<bool>(&propertyMap.second);
1384                                 if (resolveptr == nullptr)
1385                                 {
1386                                     messages::internalError(asyncResp->res);
1387                                     return;
1388                                 }
1389                                 resolved = *resolveptr;
1390                             }
1391                         }
1392                         if (id == nullptr || message == nullptr ||
1393                             severity == nullptr)
1394                         {
1395                             messages::internalError(asyncResp->res);
1396                             return;
1397                         }
1398                     }
1399                     else if (interfaceMap.first ==
1400                              "xyz.openbmc_project.Common.FilePath")
1401                     {
1402                         for (const auto& propertyMap : interfaceMap.second)
1403                         {
1404                             if (propertyMap.first == "Path")
1405                             {
1406                                 filePath = std::get_if<std::string>(
1407                                     &propertyMap.second);
1408                             }
1409                         }
1410                     }
1411                 }
1412                 // Object path without the
1413                 // xyz.openbmc_project.Logging.Entry interface, ignore
1414                 // and continue.
1415                 if (id == nullptr || message == nullptr ||
1416                     severity == nullptr || timestamp == nullptr ||
1417                     updateTimestamp == nullptr)
1418                 {
1419                     continue;
1420                 }
1421                 entriesArray.push_back({});
1422                 nlohmann::json& thisEntry = entriesArray.back();
1423                 thisEntry["@odata.type"] = "#LogEntry.v1_8_0.LogEntry";
1424                 thisEntry["@odata.id"] =
1425                     "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1426                     std::to_string(*id);
1427                 thisEntry["Name"] = "System Event Log Entry";
1428                 thisEntry["Id"] = std::to_string(*id);
1429                 thisEntry["Message"] = *message;
1430                 thisEntry["Resolved"] = resolved;
1431                 thisEntry["EntryType"] = "Event";
1432                 thisEntry["Severity"] =
1433                     translateSeverityDbusToRedfish(*severity);
1434                 thisEntry["Created"] =
1435                     crow::utility::getDateTimeUintMs(*timestamp);
1436                 thisEntry["Modified"] =
1437                     crow::utility::getDateTimeUintMs(*updateTimestamp);
1438                 if (filePath != nullptr)
1439                 {
1440                     thisEntry["AdditionalDataURI"] =
1441                         "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1442                         std::to_string(*id) + "/attachment";
1443                 }
1444             }
1445             std::sort(
1446                 entriesArray.begin(), entriesArray.end(),
1447                 [](const nlohmann::json& left, const nlohmann::json& right) {
1448                 return (left["Id"] <= right["Id"]);
1449                 });
1450             asyncResp->res.jsonValue["Members@odata.count"] =
1451                 entriesArray.size();
1452             },
1453             "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging",
1454             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1455         });
1456 }
1457 
1458 inline void requestRoutesDBusEventLogEntry(App& app)
1459 {
1460     BMCWEB_ROUTE(
1461         app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
1462         .privileges(redfish::privileges::getLogEntry)
1463         .methods(boost::beast::http::verb::get)(
1464             [&app](const crow::Request& req,
1465                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1466                    const std::string& param) {
1467         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1468         {
1469             return;
1470         }
1471         std::string entryID = param;
1472         dbus::utility::escapePathForDbus(entryID);
1473 
1474         // DBus implementation of EventLog/Entries
1475         // Make call to Logging Service to find all log entry objects
1476         crow::connections::systemBus->async_method_call(
1477             [asyncResp, entryID](const boost::system::error_code ec,
1478                                  const dbus::utility::DBusPropertiesMap& resp) {
1479             if (ec.value() == EBADR)
1480             {
1481                 messages::resourceNotFound(asyncResp->res, "EventLogEntry",
1482                                            entryID);
1483                 return;
1484             }
1485             if (ec)
1486             {
1487                 BMCWEB_LOG_ERROR
1488                     << "EventLogEntry (DBus) resp_handler got error " << ec;
1489                 messages::internalError(asyncResp->res);
1490                 return;
1491             }
1492             const uint32_t* id = nullptr;
1493             const uint64_t* timestamp = nullptr;
1494             const uint64_t* updateTimestamp = nullptr;
1495             const std::string* severity = nullptr;
1496             const std::string* message = nullptr;
1497             const std::string* filePath = nullptr;
1498             bool resolved = false;
1499 
1500             for (const auto& propertyMap : resp)
1501             {
1502                 if (propertyMap.first == "Id")
1503                 {
1504                     id = std::get_if<uint32_t>(&propertyMap.second);
1505                 }
1506                 else if (propertyMap.first == "Timestamp")
1507                 {
1508                     timestamp = std::get_if<uint64_t>(&propertyMap.second);
1509                 }
1510                 else if (propertyMap.first == "UpdateTimestamp")
1511                 {
1512                     updateTimestamp =
1513                         std::get_if<uint64_t>(&propertyMap.second);
1514                 }
1515                 else if (propertyMap.first == "Severity")
1516                 {
1517                     severity = std::get_if<std::string>(&propertyMap.second);
1518                 }
1519                 else if (propertyMap.first == "Message")
1520                 {
1521                     message = std::get_if<std::string>(&propertyMap.second);
1522                 }
1523                 else if (propertyMap.first == "Resolved")
1524                 {
1525                     const bool* resolveptr =
1526                         std::get_if<bool>(&propertyMap.second);
1527                     if (resolveptr == nullptr)
1528                     {
1529                         messages::internalError(asyncResp->res);
1530                         return;
1531                     }
1532                     resolved = *resolveptr;
1533                 }
1534                 else if (propertyMap.first == "Path")
1535                 {
1536                     filePath = std::get_if<std::string>(&propertyMap.second);
1537                 }
1538             }
1539             if (id == nullptr || message == nullptr || severity == nullptr ||
1540                 timestamp == nullptr || updateTimestamp == nullptr)
1541             {
1542                 messages::internalError(asyncResp->res);
1543                 return;
1544             }
1545             asyncResp->res.jsonValue["@odata.type"] =
1546                 "#LogEntry.v1_8_0.LogEntry";
1547             asyncResp->res.jsonValue["@odata.id"] =
1548                 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1549                 std::to_string(*id);
1550             asyncResp->res.jsonValue["Name"] = "System Event Log Entry";
1551             asyncResp->res.jsonValue["Id"] = std::to_string(*id);
1552             asyncResp->res.jsonValue["Message"] = *message;
1553             asyncResp->res.jsonValue["Resolved"] = resolved;
1554             asyncResp->res.jsonValue["EntryType"] = "Event";
1555             asyncResp->res.jsonValue["Severity"] =
1556                 translateSeverityDbusToRedfish(*severity);
1557             asyncResp->res.jsonValue["Created"] =
1558                 crow::utility::getDateTimeUintMs(*timestamp);
1559             asyncResp->res.jsonValue["Modified"] =
1560                 crow::utility::getDateTimeUintMs(*updateTimestamp);
1561             if (filePath != nullptr)
1562             {
1563                 asyncResp->res.jsonValue["AdditionalDataURI"] =
1564                     "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" +
1565                     std::to_string(*id) + "/attachment";
1566             }
1567             },
1568             "xyz.openbmc_project.Logging",
1569             "/xyz/openbmc_project/logging/entry/" + entryID,
1570             "org.freedesktop.DBus.Properties", "GetAll", "");
1571         });
1572 
1573     BMCWEB_ROUTE(
1574         app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
1575         .privileges(redfish::privileges::patchLogEntry)
1576         .methods(boost::beast::http::verb::patch)(
1577             [&app](const crow::Request& req,
1578                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1579                    const std::string& entryId) {
1580         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1581         {
1582             return;
1583         }
1584         std::optional<bool> resolved;
1585 
1586         if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved",
1587                                       resolved))
1588         {
1589             return;
1590         }
1591         BMCWEB_LOG_DEBUG << "Set Resolved";
1592 
1593         crow::connections::systemBus->async_method_call(
1594             [asyncResp, entryId](const boost::system::error_code ec) {
1595             if (ec)
1596             {
1597                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
1598                 messages::internalError(asyncResp->res);
1599                 return;
1600             }
1601             },
1602             "xyz.openbmc_project.Logging",
1603             "/xyz/openbmc_project/logging/entry/" + entryId,
1604             "org.freedesktop.DBus.Properties", "Set",
1605             "xyz.openbmc_project.Logging.Entry", "Resolved",
1606             dbus::utility::DbusVariantType(*resolved));
1607         });
1608 
1609     BMCWEB_ROUTE(
1610         app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/")
1611         .privileges(redfish::privileges::deleteLogEntry)
1612 
1613         .methods(boost::beast::http::verb::delete_)(
1614             [&app](const crow::Request& req,
1615                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1616                    const std::string& param) {
1617         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1618         {
1619             return;
1620         }
1621         BMCWEB_LOG_DEBUG << "Do delete single event entries.";
1622 
1623         std::string entryID = param;
1624 
1625         dbus::utility::escapePathForDbus(entryID);
1626 
1627         // Process response from Logging service.
1628         auto respHandler =
1629             [asyncResp, entryID](const boost::system::error_code ec) {
1630             BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done";
1631             if (ec)
1632             {
1633                 if (ec.value() == EBADR)
1634                 {
1635                     messages::resourceNotFound(asyncResp->res, "LogEntry",
1636                                                entryID);
1637                     return;
1638                 }
1639                 // TODO Handle for specific error code
1640                 BMCWEB_LOG_ERROR
1641                     << "EventLogEntry (DBus) doDelete respHandler got error "
1642                     << ec;
1643                 asyncResp->res.result(
1644                     boost::beast::http::status::internal_server_error);
1645                 return;
1646             }
1647 
1648             asyncResp->res.result(boost::beast::http::status::ok);
1649         };
1650 
1651         // Make call to Logging service to request Delete Log
1652         crow::connections::systemBus->async_method_call(
1653             respHandler, "xyz.openbmc_project.Logging",
1654             "/xyz/openbmc_project/logging/entry/" + entryID,
1655             "xyz.openbmc_project.Object.Delete", "Delete");
1656         });
1657 }
1658 
1659 inline void requestRoutesDBusEventLogEntryDownload(App& app)
1660 {
1661     BMCWEB_ROUTE(
1662         app,
1663         "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/attachment")
1664         .privileges(redfish::privileges::getLogEntry)
1665         .methods(boost::beast::http::verb::get)(
1666             [&app](const crow::Request& req,
1667                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1668                    const std::string& param) {
1669         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1670         {
1671             return;
1672         }
1673         if (!http_helpers::isOctetAccepted(req.getHeaderValue("Accept")))
1674         {
1675             asyncResp->res.result(boost::beast::http::status::bad_request);
1676             return;
1677         }
1678 
1679         std::string entryID = param;
1680         dbus::utility::escapePathForDbus(entryID);
1681 
1682         crow::connections::systemBus->async_method_call(
1683             [asyncResp, entryID](const boost::system::error_code ec,
1684                                  const sdbusplus::message::unix_fd& unixfd) {
1685             if (ec.value() == EBADR)
1686             {
1687                 messages::resourceNotFound(asyncResp->res, "EventLogAttachment",
1688                                            entryID);
1689                 return;
1690             }
1691             if (ec)
1692             {
1693                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
1694                 messages::internalError(asyncResp->res);
1695                 return;
1696             }
1697 
1698             int fd = -1;
1699             fd = dup(unixfd);
1700             if (fd == -1)
1701             {
1702                 messages::internalError(asyncResp->res);
1703                 return;
1704             }
1705 
1706             long long int size = lseek(fd, 0, SEEK_END);
1707             if (size == -1)
1708             {
1709                 messages::internalError(asyncResp->res);
1710                 return;
1711             }
1712 
1713             // Arbitrary max size of 64kb
1714             constexpr int maxFileSize = 65536;
1715             if (size > maxFileSize)
1716             {
1717                 BMCWEB_LOG_ERROR << "File size exceeds maximum allowed size of "
1718                                  << maxFileSize;
1719                 messages::internalError(asyncResp->res);
1720                 return;
1721             }
1722             std::vector<char> data(static_cast<size_t>(size));
1723             long long int rc = lseek(fd, 0, SEEK_SET);
1724             if (rc == -1)
1725             {
1726                 messages::internalError(asyncResp->res);
1727                 return;
1728             }
1729             rc = read(fd, data.data(), data.size());
1730             if ((rc == -1) || (rc != size))
1731             {
1732                 messages::internalError(asyncResp->res);
1733                 return;
1734             }
1735             close(fd);
1736 
1737             std::string_view strData(data.data(), data.size());
1738             std::string output = crow::utility::base64encode(strData);
1739 
1740             asyncResp->res.addHeader("Content-Type",
1741                                      "application/octet-stream");
1742             asyncResp->res.addHeader("Content-Transfer-Encoding", "Base64");
1743             asyncResp->res.body() = std::move(output);
1744             },
1745             "xyz.openbmc_project.Logging",
1746             "/xyz/openbmc_project/logging/entry/" + entryID,
1747             "xyz.openbmc_project.Logging.Entry", "GetEntry");
1748         });
1749 }
1750 
1751 constexpr const char* hostLoggerFolderPath = "/var/log/console";
1752 
1753 inline bool
1754     getHostLoggerFiles(const std::string& hostLoggerFilePath,
1755                        std::vector<std::filesystem::path>& hostLoggerFiles)
1756 {
1757     std::error_code ec;
1758     std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec);
1759     if (ec)
1760     {
1761         BMCWEB_LOG_ERROR << ec.message();
1762         return false;
1763     }
1764     for (const std::filesystem::directory_entry& it : logPath)
1765     {
1766         std::string filename = it.path().filename();
1767         // Prefix of each log files is "log". Find the file and save the
1768         // path
1769         if (boost::starts_with(filename, "log"))
1770         {
1771             hostLoggerFiles.emplace_back(it.path());
1772         }
1773     }
1774     // As the log files rotate, they are appended with a ".#" that is higher for
1775     // the older logs. Since we start from oldest logs, sort the name in
1776     // descending order.
1777     std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(),
1778               AlphanumLess<std::string>());
1779 
1780     return true;
1781 }
1782 
1783 inline bool
1784     getHostLoggerEntries(std::vector<std::filesystem::path>& hostLoggerFiles,
1785                          uint64_t skip, uint64_t top,
1786                          std::vector<std::string>& logEntries, size_t& logCount)
1787 {
1788     GzFileReader logFile;
1789 
1790     // Go though all log files and expose host logs.
1791     for (const std::filesystem::path& it : hostLoggerFiles)
1792     {
1793         if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount))
1794         {
1795             BMCWEB_LOG_ERROR << "fail to expose host logs";
1796             return false;
1797         }
1798     }
1799     // Get lastMessage from constructor by getter
1800     std::string lastMessage = logFile.getLastMessage();
1801     if (!lastMessage.empty())
1802     {
1803         logCount++;
1804         if (logCount > skip && logCount <= (skip + top))
1805         {
1806             logEntries.push_back(lastMessage);
1807         }
1808     }
1809     return true;
1810 }
1811 
1812 inline void fillHostLoggerEntryJson(const std::string& logEntryID,
1813                                     const std::string& msg,
1814                                     nlohmann::json& logEntryJson)
1815 {
1816     // Fill in the log entry with the gathered data.
1817     logEntryJson = {
1818         {"@odata.type", "#LogEntry.v1_4_0.LogEntry"},
1819         {"@odata.id",
1820          "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/" +
1821              logEntryID},
1822         {"Name", "Host Logger Entry"},
1823         {"Id", logEntryID},
1824         {"Message", msg},
1825         {"EntryType", "Oem"},
1826         {"Severity", "OK"},
1827         {"OemRecordFormat", "Host Logger Entry"}};
1828 }
1829 
1830 inline void requestRoutesSystemHostLogger(App& app)
1831 {
1832     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/HostLogger/")
1833         .privileges(redfish::privileges::getLogService)
1834         .methods(boost::beast::http::verb::get)(
1835             [&app](const crow::Request& req,
1836                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1837         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1838         {
1839             return;
1840         }
1841         asyncResp->res.jsonValue["@odata.id"] =
1842             "/redfish/v1/Systems/system/LogServices/HostLogger";
1843         asyncResp->res.jsonValue["@odata.type"] =
1844             "#LogService.v1_1_0.LogService";
1845         asyncResp->res.jsonValue["Name"] = "Host Logger Service";
1846         asyncResp->res.jsonValue["Description"] = "Host Logger Service";
1847         asyncResp->res.jsonValue["Id"] = "HostLogger";
1848         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
1849             "/redfish/v1/Systems/system/LogServices/HostLogger/Entries";
1850         });
1851 }
1852 
1853 inline void requestRoutesSystemHostLoggerCollection(App& app)
1854 {
1855     BMCWEB_ROUTE(app,
1856                  "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/")
1857         .privileges(redfish::privileges::getLogEntry)
1858         .methods(boost::beast::http::verb::get)(
1859             [&app](const crow::Request& req,
1860                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1861         query_param::QueryCapabilities capabilities = {
1862             .canDelegateTop = true,
1863             .canDelegateSkip = true,
1864         };
1865         query_param::Query delegatedQuery;
1866         if (!redfish::setUpRedfishRouteWithDelegation(
1867                 app, req, asyncResp, delegatedQuery, capabilities))
1868         {
1869             return;
1870         }
1871         asyncResp->res.jsonValue["@odata.id"] =
1872             "/redfish/v1/Systems/system/LogServices/HostLogger/Entries";
1873         asyncResp->res.jsonValue["@odata.type"] =
1874             "#LogEntryCollection.LogEntryCollection";
1875         asyncResp->res.jsonValue["Name"] = "HostLogger Entries";
1876         asyncResp->res.jsonValue["Description"] =
1877             "Collection of HostLogger Entries";
1878         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
1879         logEntryArray = nlohmann::json::array();
1880         asyncResp->res.jsonValue["Members@odata.count"] = 0;
1881 
1882         std::vector<std::filesystem::path> hostLoggerFiles;
1883         if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
1884         {
1885             BMCWEB_LOG_ERROR << "fail to get host log file path";
1886             return;
1887         }
1888 
1889         size_t logCount = 0;
1890         // This vector only store the entries we want to expose that
1891         // control by skip and top.
1892         std::vector<std::string> logEntries;
1893         if (!getHostLoggerEntries(hostLoggerFiles, delegatedQuery.skip,
1894                                   delegatedQuery.top, logEntries, logCount))
1895         {
1896             messages::internalError(asyncResp->res);
1897             return;
1898         }
1899         // If vector is empty, that means skip value larger than total
1900         // log count
1901         if (logEntries.empty())
1902         {
1903             asyncResp->res.jsonValue["Members@odata.count"] = logCount;
1904             return;
1905         }
1906         if (!logEntries.empty())
1907         {
1908             for (size_t i = 0; i < logEntries.size(); i++)
1909             {
1910                 logEntryArray.push_back({});
1911                 nlohmann::json& hostLogEntry = logEntryArray.back();
1912                 fillHostLoggerEntryJson(std::to_string(delegatedQuery.skip + i),
1913                                         logEntries[i], hostLogEntry);
1914             }
1915 
1916             asyncResp->res.jsonValue["Members@odata.count"] = logCount;
1917             if (delegatedQuery.skip + delegatedQuery.top < logCount)
1918             {
1919                 asyncResp->res.jsonValue["Members@odata.nextLink"] =
1920                     "/redfish/v1/Systems/system/LogServices/HostLogger/Entries?$skip=" +
1921                     std::to_string(delegatedQuery.skip + delegatedQuery.top);
1922             }
1923         }
1924         });
1925 }
1926 
1927 inline void requestRoutesSystemHostLoggerLogEntry(App& app)
1928 {
1929     BMCWEB_ROUTE(
1930         app, "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/<str>/")
1931         .privileges(redfish::privileges::getLogEntry)
1932         .methods(boost::beast::http::verb::get)(
1933             [&app](const crow::Request& req,
1934                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1935                    const std::string& param) {
1936         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1937         {
1938             return;
1939         }
1940         const std::string& targetID = param;
1941 
1942         uint64_t idInt = 0;
1943 
1944         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1945         const char* end = targetID.data() + targetID.size();
1946 
1947         auto [ptr, ec] = std::from_chars(targetID.data(), end, idInt);
1948         if (ec == std::errc::invalid_argument)
1949         {
1950             messages::resourceMissingAtURI(asyncResp->res, req.urlView);
1951             return;
1952         }
1953         if (ec == std::errc::result_out_of_range)
1954         {
1955             messages::resourceMissingAtURI(asyncResp->res, req.urlView);
1956             return;
1957         }
1958 
1959         std::vector<std::filesystem::path> hostLoggerFiles;
1960         if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles))
1961         {
1962             BMCWEB_LOG_ERROR << "fail to get host log file path";
1963             return;
1964         }
1965 
1966         size_t logCount = 0;
1967         uint64_t top = 1;
1968         std::vector<std::string> logEntries;
1969         // We can get specific entry by skip and top. For example, if we
1970         // want to get nth entry, we can set skip = n-1 and top = 1 to
1971         // get that entry
1972         if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries,
1973                                   logCount))
1974         {
1975             messages::internalError(asyncResp->res);
1976             return;
1977         }
1978 
1979         if (!logEntries.empty())
1980         {
1981             fillHostLoggerEntryJson(targetID, logEntries[0],
1982                                     asyncResp->res.jsonValue);
1983             return;
1984         }
1985 
1986         // Requested ID was not found
1987         messages::resourceMissingAtURI(asyncResp->res, req.urlView);
1988         });
1989 }
1990 
1991 constexpr char const* dumpManagerIface =
1992     "xyz.openbmc_project.Collection.DeleteAll";
1993 inline void handleLogServicesCollectionGet(
1994     crow::App& app, const crow::Request& req,
1995     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1996 {
1997     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1998     {
1999         return;
2000     }
2001     // Collections don't include the static data added by SubRoute
2002     // because it has a duplicate entry for members
2003     asyncResp->res.jsonValue["@odata.type"] =
2004         "#LogServiceCollection.LogServiceCollection";
2005     asyncResp->res.jsonValue["@odata.id"] =
2006         "/redfish/v1/Managers/bmc/LogServices";
2007     asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection";
2008     asyncResp->res.jsonValue["Description"] =
2009         "Collection of LogServices for this Manager";
2010     nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"];
2011     logServiceArray = nlohmann::json::array();
2012 
2013 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL
2014     logServiceArray.push_back(
2015         {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}});
2016 #endif
2017 
2018     asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size();
2019 
2020 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG
2021     auto respHandler =
2022         [asyncResp](
2023             const boost::system::error_code ec,
2024             const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) {
2025         if (ec)
2026         {
2027             BMCWEB_LOG_ERROR
2028                 << "handleLogServicesCollectionGet respHandler got error "
2029                 << ec;
2030             // Assume that getting an error simply means there are no dump
2031             // LogServices. Return without adding any error response.
2032             return;
2033         }
2034 
2035         nlohmann::json& logServiceArrayLocal =
2036             asyncResp->res.jsonValue["Members"];
2037 
2038         for (const std::string& path : subTreePaths)
2039         {
2040             if (path == "/xyz/openbmc_project/dump/bmc")
2041             {
2042                 logServiceArrayLocal.push_back(
2043                     {{"@odata.id",
2044                       "/redfish/v1/Managers/bmc/LogServices/Dump"}});
2045             }
2046             else if (path == "/xyz/openbmc_project/dump/faultlog")
2047             {
2048                 logServiceArrayLocal.push_back(
2049                     {{"@odata.id",
2050                       "/redfish/v1/Managers/bmc/LogServices/FaultLog"}});
2051             }
2052         }
2053 
2054         asyncResp->res.jsonValue["Members@odata.count"] =
2055             logServiceArrayLocal.size();
2056     };
2057 
2058     crow::connections::systemBus->async_method_call(
2059         respHandler, "xyz.openbmc_project.ObjectMapper",
2060         "/xyz/openbmc_project/object_mapper",
2061         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
2062         "/xyz/openbmc_project/dump", 0,
2063         std::array<const char*, 1>{dumpManagerIface});
2064 #endif
2065 }
2066 
2067 inline void requestRoutesBMCLogServiceCollection(App& app)
2068 {
2069     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/")
2070         .privileges(redfish::privileges::getLogServiceCollection)
2071         .methods(boost::beast::http::verb::get)(
2072             std::bind_front(handleLogServicesCollectionGet, std::ref(app)));
2073 }
2074 
2075 inline void requestRoutesBMCJournalLogService(App& app)
2076 {
2077     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/")
2078         .privileges(redfish::privileges::getLogService)
2079         .methods(boost::beast::http::verb::get)(
2080             [&app](const crow::Request& req,
2081                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2082         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2083         {
2084             return;
2085         }
2086         asyncResp->res.jsonValue["@odata.type"] =
2087             "#LogService.v1_1_0.LogService";
2088         asyncResp->res.jsonValue["@odata.id"] =
2089             "/redfish/v1/Managers/bmc/LogServices/Journal";
2090         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
2091         asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
2092         asyncResp->res.jsonValue["Id"] = "BMC Journal";
2093         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2094 
2095         std::pair<std::string, std::string> redfishDateTimeOffset =
2096             crow::utility::getDateTimeOffsetNow();
2097         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2098         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2099             redfishDateTimeOffset.second;
2100 
2101         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
2102             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
2103         });
2104 }
2105 
2106 static int
2107     fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID,
2108                                sd_journal* journal,
2109                                nlohmann::json::object_t& bmcJournalLogEntryJson)
2110 {
2111     // Get the Log Entry contents
2112     int ret = 0;
2113 
2114     std::string message;
2115     std::string_view syslogID;
2116     ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID);
2117     if (ret < 0)
2118     {
2119         BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: "
2120                          << strerror(-ret);
2121     }
2122     if (!syslogID.empty())
2123     {
2124         message += std::string(syslogID) + ": ";
2125     }
2126 
2127     std::string_view msg;
2128     ret = getJournalMetadata(journal, "MESSAGE", msg);
2129     if (ret < 0)
2130     {
2131         BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret);
2132         return 1;
2133     }
2134     message += std::string(msg);
2135 
2136     // Get the severity from the PRIORITY field
2137     long int severity = 8; // Default to an invalid priority
2138     ret = getJournalMetadata(journal, "PRIORITY", 10, severity);
2139     if (ret < 0)
2140     {
2141         BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret);
2142     }
2143 
2144     // Get the Created time from the timestamp
2145     std::string entryTimeStr;
2146     if (!getEntryTimestamp(journal, entryTimeStr))
2147     {
2148         return 1;
2149     }
2150 
2151     // Fill in the log entry with the gathered data
2152     bmcJournalLogEntryJson = {
2153         {"@odata.type", "#LogEntry.v1_8_0.LogEntry"},
2154         {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" +
2155                           bmcJournalLogEntryID},
2156         {"Name", "BMC Journal Entry"},
2157         {"Id", bmcJournalLogEntryID},
2158         {"Message", std::move(message)},
2159         {"EntryType", "Oem"},
2160         {"Severity", severity <= 2   ? "Critical"
2161                      : severity <= 4 ? "Warning"
2162                                      : "OK"},
2163         {"OemRecordFormat", "BMC Journal Entry"},
2164         {"Created", std::move(entryTimeStr)}};
2165     return 0;
2166 }
2167 
2168 inline void requestRoutesBMCJournalLogEntryCollection(App& app)
2169 {
2170     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/")
2171         .privileges(redfish::privileges::getLogEntryCollection)
2172         .methods(boost::beast::http::verb::get)(
2173             [&app](const crow::Request& req,
2174                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2175         query_param::QueryCapabilities capabilities = {
2176             .canDelegateTop = true,
2177             .canDelegateSkip = true,
2178         };
2179         query_param::Query delegatedQuery;
2180         if (!redfish::setUpRedfishRouteWithDelegation(
2181                 app, req, asyncResp, delegatedQuery, capabilities))
2182         {
2183             return;
2184         }
2185         // Collections don't include the static data added by SubRoute
2186         // because it has a duplicate entry for members
2187         asyncResp->res.jsonValue["@odata.type"] =
2188             "#LogEntryCollection.LogEntryCollection";
2189         asyncResp->res.jsonValue["@odata.id"] =
2190             "/redfish/v1/Managers/bmc/LogServices/Journal/Entries";
2191         asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
2192         asyncResp->res.jsonValue["Description"] =
2193             "Collection of BMC Journal Entries";
2194         nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
2195         logEntryArray = nlohmann::json::array();
2196 
2197         // Go through the journal and use the timestamp to create a
2198         // unique ID for each entry
2199         sd_journal* journalTmp = nullptr;
2200         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
2201         if (ret < 0)
2202         {
2203             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
2204             messages::internalError(asyncResp->res);
2205             return;
2206         }
2207         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
2208             journalTmp, sd_journal_close);
2209         journalTmp = nullptr;
2210         uint64_t entryCount = 0;
2211         // Reset the unique ID on the first entry
2212         bool firstEntry = true;
2213         SD_JOURNAL_FOREACH(journal.get())
2214         {
2215             entryCount++;
2216             // Handle paging using skip (number of entries to skip from
2217             // the start) and top (number of entries to display)
2218             if (entryCount <= delegatedQuery.skip ||
2219                 entryCount > delegatedQuery.skip + delegatedQuery.top)
2220             {
2221                 continue;
2222             }
2223 
2224             std::string idStr;
2225             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
2226             {
2227                 continue;
2228             }
2229             firstEntry = false;
2230 
2231             nlohmann::json::object_t bmcJournalLogEntry;
2232             if (fillBMCJournalLogEntryJson(idStr, journal.get(),
2233                                            bmcJournalLogEntry) != 0)
2234             {
2235                 messages::internalError(asyncResp->res);
2236                 return;
2237             }
2238             logEntryArray.push_back(std::move(bmcJournalLogEntry));
2239         }
2240         asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
2241         if (delegatedQuery.skip + delegatedQuery.top < entryCount)
2242         {
2243             asyncResp->res.jsonValue["Members@odata.nextLink"] =
2244                 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" +
2245                 std::to_string(delegatedQuery.skip + delegatedQuery.top);
2246         }
2247         });
2248 }
2249 
2250 inline void requestRoutesBMCJournalLogEntry(App& app)
2251 {
2252     BMCWEB_ROUTE(app,
2253                  "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/")
2254         .privileges(redfish::privileges::getLogEntry)
2255         .methods(boost::beast::http::verb::get)(
2256             [&app](const crow::Request& req,
2257                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2258                    const std::string& entryID) {
2259         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2260         {
2261             return;
2262         }
2263         // Convert the unique ID back to a timestamp to find the entry
2264         uint64_t ts = 0;
2265         uint64_t index = 0;
2266         if (!getTimestampFromID(asyncResp, entryID, ts, index))
2267         {
2268             return;
2269         }
2270 
2271         sd_journal* journalTmp = nullptr;
2272         int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
2273         if (ret < 0)
2274         {
2275             BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret);
2276             messages::internalError(asyncResp->res);
2277             return;
2278         }
2279         std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
2280             journalTmp, sd_journal_close);
2281         journalTmp = nullptr;
2282         // Go to the timestamp in the log and move to the entry at the
2283         // index tracking the unique ID
2284         std::string idStr;
2285         bool firstEntry = true;
2286         ret = sd_journal_seek_realtime_usec(journal.get(), ts);
2287         if (ret < 0)
2288         {
2289             BMCWEB_LOG_ERROR << "failed to seek to an entry in journal"
2290                              << strerror(-ret);
2291             messages::internalError(asyncResp->res);
2292             return;
2293         }
2294         for (uint64_t i = 0; i <= index; i++)
2295         {
2296             sd_journal_next(journal.get());
2297             if (!getUniqueEntryID(journal.get(), idStr, firstEntry))
2298             {
2299                 messages::internalError(asyncResp->res);
2300                 return;
2301             }
2302             firstEntry = false;
2303         }
2304         // Confirm that the entry ID matches what was requested
2305         if (idStr != entryID)
2306         {
2307             messages::resourceMissingAtURI(asyncResp->res, req.urlView);
2308             return;
2309         }
2310 
2311         nlohmann::json::object_t bmcJournalLogEntry;
2312         if (fillBMCJournalLogEntryJson(entryID, journal.get(),
2313                                        bmcJournalLogEntry) != 0)
2314         {
2315             messages::internalError(asyncResp->res);
2316             return;
2317         }
2318         asyncResp->res.jsonValue.update(bmcJournalLogEntry);
2319         });
2320 }
2321 
2322 inline void
2323     getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2324                        const std::string& dumpType)
2325 {
2326     std::string dumpPath;
2327     std::string overWritePolicy;
2328     bool collectDiagnosticDataSupported = false;
2329 
2330     if (dumpType == "BMC")
2331     {
2332         dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump";
2333         overWritePolicy = "WrapsWhenFull";
2334         collectDiagnosticDataSupported = true;
2335     }
2336     else if (dumpType == "FaultLog")
2337     {
2338         dumpPath = "/redfish/v1/Managers/bmc/LogServices/FaultLog";
2339         overWritePolicy = "Unknown";
2340         collectDiagnosticDataSupported = false;
2341     }
2342     else if (dumpType == "System")
2343     {
2344         dumpPath = "/redfish/v1/Systems/system/LogServices/Dump";
2345         overWritePolicy = "WrapsWhenFull";
2346         collectDiagnosticDataSupported = true;
2347     }
2348     else
2349     {
2350         BMCWEB_LOG_ERROR << "getDumpServiceInfo() invalid dump type: "
2351                          << dumpType;
2352         messages::internalError(asyncResp->res);
2353         return;
2354     }
2355 
2356     asyncResp->res.jsonValue["@odata.id"] = dumpPath;
2357     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
2358     asyncResp->res.jsonValue["Name"] = "Dump LogService";
2359     asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService";
2360     asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
2361     asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy);
2362 
2363     std::pair<std::string, std::string> redfishDateTimeOffset =
2364         crow::utility::getDateTimeOffsetNow();
2365     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2366     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2367         redfishDateTimeOffset.second;
2368 
2369     asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries";
2370     asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
2371         dumpPath + "/Actions/LogService.ClearLog";
2372 
2373     if (collectDiagnosticDataSupported)
2374     {
2375         asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2376                                 ["target"] =
2377             dumpPath + "/Actions/LogService.CollectDiagnosticData";
2378     }
2379 }
2380 
2381 inline void handleLogServicesDumpServiceGet(
2382     crow::App& app, const std::string& dumpType, const crow::Request& req,
2383     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2384 {
2385     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2386     {
2387         return;
2388     }
2389     getDumpServiceInfo(asyncResp, dumpType);
2390 }
2391 
2392 inline void handleLogServicesDumpEntriesCollectionGet(
2393     crow::App& app, const std::string& dumpType, const crow::Request& req,
2394     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2395 {
2396     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2397     {
2398         return;
2399     }
2400     getDumpEntryCollection(asyncResp, dumpType);
2401 }
2402 
2403 inline void handleLogServicesDumpEntryGet(
2404     crow::App& app, const std::string& dumpType, const crow::Request& req,
2405     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2406     const std::string& dumpId)
2407 {
2408     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2409     {
2410         return;
2411     }
2412     getDumpEntryById(asyncResp, dumpId, dumpType);
2413 }
2414 
2415 inline void handleLogServicesDumpEntryDelete(
2416     crow::App& app, const std::string& dumpType, const crow::Request& req,
2417     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2418     const std::string& dumpId)
2419 {
2420     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2421     {
2422         return;
2423     }
2424     deleteDumpEntry(asyncResp, dumpId, dumpType);
2425 }
2426 
2427 inline void handleLogServicesDumpCollectDiagnosticDataPost(
2428     crow::App& app, const std::string& dumpType, const crow::Request& req,
2429     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2430 {
2431     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2432     {
2433         return;
2434     }
2435     createDump(asyncResp, req, dumpType);
2436 }
2437 
2438 inline void handleLogServicesDumpClearLogPost(
2439     crow::App& app, const std::string& dumpType, const crow::Request& req,
2440     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2441 {
2442     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2443     {
2444         return;
2445     }
2446     clearDump(asyncResp, dumpType);
2447 }
2448 
2449 inline void requestRoutesBMCDumpService(App& app)
2450 {
2451     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/")
2452         .privileges(redfish::privileges::getLogService)
2453         .methods(boost::beast::http::verb::get)(std::bind_front(
2454             handleLogServicesDumpServiceGet, std::ref(app), "BMC"));
2455 }
2456 
2457 inline void requestRoutesBMCDumpEntryCollection(App& app)
2458 {
2459     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/")
2460         .privileges(redfish::privileges::getLogEntryCollection)
2461         .methods(boost::beast::http::verb::get)(std::bind_front(
2462             handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC"));
2463 }
2464 
2465 inline void requestRoutesBMCDumpEntry(App& app)
2466 {
2467     BMCWEB_ROUTE(app,
2468                  "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
2469         .privileges(redfish::privileges::getLogEntry)
2470         .methods(boost::beast::http::verb::get)(std::bind_front(
2471             handleLogServicesDumpEntryGet, std::ref(app), "BMC"));
2472 
2473     BMCWEB_ROUTE(app,
2474                  "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/")
2475         .privileges(redfish::privileges::deleteLogEntry)
2476         .methods(boost::beast::http::verb::delete_)(std::bind_front(
2477             handleLogServicesDumpEntryDelete, std::ref(app), "BMC"));
2478 }
2479 
2480 inline void requestRoutesBMCDumpCreate(App& app)
2481 {
2482     BMCWEB_ROUTE(
2483         app,
2484         "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2485         .privileges(redfish::privileges::postLogService)
2486         .methods(boost::beast::http::verb::post)(
2487             std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost,
2488                             std::ref(app), "BMC"));
2489 }
2490 
2491 inline void requestRoutesBMCDumpClear(App& app)
2492 {
2493     BMCWEB_ROUTE(
2494         app,
2495         "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/")
2496         .privileges(redfish::privileges::postLogService)
2497         .methods(boost::beast::http::verb::post)(std::bind_front(
2498             handleLogServicesDumpClearLogPost, std::ref(app), "BMC"));
2499 }
2500 
2501 inline void requestRoutesFaultLogDumpService(App& app)
2502 {
2503     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/")
2504         .privileges(redfish::privileges::getLogService)
2505         .methods(boost::beast::http::verb::get)(std::bind_front(
2506             handleLogServicesDumpServiceGet, std::ref(app), "FaultLog"));
2507 }
2508 
2509 inline void requestRoutesFaultLogDumpEntryCollection(App& app)
2510 {
2511     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/")
2512         .privileges(redfish::privileges::getLogEntryCollection)
2513         .methods(boost::beast::http::verb::get)(
2514             std::bind_front(handleLogServicesDumpEntriesCollectionGet,
2515                             std::ref(app), "FaultLog"));
2516 }
2517 
2518 inline void requestRoutesFaultLogDumpEntry(App& app)
2519 {
2520     BMCWEB_ROUTE(app,
2521                  "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/")
2522         .privileges(redfish::privileges::getLogEntry)
2523         .methods(boost::beast::http::verb::get)(std::bind_front(
2524             handleLogServicesDumpEntryGet, std::ref(app), "FaultLog"));
2525 
2526     BMCWEB_ROUTE(app,
2527                  "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/")
2528         .privileges(redfish::privileges::deleteLogEntry)
2529         .methods(boost::beast::http::verb::delete_)(std::bind_front(
2530             handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog"));
2531 }
2532 
2533 inline void requestRoutesFaultLogDumpClear(App& app)
2534 {
2535     BMCWEB_ROUTE(
2536         app,
2537         "/redfish/v1/Managers/bmc/LogServices/FaultLog/Actions/LogService.ClearLog/")
2538         .privileges(redfish::privileges::postLogService)
2539         .methods(boost::beast::http::verb::post)(std::bind_front(
2540             handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog"));
2541 }
2542 
2543 inline void requestRoutesSystemDumpService(App& app)
2544 {
2545     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/")
2546         .privileges(redfish::privileges::getLogService)
2547         .methods(boost::beast::http::verb::get)(
2548             [&app](const crow::Request& req,
2549                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2550         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2551         {
2552             return;
2553         }
2554         asyncResp->res.jsonValue["@odata.id"] =
2555             "/redfish/v1/Systems/system/LogServices/Dump";
2556         asyncResp->res.jsonValue["@odata.type"] =
2557             "#LogService.v1_2_0.LogService";
2558         asyncResp->res.jsonValue["Name"] = "Dump LogService";
2559         asyncResp->res.jsonValue["Description"] = "System Dump LogService";
2560         asyncResp->res.jsonValue["Id"] = "Dump";
2561         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2562 
2563         std::pair<std::string, std::string> redfishDateTimeOffset =
2564             crow::utility::getDateTimeOffsetNow();
2565         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2566         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2567             redfishDateTimeOffset.second;
2568 
2569         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
2570             "/redfish/v1/Systems/system/LogServices/Dump/Entries";
2571         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
2572             "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog";
2573 
2574         asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2575                                 ["target"] =
2576             "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData";
2577         });
2578 }
2579 
2580 inline void requestRoutesSystemDumpEntryCollection(App& app)
2581 {
2582 
2583     /**
2584      * Functions triggers appropriate requests on DBus
2585      */
2586     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/")
2587         .privileges(redfish::privileges::getLogEntryCollection)
2588         .methods(boost::beast::http::verb::get)(
2589             [&app](const crow::Request& req,
2590                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2591         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2592         {
2593             return;
2594         }
2595         getDumpEntryCollection(asyncResp, "System");
2596         });
2597 }
2598 
2599 inline void requestRoutesSystemDumpEntry(App& app)
2600 {
2601     BMCWEB_ROUTE(app,
2602                  "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/")
2603         .privileges(redfish::privileges::getLogEntry)
2604 
2605         .methods(boost::beast::http::verb::get)(
2606             [&app](const crow::Request& req,
2607                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2608                    const std::string& param) {
2609         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2610         {
2611             return;
2612         }
2613         getDumpEntryById(asyncResp, param, "System");
2614         });
2615 
2616     BMCWEB_ROUTE(app,
2617                  "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/")
2618         .privileges(redfish::privileges::deleteLogEntry)
2619         .methods(boost::beast::http::verb::delete_)(
2620             [&app](const crow::Request& req,
2621                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2622                    const std::string& param) {
2623         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2624         {
2625             return;
2626         }
2627         deleteDumpEntry(asyncResp, param, "system");
2628         });
2629 }
2630 
2631 inline void requestRoutesSystemDumpCreate(App& app)
2632 {
2633     BMCWEB_ROUTE(
2634         app,
2635         "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData/")
2636         .privileges(redfish::privileges::postLogService)
2637         .methods(boost::beast::http::verb::post)(
2638             [&app](const crow::Request& req,
2639                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2640         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2641         {
2642             return;
2643         }
2644         createDump(asyncResp, req, "System");
2645         });
2646 }
2647 
2648 inline void requestRoutesSystemDumpClear(App& app)
2649 {
2650     BMCWEB_ROUTE(
2651         app,
2652         "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog/")
2653         .privileges(redfish::privileges::postLogService)
2654         .methods(boost::beast::http::verb::post)(
2655             [&app](const crow::Request& req,
2656                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
2657 
2658             {
2659         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2660         {
2661             return;
2662         }
2663         clearDump(asyncResp, "System");
2664         });
2665 }
2666 
2667 inline void requestRoutesCrashdumpService(App& app)
2668 {
2669     // Note: Deviated from redfish privilege registry for GET & HEAD
2670     // method for security reasons.
2671     /**
2672      * Functions triggers appropriate requests on DBus
2673      */
2674     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/")
2675         // This is incorrect, should be:
2676         //.privileges(redfish::privileges::getLogService)
2677         .privileges({{"ConfigureManager"}})
2678         .methods(boost::beast::http::verb::get)(
2679             [&app](const crow::Request& req,
2680                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2681         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2682         {
2683             return;
2684         }
2685         // Copy over the static data to include the entries added by
2686         // SubRoute
2687         asyncResp->res.jsonValue["@odata.id"] =
2688             "/redfish/v1/Systems/system/LogServices/Crashdump";
2689         asyncResp->res.jsonValue["@odata.type"] =
2690             "#LogService.v1_2_0.LogService";
2691         asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service";
2692         asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service";
2693         asyncResp->res.jsonValue["Id"] = "Oem Crashdump";
2694         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
2695         asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3;
2696 
2697         std::pair<std::string, std::string> redfishDateTimeOffset =
2698             crow::utility::getDateTimeOffsetNow();
2699         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
2700         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
2701             redfishDateTimeOffset.second;
2702 
2703         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
2704             "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
2705         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] =
2706             "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog";
2707         asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"]
2708                                 ["target"] =
2709             "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData";
2710         });
2711 }
2712 
2713 void inline requestRoutesCrashdumpClear(App& app)
2714 {
2715     BMCWEB_ROUTE(
2716         app,
2717         "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog/")
2718         // This is incorrect, should be:
2719         //.privileges(redfish::privileges::postLogService)
2720         .privileges({{"ConfigureComponents"}})
2721         .methods(boost::beast::http::verb::post)(
2722             [&app](const crow::Request& req,
2723                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2724         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2725         {
2726             return;
2727         }
2728         crow::connections::systemBus->async_method_call(
2729             [asyncResp](const boost::system::error_code ec,
2730                         const std::string&) {
2731             if (ec)
2732             {
2733                 messages::internalError(asyncResp->res);
2734                 return;
2735             }
2736             messages::success(asyncResp->res);
2737             },
2738             crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll");
2739         });
2740 }
2741 
2742 static void
2743     logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2744                       const std::string& logID, nlohmann::json& logEntryJson)
2745 {
2746     auto getStoredLogCallback =
2747         [asyncResp, logID,
2748          &logEntryJson](const boost::system::error_code ec,
2749                         const dbus::utility::DBusPropertiesMap& params) {
2750         if (ec)
2751         {
2752             BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
2753             if (ec.value() ==
2754                 boost::system::linux_error::bad_request_descriptor)
2755             {
2756                 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
2757             }
2758             else
2759             {
2760                 messages::internalError(asyncResp->res);
2761             }
2762             return;
2763         }
2764 
2765         std::string timestamp{};
2766         std::string filename{};
2767         std::string logfile{};
2768         parseCrashdumpParameters(params, filename, timestamp, logfile);
2769 
2770         if (filename.empty() || timestamp.empty())
2771         {
2772             messages::resourceMissingAtURI(asyncResp->res,
2773                                            crow::utility::urlFromPieces(logID));
2774             return;
2775         }
2776 
2777         std::string crashdumpURI =
2778             "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
2779             logID + "/" + filename;
2780         nlohmann::json::object_t logEntry = {
2781             {"@odata.type", "#LogEntry.v1_7_0.LogEntry"},
2782             {"@odata.id",
2783              "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
2784                  logID},
2785             {"Name", "CPU Crashdump"},
2786             {"Id", logID},
2787             {"EntryType", "Oem"},
2788             {"AdditionalDataURI", std::move(crashdumpURI)},
2789             {"DiagnosticDataType", "OEM"},
2790             {"OEMDiagnosticDataType", "PECICrashdump"},
2791             {"Created", std::move(timestamp)}};
2792 
2793         // If logEntryJson references an array of LogEntry resources
2794         // ('Members' list), then push this as a new entry, otherwise set it
2795         // directly
2796         if (logEntryJson.is_array())
2797         {
2798             logEntryJson.push_back(logEntry);
2799             asyncResp->res.jsonValue["Members@odata.count"] =
2800                 logEntryJson.size();
2801         }
2802         else
2803         {
2804             logEntryJson.update(logEntry);
2805         }
2806     };
2807     crow::connections::systemBus->async_method_call(
2808         std::move(getStoredLogCallback), crashdumpObject,
2809         crashdumpPath + std::string("/") + logID,
2810         "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
2811 }
2812 
2813 inline void requestRoutesCrashdumpEntryCollection(App& app)
2814 {
2815     // Note: Deviated from redfish privilege registry for GET & HEAD
2816     // method for security reasons.
2817     /**
2818      * Functions triggers appropriate requests on DBus
2819      */
2820     BMCWEB_ROUTE(app,
2821                  "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/")
2822         // This is incorrect, should be.
2823         //.privileges(redfish::privileges::postLogEntryCollection)
2824         .privileges({{"ConfigureComponents"}})
2825         .methods(boost::beast::http::verb::get)(
2826             [&app](const crow::Request& req,
2827                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
2828         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2829         {
2830             return;
2831         }
2832         crow::connections::systemBus->async_method_call(
2833             [asyncResp](const boost::system::error_code ec,
2834                         const std::vector<std::string>& resp) {
2835             if (ec)
2836             {
2837                 if (ec.value() !=
2838                     boost::system::errc::no_such_file_or_directory)
2839                 {
2840                     BMCWEB_LOG_DEBUG << "failed to get entries ec: "
2841                                      << ec.message();
2842                     messages::internalError(asyncResp->res);
2843                     return;
2844                 }
2845             }
2846             asyncResp->res.jsonValue["@odata.type"] =
2847                 "#LogEntryCollection.LogEntryCollection";
2848             asyncResp->res.jsonValue["@odata.id"] =
2849                 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries";
2850             asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries";
2851             asyncResp->res.jsonValue["Description"] =
2852                 "Collection of Crashdump Entries";
2853             asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
2854             asyncResp->res.jsonValue["Members@odata.count"] = 0;
2855 
2856             for (const std::string& path : resp)
2857             {
2858                 const sdbusplus::message::object_path objPath(path);
2859                 // Get the log ID
2860                 std::string logID = objPath.filename();
2861                 if (logID.empty())
2862                 {
2863                     continue;
2864                 }
2865                 // Add the log entry to the array
2866                 logCrashdumpEntry(asyncResp, logID,
2867                                   asyncResp->res.jsonValue["Members"]);
2868             }
2869             },
2870             "xyz.openbmc_project.ObjectMapper",
2871             "/xyz/openbmc_project/object_mapper",
2872             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0,
2873             std::array<const char*, 1>{crashdumpInterface});
2874         });
2875 }
2876 
2877 inline void requestRoutesCrashdumpEntry(App& app)
2878 {
2879     // Note: Deviated from redfish privilege registry for GET & HEAD
2880     // method for security reasons.
2881 
2882     BMCWEB_ROUTE(
2883         app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/")
2884         // this is incorrect, should be
2885         // .privileges(redfish::privileges::getLogEntry)
2886         .privileges({{"ConfigureComponents"}})
2887         .methods(boost::beast::http::verb::get)(
2888             [&app](const crow::Request& req,
2889                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2890                    const std::string& param) {
2891         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2892         {
2893             return;
2894         }
2895         const std::string& logID = param;
2896         logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue);
2897         });
2898 }
2899 
2900 inline void requestRoutesCrashdumpFile(App& app)
2901 {
2902     // Note: Deviated from redfish privilege registry for GET & HEAD
2903     // method for security reasons.
2904     BMCWEB_ROUTE(
2905         app,
2906         "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/<str>/")
2907         .privileges(redfish::privileges::getLogEntry)
2908         .methods(boost::beast::http::verb::get)(
2909             [&app](const crow::Request& req,
2910                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2911                    const std::string& logID, const std::string& fileName) {
2912         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2913         {
2914             return;
2915         }
2916         auto getStoredLogCallback =
2917             [asyncResp, logID, fileName, url(boost::urls::url(req.urlView))](
2918                 const boost::system::error_code ec,
2919                 const std::vector<
2920                     std::pair<std::string, dbus::utility::DbusVariantType>>&
2921                     resp) {
2922             if (ec)
2923             {
2924                 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message();
2925                 messages::internalError(asyncResp->res);
2926                 return;
2927             }
2928 
2929             std::string dbusFilename{};
2930             std::string dbusTimestamp{};
2931             std::string dbusFilepath{};
2932 
2933             parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp,
2934                                      dbusFilepath);
2935 
2936             if (dbusFilename.empty() || dbusTimestamp.empty() ||
2937                 dbusFilepath.empty())
2938             {
2939                 messages::resourceMissingAtURI(asyncResp->res, url);
2940                 return;
2941             }
2942 
2943             // Verify the file name parameter is correct
2944             if (fileName != dbusFilename)
2945             {
2946                 messages::resourceMissingAtURI(asyncResp->res, url);
2947                 return;
2948             }
2949 
2950             if (!std::filesystem::exists(dbusFilepath))
2951             {
2952                 messages::resourceMissingAtURI(asyncResp->res, url);
2953                 return;
2954             }
2955             std::ifstream ifs(dbusFilepath, std::ios::in | std::ios::binary);
2956             asyncResp->res.body() =
2957                 std::string(std::istreambuf_iterator<char>{ifs}, {});
2958 
2959             // Configure this to be a file download when accessed
2960             // from a browser
2961             asyncResp->res.addHeader("Content-Disposition", "attachment");
2962         };
2963         crow::connections::systemBus->async_method_call(
2964             std::move(getStoredLogCallback), crashdumpObject,
2965             crashdumpPath + std::string("/") + logID,
2966             "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface);
2967         });
2968 }
2969 
2970 enum class OEMDiagnosticType
2971 {
2972     onDemand,
2973     telemetry,
2974     invalid,
2975 };
2976 
2977 inline OEMDiagnosticType
2978     getOEMDiagnosticType(const std::string_view& oemDiagStr)
2979 {
2980     if (oemDiagStr == "OnDemand")
2981     {
2982         return OEMDiagnosticType::onDemand;
2983     }
2984     if (oemDiagStr == "Telemetry")
2985     {
2986         return OEMDiagnosticType::telemetry;
2987     }
2988 
2989     return OEMDiagnosticType::invalid;
2990 }
2991 
2992 inline void requestRoutesCrashdumpCollect(App& app)
2993 {
2994     // Note: Deviated from redfish privilege registry for GET & HEAD
2995     // method for security reasons.
2996     BMCWEB_ROUTE(
2997         app,
2998         "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/")
2999         // The below is incorrect;  Should be ConfigureManager
3000         //.privileges(redfish::privileges::postLogService)
3001         .privileges({{"ConfigureComponents"}})
3002         .methods(boost::beast::http::verb::post)(
3003             [&app](const crow::Request& req,
3004                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
3005         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3006         {
3007             return;
3008         }
3009         std::string diagnosticDataType;
3010         std::string oemDiagnosticDataType;
3011         if (!redfish::json_util::readJsonAction(
3012                 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType,
3013                 "OEMDiagnosticDataType", oemDiagnosticDataType))
3014         {
3015             return;
3016         }
3017 
3018         if (diagnosticDataType != "OEM")
3019         {
3020             BMCWEB_LOG_ERROR
3021                 << "Only OEM DiagnosticDataType supported for Crashdump";
3022             messages::actionParameterValueFormatError(
3023                 asyncResp->res, diagnosticDataType, "DiagnosticDataType",
3024                 "CollectDiagnosticData");
3025             return;
3026         }
3027 
3028         OEMDiagnosticType oemDiagType =
3029             getOEMDiagnosticType(oemDiagnosticDataType);
3030 
3031         std::string iface;
3032         std::string method;
3033         std::string taskMatchStr;
3034         if (oemDiagType == OEMDiagnosticType::onDemand)
3035         {
3036             iface = crashdumpOnDemandInterface;
3037             method = "GenerateOnDemandLog";
3038             taskMatchStr = "type='signal',"
3039                            "interface='org.freedesktop.DBus.Properties',"
3040                            "member='PropertiesChanged',"
3041                            "arg0namespace='com.intel.crashdump'";
3042         }
3043         else if (oemDiagType == OEMDiagnosticType::telemetry)
3044         {
3045             iface = crashdumpTelemetryInterface;
3046             method = "GenerateTelemetryLog";
3047             taskMatchStr = "type='signal',"
3048                            "interface='org.freedesktop.DBus.Properties',"
3049                            "member='PropertiesChanged',"
3050                            "arg0namespace='com.intel.crashdump'";
3051         }
3052         else
3053         {
3054             BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: "
3055                              << oemDiagnosticDataType;
3056             messages::actionParameterValueFormatError(
3057                 asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType",
3058                 "CollectDiagnosticData");
3059             return;
3060         }
3061 
3062         auto collectCrashdumpCallback =
3063             [asyncResp, payload(task::Payload(req)),
3064              taskMatchStr](const boost::system::error_code ec,
3065                            const std::string&) mutable {
3066             if (ec)
3067             {
3068                 if (ec.value() == boost::system::errc::operation_not_supported)
3069                 {
3070                     messages::resourceInStandby(asyncResp->res);
3071                 }
3072                 else if (ec.value() ==
3073                          boost::system::errc::device_or_resource_busy)
3074                 {
3075                     messages::serviceTemporarilyUnavailable(asyncResp->res,
3076                                                             "60");
3077                 }
3078                 else
3079                 {
3080                     messages::internalError(asyncResp->res);
3081                 }
3082                 return;
3083             }
3084             std::shared_ptr<task::TaskData> task = task::TaskData::createTask(
3085                 [](boost::system::error_code err, sdbusplus::message::message&,
3086                    const std::shared_ptr<task::TaskData>& taskData) {
3087                 if (!err)
3088                 {
3089                     taskData->messages.emplace_back(messages::taskCompletedOK(
3090                         std::to_string(taskData->index)));
3091                     taskData->state = "Completed";
3092                 }
3093                 return task::completed;
3094                 },
3095                 taskMatchStr);
3096 
3097             task->startTimer(std::chrono::minutes(5));
3098             task->populateResp(asyncResp->res);
3099             task->payload.emplace(std::move(payload));
3100         };
3101 
3102         crow::connections::systemBus->async_method_call(
3103             std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath,
3104             iface, method);
3105         });
3106 }
3107 
3108 /**
3109  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
3110  */
3111 inline void requestRoutesDBusLogServiceActionsClear(App& app)
3112 {
3113     /**
3114      * Function handles POST method request.
3115      * The Clear Log actions does not require any parameter.The action deletes
3116      * all entries found in the Entries collection for this Log Service.
3117      */
3118 
3119     BMCWEB_ROUTE(
3120         app,
3121         "/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog/")
3122         .privileges(redfish::privileges::postLogService)
3123         .methods(boost::beast::http::verb::post)(
3124             [&app](const crow::Request& req,
3125                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
3126         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3127         {
3128             return;
3129         }
3130         BMCWEB_LOG_DEBUG << "Do delete all entries.";
3131 
3132         // Process response from Logging service.
3133         auto respHandler = [asyncResp](const boost::system::error_code ec) {
3134             BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done";
3135             if (ec)
3136             {
3137                 // TODO Handle for specific error code
3138                 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec;
3139                 asyncResp->res.result(
3140                     boost::beast::http::status::internal_server_error);
3141                 return;
3142             }
3143 
3144             asyncResp->res.result(boost::beast::http::status::no_content);
3145         };
3146 
3147         // Make call to Logging service to request Clear Log
3148         crow::connections::systemBus->async_method_call(
3149             respHandler, "xyz.openbmc_project.Logging",
3150             "/xyz/openbmc_project/logging",
3151             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3152         });
3153 }
3154 
3155 /****************************************************
3156  * Redfish PostCode interfaces
3157  * using DBUS interface: getPostCodesTS
3158  ******************************************************/
3159 inline void requestRoutesPostCodesLogService(App& app)
3160 {
3161     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/")
3162         .privileges(redfish::privileges::getLogService)
3163         .methods(boost::beast::http::verb::get)(
3164             [&app](const crow::Request& req,
3165                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
3166         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3167         {
3168             return;
3169         }
3170 
3171         asyncResp->res.jsonValue["@odata.id"] =
3172             "/redfish/v1/Systems/system/LogServices/PostCodes";
3173         asyncResp->res.jsonValue["@odata.type"] =
3174             "#LogService.v1_1_0.LogService";
3175         asyncResp->res.jsonValue["Name"] = "POST Code Log Service";
3176         asyncResp->res.jsonValue["Description"] = "POST Code Log Service";
3177         asyncResp->res.jsonValue["Id"] = "BIOS POST Code Log";
3178         asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
3179         asyncResp->res.jsonValue["Entries"]["@odata.id"] =
3180             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
3181 
3182         std::pair<std::string, std::string> redfishDateTimeOffset =
3183             crow::utility::getDateTimeOffsetNow();
3184         asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
3185         asyncResp->res.jsonValue["DateTimeLocalOffset"] =
3186             redfishDateTimeOffset.second;
3187 
3188         asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = {
3189             {"target",
3190              "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}};
3191         });
3192 }
3193 
3194 inline void requestRoutesPostCodesClear(App& app)
3195 {
3196     BMCWEB_ROUTE(
3197         app,
3198         "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog/")
3199         // The following privilege is incorrect;  It should be ConfigureManager
3200         //.privileges(redfish::privileges::postLogService)
3201         .privileges({{"ConfigureComponents"}})
3202         .methods(boost::beast::http::verb::post)(
3203             [&app](const crow::Request& req,
3204                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
3205         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3206         {
3207             return;
3208         }
3209         BMCWEB_LOG_DEBUG << "Do delete all postcodes entries.";
3210 
3211         // Make call to post-code service to request clear all
3212         crow::connections::systemBus->async_method_call(
3213             [asyncResp](const boost::system::error_code ec) {
3214             if (ec)
3215             {
3216                 // TODO Handle for specific error code
3217                 BMCWEB_LOG_ERROR << "doClearPostCodes resp_handler got error "
3218                                  << ec;
3219                 asyncResp->res.result(
3220                     boost::beast::http::status::internal_server_error);
3221                 messages::internalError(asyncResp->res);
3222                 return;
3223             }
3224             },
3225             "xyz.openbmc_project.State.Boot.PostCode0",
3226             "/xyz/openbmc_project/State/Boot/PostCode0",
3227             "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
3228         });
3229 }
3230 
3231 static void fillPostCodeEntry(
3232     const std::shared_ptr<bmcweb::AsyncResp>& aResp,
3233     const boost::container::flat_map<
3234         uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode,
3235     const uint16_t bootIndex, const uint64_t codeIndex = 0,
3236     const uint64_t skip = 0, const uint64_t top = 0)
3237 {
3238     // Get the Message from the MessageRegistry
3239     const registries::Message* message =
3240         registries::getMessage("OpenBMC.0.2.BIOSPOSTCode");
3241 
3242     uint64_t currentCodeIndex = 0;
3243     nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"];
3244 
3245     uint64_t firstCodeTimeUs = 0;
3246     for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
3247              code : postcode)
3248     {
3249         currentCodeIndex++;
3250         std::string postcodeEntryID =
3251             "B" + std::to_string(bootIndex) + "-" +
3252             std::to_string(currentCodeIndex); // 1 based index in EntryID string
3253 
3254         uint64_t usecSinceEpoch = code.first;
3255         uint64_t usTimeOffset = 0;
3256 
3257         if (1 == currentCodeIndex)
3258         { // already incremented
3259             firstCodeTimeUs = code.first;
3260         }
3261         else
3262         {
3263             usTimeOffset = code.first - firstCodeTimeUs;
3264         }
3265 
3266         // skip if no specific codeIndex is specified and currentCodeIndex does
3267         // not fall between top and skip
3268         if ((codeIndex == 0) &&
3269             (currentCodeIndex <= skip || currentCodeIndex > top))
3270         {
3271             continue;
3272         }
3273 
3274         // skip if a specific codeIndex is specified and does not match the
3275         // currentIndex
3276         if ((codeIndex > 0) && (currentCodeIndex != codeIndex))
3277         {
3278             // This is done for simplicity. 1st entry is needed to calculate
3279             // time offset. To improve efficiency, one can get to the entry
3280             // directly (possibly with flatmap's nth method)
3281             continue;
3282         }
3283 
3284         // currentCodeIndex is within top and skip or equal to specified code
3285         // index
3286 
3287         // Get the Created time from the timestamp
3288         std::string entryTimeStr;
3289         entryTimeStr =
3290             crow::utility::getDateTimeUint(usecSinceEpoch / 1000 / 1000);
3291 
3292         // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex)
3293         std::ostringstream hexCode;
3294         hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex
3295                 << std::get<0>(code.second);
3296         std::ostringstream timeOffsetStr;
3297         // Set Fixed -Point Notation
3298         timeOffsetStr << std::fixed;
3299         // Set precision to 4 digits
3300         timeOffsetStr << std::setprecision(4);
3301         // Add double to stream
3302         timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000;
3303         std::vector<std::string> messageArgs = {
3304             std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()};
3305 
3306         // Get MessageArgs template from message registry
3307         std::string msg;
3308         if (message != nullptr)
3309         {
3310             msg = message->message;
3311 
3312             // fill in this post code value
3313             int i = 0;
3314             for (const std::string& messageArg : messageArgs)
3315             {
3316                 std::string argStr = "%" + std::to_string(++i);
3317                 size_t argPos = msg.find(argStr);
3318                 if (argPos != std::string::npos)
3319                 {
3320                     msg.replace(argPos, argStr.length(), messageArg);
3321                 }
3322             }
3323         }
3324 
3325         // Get Severity template from message registry
3326         std::string severity;
3327         if (message != nullptr)
3328         {
3329             severity = message->messageSeverity;
3330         }
3331 
3332         // add to AsyncResp
3333         logEntryArray.push_back({});
3334         nlohmann::json& bmcLogEntry = logEntryArray.back();
3335         bmcLogEntry = {
3336             {"@odata.type", "#LogEntry.v1_8_0.LogEntry"},
3337             {"@odata.id",
3338              "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" +
3339                  postcodeEntryID},
3340             {"Name", "POST Code Log Entry"},
3341             {"Id", postcodeEntryID},
3342             {"Message", std::move(msg)},
3343             {"MessageId", "OpenBMC.0.2.BIOSPOSTCode"},
3344             {"MessageArgs", std::move(messageArgs)},
3345             {"EntryType", "Event"},
3346             {"Severity", std::move(severity)},
3347             {"Created", entryTimeStr}};
3348         if (!std::get<std::vector<uint8_t>>(code.second).empty())
3349         {
3350             bmcLogEntry["AdditionalDataURI"] =
3351                 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" +
3352                 postcodeEntryID + "/attachment";
3353         }
3354     }
3355 }
3356 
3357 static void getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
3358                                 const uint16_t bootIndex,
3359                                 const uint64_t codeIndex)
3360 {
3361     crow::connections::systemBus->async_method_call(
3362         [aResp, bootIndex,
3363          codeIndex](const boost::system::error_code ec,
3364                     const boost::container::flat_map<
3365                         uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
3366                         postcode) {
3367         if (ec)
3368         {
3369             BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
3370             messages::internalError(aResp->res);
3371             return;
3372         }
3373 
3374         // skip the empty postcode boots
3375         if (postcode.empty())
3376         {
3377             return;
3378         }
3379 
3380         fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex);
3381 
3382         aResp->res.jsonValue["Members@odata.count"] =
3383             aResp->res.jsonValue["Members"].size();
3384         },
3385         "xyz.openbmc_project.State.Boot.PostCode0",
3386         "/xyz/openbmc_project/State/Boot/PostCode0",
3387         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
3388         bootIndex);
3389 }
3390 
3391 static void getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
3392                                const uint16_t bootIndex,
3393                                const uint16_t bootCount,
3394                                const uint64_t entryCount, const uint64_t skip,
3395                                const uint64_t top)
3396 {
3397     crow::connections::systemBus->async_method_call(
3398         [aResp, bootIndex, bootCount, entryCount, skip,
3399          top](const boost::system::error_code ec,
3400               const boost::container::flat_map<
3401                   uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>&
3402                   postcode) {
3403         if (ec)
3404         {
3405             BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error";
3406             messages::internalError(aResp->res);
3407             return;
3408         }
3409 
3410         uint64_t endCount = entryCount;
3411         if (!postcode.empty())
3412         {
3413             endCount = entryCount + postcode.size();
3414 
3415             if ((skip < endCount) && ((top + skip) > entryCount))
3416             {
3417                 uint64_t thisBootSkip = std::max(skip, entryCount) - entryCount;
3418                 uint64_t thisBootTop =
3419                     std::min(top + skip, endCount) - entryCount;
3420 
3421                 fillPostCodeEntry(aResp, postcode, bootIndex, 0, thisBootSkip,
3422                                   thisBootTop);
3423             }
3424             aResp->res.jsonValue["Members@odata.count"] = endCount;
3425         }
3426 
3427         // continue to previous bootIndex
3428         if (bootIndex < bootCount)
3429         {
3430             getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1),
3431                                bootCount, endCount, skip, top);
3432         }
3433         else
3434         {
3435             aResp->res.jsonValue["Members@odata.nextLink"] =
3436                 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" +
3437                 std::to_string(skip + top);
3438         }
3439         },
3440         "xyz.openbmc_project.State.Boot.PostCode0",
3441         "/xyz/openbmc_project/State/Boot/PostCode0",
3442         "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp",
3443         bootIndex);
3444 }
3445 
3446 static void
3447     getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
3448                          const uint64_t skip, const uint64_t top)
3449 {
3450     uint64_t entryCount = 0;
3451     sdbusplus::asio::getProperty<uint16_t>(
3452         *crow::connections::systemBus,
3453         "xyz.openbmc_project.State.Boot.PostCode0",
3454         "/xyz/openbmc_project/State/Boot/PostCode0",
3455         "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount",
3456         [aResp, entryCount, skip, top](const boost::system::error_code ec,
3457                                        const uint16_t bootCount) {
3458         if (ec)
3459         {
3460             BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
3461             messages::internalError(aResp->res);
3462             return;
3463         }
3464         getPostCodeForBoot(aResp, 1, bootCount, entryCount, skip, top);
3465         });
3466 }
3467 
3468 inline void requestRoutesPostCodesEntryCollection(App& app)
3469 {
3470     BMCWEB_ROUTE(app,
3471                  "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/")
3472         .privileges(redfish::privileges::getLogEntryCollection)
3473         .methods(boost::beast::http::verb::get)(
3474             [&app](const crow::Request& req,
3475                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
3476         query_param::QueryCapabilities capabilities = {
3477             .canDelegateTop = true,
3478             .canDelegateSkip = true,
3479         };
3480         query_param::Query delegatedQuery;
3481         if (!redfish::setUpRedfishRouteWithDelegation(
3482                 app, req, asyncResp, delegatedQuery, capabilities))
3483         {
3484             return;
3485         }
3486         asyncResp->res.jsonValue["@odata.type"] =
3487             "#LogEntryCollection.LogEntryCollection";
3488         asyncResp->res.jsonValue["@odata.id"] =
3489             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
3490         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
3491         asyncResp->res.jsonValue["Description"] =
3492             "Collection of POST Code Log Entries";
3493         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3494         asyncResp->res.jsonValue["Members@odata.count"] = 0;
3495 
3496         getCurrentBootNumber(asyncResp, delegatedQuery.skip,
3497                              delegatedQuery.top);
3498         });
3499 }
3500 
3501 /**
3502  * @brief Parse post code ID and get the current value and index value
3503  *        eg: postCodeID=B1-2, currentValue=1, index=2
3504  *
3505  * @param[in]  postCodeID     Post Code ID
3506  * @param[out] currentValue   Current value
3507  * @param[out] index          Index value
3508  *
3509  * @return bool true if the parsing is successful, false the parsing fails
3510  */
3511 inline static bool parsePostCode(const std::string& postCodeID,
3512                                  uint64_t& currentValue, uint16_t& index)
3513 {
3514     std::vector<std::string> split;
3515     boost::algorithm::split(split, postCodeID, boost::is_any_of("-"));
3516     if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B')
3517     {
3518         return false;
3519     }
3520 
3521     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
3522     const char* start = split[0].data() + 1;
3523     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
3524     const char* end = split[0].data() + split[0].size();
3525     auto [ptrIndex, ecIndex] = std::from_chars(start, end, index);
3526 
3527     if (ptrIndex != end || ecIndex != std::errc())
3528     {
3529         return false;
3530     }
3531 
3532     start = split[1].data();
3533 
3534     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
3535     end = split[1].data() + split[1].size();
3536     auto [ptrValue, ecValue] = std::from_chars(start, end, currentValue);
3537 
3538     return ptrValue == end && ecValue != std::errc();
3539 }
3540 
3541 inline void requestRoutesPostCodesEntryAdditionalData(App& app)
3542 {
3543     BMCWEB_ROUTE(
3544         app,
3545         "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/attachment/")
3546         .privileges(redfish::privileges::getLogEntry)
3547         .methods(boost::beast::http::verb::get)(
3548             [&app](const crow::Request& req,
3549                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3550                    const std::string& postCodeID) {
3551         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3552         {
3553             return;
3554         }
3555         if (!http_helpers::isOctetAccepted(req.getHeaderValue("Accept")))
3556         {
3557             asyncResp->res.result(boost::beast::http::status::bad_request);
3558             return;
3559         }
3560 
3561         uint64_t currentValue = 0;
3562         uint16_t index = 0;
3563         if (!parsePostCode(postCodeID, currentValue, index))
3564         {
3565             messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID);
3566             return;
3567         }
3568 
3569         crow::connections::systemBus->async_method_call(
3570             [asyncResp, postCodeID, currentValue](
3571                 const boost::system::error_code ec,
3572                 const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>&
3573                     postcodes) {
3574             if (ec.value() == EBADR)
3575             {
3576                 messages::resourceNotFound(asyncResp->res, "LogEntry",
3577                                            postCodeID);
3578                 return;
3579             }
3580             if (ec)
3581             {
3582                 BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
3583                 messages::internalError(asyncResp->res);
3584                 return;
3585             }
3586 
3587             size_t value = static_cast<size_t>(currentValue) - 1;
3588             if (value == std::string::npos || postcodes.size() < currentValue)
3589             {
3590                 BMCWEB_LOG_ERROR << "Wrong currentValue value";
3591                 messages::resourceNotFound(asyncResp->res, "LogEntry",
3592                                            postCodeID);
3593                 return;
3594             }
3595 
3596             const auto& [tID, c] = postcodes[value];
3597             if (c.empty())
3598             {
3599                 BMCWEB_LOG_INFO << "No found post code data";
3600                 messages::resourceNotFound(asyncResp->res, "LogEntry",
3601                                            postCodeID);
3602                 return;
3603             }
3604             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
3605             const char* d = reinterpret_cast<const char*>(c.data());
3606             std::string_view strData(d, c.size());
3607 
3608             asyncResp->res.addHeader("Content-Type",
3609                                      "application/octet-stream");
3610             asyncResp->res.addHeader("Content-Transfer-Encoding", "Base64");
3611             asyncResp->res.body() = crow::utility::base64encode(strData);
3612             },
3613             "xyz.openbmc_project.State.Boot.PostCode0",
3614             "/xyz/openbmc_project/State/Boot/PostCode0",
3615             "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index);
3616         });
3617 }
3618 
3619 inline void requestRoutesPostCodesEntry(App& app)
3620 {
3621     BMCWEB_ROUTE(
3622         app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/")
3623         .privileges(redfish::privileges::getLogEntry)
3624         .methods(boost::beast::http::verb::get)(
3625             [&app](const crow::Request& req,
3626                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
3627                    const std::string& targetID) {
3628         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
3629         {
3630             return;
3631         }
3632         uint16_t bootIndex = 0;
3633         uint64_t codeIndex = 0;
3634         if (!parsePostCode(targetID, codeIndex, bootIndex))
3635         {
3636             // Requested ID was not found
3637             messages::resourceMissingAtURI(asyncResp->res, req.urlView);
3638             return;
3639         }
3640         if (bootIndex == 0 || codeIndex == 0)
3641         {
3642             BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string "
3643                              << targetID;
3644         }
3645 
3646         asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry";
3647         asyncResp->res.jsonValue["@odata.id"] =
3648             "/redfish/v1/Systems/system/LogServices/PostCodes/Entries";
3649         asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries";
3650         asyncResp->res.jsonValue["Description"] =
3651             "Collection of POST Code Log Entries";
3652         asyncResp->res.jsonValue["Members"] = nlohmann::json::array();
3653         asyncResp->res.jsonValue["Members@odata.count"] = 0;
3654 
3655         getPostCodeForEntry(asyncResp, bootIndex, codeIndex);
3656         });
3657 }
3658 
3659 } // namespace redfish
3660