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