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