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