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