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