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