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