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