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