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