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