1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5
6 #include "async_resp.hpp"
7 #include "dbus_utility.hpp"
8 #include "error_messages.hpp"
9 #include "generated/enums/log_service.hpp"
10 #include "http_response.hpp"
11 #include "logging.hpp"
12 #include "registries.hpp"
13 #include "str_utility.hpp"
14 #include "utils/dbus_event_log_entry.hpp"
15 #include "utils/etag_utils.hpp"
16 #include "utils/log_services_utils.hpp"
17 #include "utils/query_param.hpp"
18 #include "utils/time_utils.hpp"
19
20 #include <boost/beast/http/field.hpp>
21 #include <boost/beast/http/status.hpp>
22 #include <boost/beast/http/verb.hpp>
23 #include <boost/system/linux_error.hpp>
24 #include <boost/url/format.hpp>
25 #include <boost/url/url.hpp>
26 #include <sdbusplus/message.hpp>
27 #include <sdbusplus/message/native_types.hpp>
28 #include <sdbusplus/unpack_properties.hpp>
29
30 #include <algorithm>
31 #include <cstddef>
32 #include <cstdint>
33 #include <cstdio>
34 #include <ctime>
35 #include <fstream>
36 #include <iomanip>
37 #include <memory>
38 #include <optional>
39 #include <sstream>
40 #include <string>
41 #include <string_view>
42 #include <utility>
43
44 namespace redfish
45 {
46 namespace eventlog_utils
47 {
48
49 constexpr const char* rfSystemsStr = "Systems";
50 constexpr const char* rfManagersStr = "Managers";
51
52 enum class LogServiceParentCollection
53 {
54 Systems,
55 Managers
56 };
57
logServiceParentCollectionToString(LogServiceParentCollection collection)58 inline std::string logServiceParentCollectionToString(
59 LogServiceParentCollection collection)
60 {
61 std::string collectionStr;
62 switch (collection)
63 {
64 case LogServiceParentCollection::Managers:
65 collectionStr = rfManagersStr;
66 break;
67 case LogServiceParentCollection::Systems:
68 collectionStr = rfSystemsStr;
69 break;
70 default:
71 BMCWEB_LOG_ERROR("Unable to stringify bmcweb eventlog location");
72 break;
73 }
74 return collectionStr;
75 }
76
getMemberIdFromParentCollection(LogServiceParentCollection collection)77 inline std::string_view getMemberIdFromParentCollection(
78 LogServiceParentCollection collection)
79 {
80 std::string_view memberId;
81
82 switch (collection)
83 {
84 case LogServiceParentCollection::Managers:
85 memberId = BMCWEB_REDFISH_MANAGER_URI_NAME;
86 break;
87 case LogServiceParentCollection::Systems:
88 memberId = BMCWEB_REDFISH_SYSTEM_URI_NAME;
89 break;
90 default:
91 BMCWEB_LOG_ERROR(
92 "Unable to stringify bmcweb eventlog location childId");
93 break;
94 }
95 return memberId;
96 }
97
getLogEntryDescriptorFromParentCollection(LogServiceParentCollection collection)98 inline std::string getLogEntryDescriptorFromParentCollection(
99 LogServiceParentCollection collection)
100 {
101 std::string descriptor;
102 switch (collection)
103 {
104 case LogServiceParentCollection::Managers:
105 descriptor = "Manager";
106 break;
107 case LogServiceParentCollection::Systems:
108 descriptor = "System";
109 break;
110 default:
111 BMCWEB_LOG_ERROR("Unable to get Log Entry descriptor");
112 break;
113 }
114 return descriptor;
115 }
116
handleSystemsAndManagersEventLogServiceGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,LogServiceParentCollection collection)117 inline void handleSystemsAndManagersEventLogServiceGet(
118 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
119 LogServiceParentCollection collection)
120 {
121 const std::string collectionStr =
122 logServiceParentCollectionToString(collection);
123 const std::string_view memberId =
124 getMemberIdFromParentCollection(collection);
125 const std::string logEntryDescriptor =
126 getLogEntryDescriptorFromParentCollection(collection);
127
128 if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
129 {
130 messages::internalError(asyncResp->res);
131 return;
132 }
133
134 asyncResp->res.jsonValue["@odata.id"] = std::format(
135 "/redfish/v1/{}/{}/LogServices/EventLog", collectionStr, memberId);
136 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
137 asyncResp->res.jsonValue["Name"] = "Event Log Service";
138 asyncResp->res.jsonValue["Description"] =
139 std::format("{} Event Log Service", logEntryDescriptor);
140 asyncResp->res.jsonValue["Id"] = "EventLog";
141 asyncResp->res.jsonValue["OverWritePolicy"] =
142 log_service::OverWritePolicy::WrapsWhenFull;
143
144 std::pair<std::string, std::string> redfishDateTimeOffset =
145 redfish::time_utils::getDateTimeOffsetNow();
146
147 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
148 asyncResp->res.jsonValue["DateTimeLocalOffset"] =
149 redfishDateTimeOffset.second;
150
151 asyncResp->res.jsonValue["Entries"]["@odata.id"] =
152 std::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries",
153 collectionStr, memberId);
154 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"]
155
156 = std::format(
157 "/redfish/v1/{}/{}/LogServices/EventLog/Actions/LogService.ClearLog",
158 collectionStr, memberId);
159 etag_utils::setEtagOmitDateTimeHandler(asyncResp);
160 }
161
162 /*
163 * Journal EventLog utilities
164 * */
165
getRedfishLogFiles(std::vector<std::filesystem::path> & redfishLogFiles)166 inline bool getRedfishLogFiles(
167 std::vector<std::filesystem::path>& redfishLogFiles)
168 {
169 static const std::filesystem::path redfishLogDir = "/var/log";
170 static const std::string redfishLogFilename = "redfish";
171
172 // Loop through the directory looking for redfish log files
173 for (const std::filesystem::directory_entry& dirEnt :
174 std::filesystem::directory_iterator(redfishLogDir))
175 {
176 // If we find a redfish log file, save the path
177 std::string filename = dirEnt.path().filename();
178 if (filename.starts_with(redfishLogFilename))
179 {
180 redfishLogFiles.emplace_back(redfishLogDir / filename);
181 }
182 }
183 // As the log files rotate, they are appended with a ".#" that is higher for
184 // the older logs. Since we don't expect more than 10 log files, we
185 // can just sort the list to get them in order from newest to oldest
186 std::ranges::sort(redfishLogFiles);
187
188 return !redfishLogFiles.empty();
189 }
190
getUniqueEntryID(const std::string & logEntry,std::string & entryID,const bool firstEntry=true)191 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID,
192 const bool firstEntry = true)
193 {
194 static time_t prevTs = 0;
195 static int index = 0;
196 if (firstEntry)
197 {
198 prevTs = 0;
199 }
200
201 // Get the entry timestamp
202 std::time_t curTs = 0;
203 std::tm timeStruct = {};
204 std::istringstream entryStream(logEntry);
205 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S"))
206 {
207 curTs = std::mktime(&timeStruct);
208 }
209 // If the timestamp isn't unique, increment the index
210 if (curTs == prevTs)
211 {
212 index++;
213 }
214 else
215 {
216 // Otherwise, reset it
217 index = 0;
218 }
219 // Save the timestamp
220 prevTs = curTs;
221
222 entryID = std::to_string(curTs);
223 if (index > 0)
224 {
225 entryID += "_" + std::to_string(index);
226 }
227 return true;
228 }
229
230 enum class LogParseError
231 {
232 success,
233 parseFailed,
234 messageIdNotInRegistry,
235 };
236
fillEventLogEntryJson(const std::string & logEntryID,const std::string & logEntry,nlohmann::json::object_t & logEntryJson,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor)237 static LogParseError fillEventLogEntryJson(
238 const std::string& logEntryID, const std::string& logEntry,
239 nlohmann::json::object_t& logEntryJson, const std::string& collectionStr,
240 const std::string_view memberId, const std::string& logEntryDescriptor)
241 {
242 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>"
243 // First get the Timestamp
244 size_t space = logEntry.find_first_of(' ');
245 if (space == std::string::npos)
246 {
247 return LogParseError::parseFailed;
248 }
249 std::string timestamp = logEntry.substr(0, space);
250 // Then get the log contents
251 size_t entryStart = logEntry.find_first_not_of(' ', space);
252 if (entryStart == std::string::npos)
253 {
254 return LogParseError::parseFailed;
255 }
256 std::string_view entry(logEntry);
257 entry.remove_prefix(entryStart);
258 // Use split to separate the entry into its fields
259 std::vector<std::string> logEntryFields;
260 bmcweb::split(logEntryFields, entry, ',');
261 // We need at least a MessageId to be valid
262 auto logEntryIter = logEntryFields.begin();
263 if (logEntryIter == logEntryFields.end())
264 {
265 return LogParseError::parseFailed;
266 }
267 std::string& messageID = *logEntryIter;
268
269 std::optional<registries::MessageId> msgComponents =
270 registries::getMessageComponents(messageID);
271 if (!msgComponents)
272 {
273 return LogParseError::parseFailed;
274 }
275
276 std::optional<registries::RegistryEntryRef> registry =
277 registries::getRegistryFromPrefix(msgComponents->registryName);
278 if (!registry)
279 {
280 return LogParseError::messageIdNotInRegistry;
281 }
282
283 // Get the Message from the MessageKey and RegistryEntries
284 const registries::Message* message = registries::getMessageFromRegistry(
285 msgComponents->messageKey, registry->get().entries);
286
287 logEntryIter++;
288 if (message == nullptr)
289 {
290 BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry);
291 return LogParseError::messageIdNotInRegistry;
292 }
293
294 const unsigned int& versionMajor = registry->get().header.versionMajor;
295 const unsigned int& versionMinor = registry->get().header.versionMinor;
296
297 std::vector<std::string_view> messageArgs(logEntryIter,
298 logEntryFields.end());
299 messageArgs.resize(message->numberOfArgs);
300
301 std::string msg =
302 redfish::registries::fillMessageArgs(messageArgs, message->message);
303 if (msg.empty())
304 {
305 return LogParseError::parseFailed;
306 }
307
308 // Get the Created time from the timestamp. The log timestamp is in RFC3339
309 // format which matches the Redfish format except for the
310 // fractional seconds between the '.' and the '+', so just remove them.
311 std::size_t dot = timestamp.find_first_of('.');
312 std::size_t plus = timestamp.find_first_of('+');
313 if (dot != std::string::npos && plus != std::string::npos)
314 {
315 timestamp.erase(dot, plus - dot);
316 }
317
318 // Fill in the log entry with the gathered data
319 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
320 logEntryJson["@odata.id"] =
321 boost::urls::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries/{}",
322 collectionStr, memberId, logEntryID);
323 logEntryJson["Name"] =
324 std::format("{} Event Log Entry", logEntryDescriptor);
325 logEntryJson["Id"] = logEntryID;
326 logEntryJson["Message"] = std::move(msg);
327 logEntryJson["MessageId"] =
328 std::format("{}.{}.{}.{}", msgComponents->registryName, versionMajor,
329 versionMinor, msgComponents->messageKey);
330 logEntryJson["MessageArgs"] = messageArgs;
331 logEntryJson["EntryType"] = "Event";
332 logEntryJson["Severity"] = message->messageSeverity;
333 logEntryJson["Created"] = std::move(timestamp);
334 return LogParseError::success;
335 }
336
handleSystemsAndManagersLogServiceEventLogLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,query_param::Query & delegatedQuery,LogServiceParentCollection collection)337 inline void handleSystemsAndManagersLogServiceEventLogLogEntryCollection(
338 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
339 query_param::Query& delegatedQuery, LogServiceParentCollection collection)
340 {
341 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
342 size_t skip = delegatedQuery.skip.value_or(0);
343
344 const std::string collectionStr =
345 logServiceParentCollectionToString(collection);
346 const std::string_view memberId =
347 getMemberIdFromParentCollection(collection);
348 const std::string logEntryDescriptor =
349 getLogEntryDescriptorFromParentCollection(collection);
350
351 if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
352 {
353 messages::internalError(asyncResp->res);
354 return;
355 }
356
357 // Collections don't include the static data added by SubRoute
358 // because it has a duplicate entry for members
359 asyncResp->res.jsonValue["@odata.type"] =
360 "#LogEntryCollection.LogEntryCollection";
361 asyncResp->res.jsonValue["@odata.id"] =
362 std::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries",
363 collectionStr, memberId);
364 asyncResp->res.jsonValue["Name"] =
365 std::format("{} Event Log Entries", logEntryDescriptor);
366 asyncResp->res.jsonValue["Description"] =
367 std::format("Collection of {} Event Log Entries", logEntryDescriptor);
368
369 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"];
370 logEntryArray = nlohmann::json::array();
371 // Go through the log files and create a unique ID for each
372 // entry
373 std::vector<std::filesystem::path> redfishLogFiles;
374 getRedfishLogFiles(redfishLogFiles);
375 uint64_t entryCount = 0;
376 std::string logEntry;
377
378 // Oldest logs are in the last file, so start there and loop
379 // backwards
380 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
381 {
382 std::ifstream logStream(*it);
383 if (!logStream.is_open())
384 {
385 continue;
386 }
387
388 // Reset the unique ID on the first entry
389 bool firstEntry = true;
390 while (std::getline(logStream, logEntry))
391 {
392 std::string idStr;
393 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
394 {
395 continue;
396 }
397 firstEntry = false;
398
399 nlohmann::json::object_t bmcLogEntry;
400 LogParseError status = fillEventLogEntryJson(
401 idStr, logEntry, bmcLogEntry, collectionStr, memberId,
402 logEntryDescriptor);
403 if (status == LogParseError::messageIdNotInRegistry)
404 {
405 continue;
406 }
407 if (status != LogParseError::success)
408 {
409 messages::internalError(asyncResp->res);
410 return;
411 }
412
413 entryCount++;
414 // Handle paging using skip (number of entries to skip from the
415 // start) and top (number of entries to display)
416 if (entryCount <= skip || entryCount > skip + top)
417 {
418 continue;
419 }
420
421 logEntryArray.emplace_back(std::move(bmcLogEntry));
422 }
423 }
424 asyncResp->res.jsonValue["Members@odata.count"] = entryCount;
425 if (skip + top < entryCount)
426 {
427 asyncResp->res.jsonValue["Members@odata.nextLink"] =
428 boost::urls::format(
429 "/redfish/v1/{}/{}/LogServices/EventLog/Entries?$skip={}",
430 collectionStr, memberId, std::to_string(skip + top));
431 }
432 }
433
handleSystemsAndManagersLogServiceEventLogEntriesGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & param,LogServiceParentCollection collection)434 inline void handleSystemsAndManagersLogServiceEventLogEntriesGet(
435 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
436 const std::string& param, LogServiceParentCollection collection)
437 {
438 const std::string& targetID = param;
439
440 const std::string collectionStr =
441 logServiceParentCollectionToString(collection);
442 const std::string_view memberId =
443 getMemberIdFromParentCollection(collection);
444 const std::string logEntryDescriptor =
445 getLogEntryDescriptorFromParentCollection(collection);
446
447 if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
448 {
449 messages::internalError(asyncResp->res);
450 return;
451 }
452
453 // Go through the log files and check the unique ID for each
454 // entry to find the target entry
455 std::vector<std::filesystem::path> redfishLogFiles;
456 getRedfishLogFiles(redfishLogFiles);
457 std::string logEntry;
458
459 // Oldest logs are in the last file, so start there and loop
460 // backwards
461 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); it++)
462 {
463 std::ifstream logStream(*it);
464 if (!logStream.is_open())
465 {
466 continue;
467 }
468
469 // Reset the unique ID on the first entry
470 bool firstEntry = true;
471 while (std::getline(logStream, logEntry))
472 {
473 std::string idStr;
474 if (!getUniqueEntryID(logEntry, idStr, firstEntry))
475 {
476 continue;
477 }
478 firstEntry = false;
479
480 if (idStr == targetID)
481 {
482 nlohmann::json::object_t bmcLogEntry;
483 LogParseError status = fillEventLogEntryJson(
484 idStr, logEntry, bmcLogEntry, collectionStr, memberId,
485 logEntryDescriptor);
486 if (status != LogParseError::success)
487 {
488 messages::internalError(asyncResp->res);
489 return;
490 }
491 asyncResp->res.jsonValue.update(bmcLogEntry);
492 return;
493 }
494 }
495 }
496 // Requested ID was not found
497 messages::resourceNotFound(asyncResp->res, "LogEntry", targetID);
498 }
499
handleSystemsAndManagersLogServicesEventLogActionsClearPost(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)500 inline void handleSystemsAndManagersLogServicesEventLogActionsClearPost(
501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
502 {
503 // Clear the EventLog by deleting the log files
504 std::vector<std::filesystem::path> redfishLogFiles;
505 if (getRedfishLogFiles(redfishLogFiles))
506 {
507 for (const std::filesystem::path& file : redfishLogFiles)
508 {
509 std::error_code ec;
510 std::filesystem::remove(file, ec);
511 }
512 }
513
514 // Reload rsyslog so it knows to start new log files
515 dbus::utility::async_method_call(
516 asyncResp,
517 [asyncResp](const boost::system::error_code& ec) {
518 if (ec)
519 {
520 BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec);
521 messages::internalError(asyncResp->res);
522 return;
523 }
524
525 messages::success(asyncResp->res);
526 },
527 "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
528 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service",
529 "replace");
530 }
531
532 /*
533 * DBus EventLog utilities
534 * */
535
getProviderNotifyAction(const std::string & notify)536 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
537 {
538 std::optional<bool> notifyAction;
539 if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
540 {
541 notifyAction = true;
542 }
543 else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
544 {
545 notifyAction = false;
546 }
547
548 return notifyAction;
549 }
550
translateSeverityDbusToRedfish(const std::string & s)551 inline std::string translateSeverityDbusToRedfish(const std::string& s)
552 {
553 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
554 (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
555 (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
556 (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
557 {
558 return "Critical";
559 }
560 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
561 (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
562 (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
563 {
564 return "OK";
565 }
566 if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
567 {
568 return "Warning";
569 }
570 return "";
571 }
572
fillEventLogLogEntryFromDbusLogEntry(const DbusEventLogEntry & entry,nlohmann::json & objectToFillOut,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor)573 inline void fillEventLogLogEntryFromDbusLogEntry(
574 const DbusEventLogEntry& entry, nlohmann::json& objectToFillOut,
575 const std::string& collectionStr, const std::string_view memberId,
576 const std::string& logEntryDescriptor)
577 {
578 objectToFillOut["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
579 objectToFillOut["@odata.id"] =
580 boost::urls::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries/{}",
581 collectionStr, memberId, std::to_string(entry.Id));
582 objectToFillOut["Name"] =
583 std::format("{} Event Log Entry", logEntryDescriptor);
584 objectToFillOut["Id"] = std::to_string(entry.Id);
585 objectToFillOut["Message"] = entry.Message;
586 objectToFillOut["Resolved"] = entry.Resolved;
587 std::optional<bool> notifyAction =
588 getProviderNotifyAction(entry.ServiceProviderNotify);
589 if (notifyAction)
590 {
591 objectToFillOut["ServiceProviderNotified"] = *notifyAction;
592 }
593 if ((entry.Resolution != nullptr) && !entry.Resolution->empty())
594 {
595 objectToFillOut["Resolution"] = *entry.Resolution;
596 }
597 objectToFillOut["EntryType"] = "Event";
598 objectToFillOut["Severity"] =
599 translateSeverityDbusToRedfish(entry.Severity);
600 objectToFillOut["Created"] =
601 redfish::time_utils::getDateTimeUintMs(entry.Timestamp);
602 objectToFillOut["Modified"] =
603 redfish::time_utils::getDateTimeUintMs(entry.UpdateTimestamp);
604 if (entry.Path != nullptr)
605 {
606 objectToFillOut["AdditionalDataURI"] = boost::urls::format(
607 "/redfish/v1/{}/{}/LogServices/EventLog/Entries/{}/attachment",
608 collectionStr, memberId, std::to_string(entry.Id));
609 }
610 }
611
afterLogEntriesGetManagedObjects(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor,const boost::system::error_code & ec,const dbus::utility::ManagedObjectType & resp)612 inline void afterLogEntriesGetManagedObjects(
613 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
614 const std::string& collectionStr, const std::string_view memberId,
615 const std::string& logEntryDescriptor, const boost::system::error_code& ec,
616 const dbus::utility::ManagedObjectType& resp)
617 {
618 if (ec)
619 {
620 // TODO Handle for specific error code
621 BMCWEB_LOG_ERROR("getLogEntriesIfaceData resp_handler got error {}",
622 ec);
623 messages::internalError(asyncResp->res);
624 return;
625 }
626 nlohmann::json::array_t entriesArray;
627 for (const auto& objectPath : resp)
628 {
629 dbus::utility::DBusPropertiesMap propsFlattened;
630 auto isEntry =
631 std::ranges::find_if(objectPath.second, [](const auto& object) {
632 return object.first == "xyz.openbmc_project.Logging.Entry";
633 });
634 if (isEntry == objectPath.second.end())
635 {
636 continue;
637 }
638
639 for (const auto& interfaceMap : objectPath.second)
640 {
641 for (const auto& propertyMap : interfaceMap.second)
642 {
643 propsFlattened.emplace_back(propertyMap.first,
644 propertyMap.second);
645 }
646 }
647 std::optional<DbusEventLogEntry> optEntry =
648 fillDbusEventLogEntryFromPropertyMap(propsFlattened);
649
650 if (!optEntry.has_value())
651 {
652 messages::internalError(asyncResp->res);
653 return;
654 }
655 fillEventLogLogEntryFromDbusLogEntry(
656 *optEntry, entriesArray.emplace_back(), collectionStr, memberId,
657 logEntryDescriptor);
658 }
659
660 redfish::json_util::sortJsonArrayByKey(entriesArray, "Id");
661 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
662 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
663 }
664
dBusEventLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,LogServiceParentCollection collection)665 inline void dBusEventLogEntryCollection(
666 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
667 LogServiceParentCollection collection)
668 {
669 const std::string_view memberId =
670 getMemberIdFromParentCollection(collection);
671 const std::string collectionStr =
672 logServiceParentCollectionToString(collection);
673 const std::string logEntryDescriptor =
674 getLogEntryDescriptorFromParentCollection(collection);
675
676 if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
677 {
678 messages::internalError(asyncResp->res);
679 return;
680 }
681
682 // Collections don't include the static data added by SubRoute
683 // because it has a duplicate entry for members
684 asyncResp->res.jsonValue["@odata.type"] =
685 "#LogEntryCollection.LogEntryCollection";
686 asyncResp->res.jsonValue["@odata.id"] =
687 std::format("/redfish/v1/{}/{}/LogServices/EventLog/Entries",
688 collectionStr, memberId);
689 asyncResp->res.jsonValue["Name"] =
690 std::format("{} Event Log Entries", logEntryDescriptor);
691 asyncResp->res.jsonValue["Description"] =
692 std::format("Collection of {} Event Log Entries", logEntryDescriptor);
693
694 // DBus implementation of EventLog/Entries
695 // Make call to Logging Service to find all log entry objects
696 sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
697 dbus::utility::getManagedObjects(
698 "xyz.openbmc_project.Logging", path,
699 [asyncResp, collectionStr, memberId,
700 logEntryDescriptor](const boost::system::error_code& ec,
701 const dbus::utility::ManagedObjectType& resp) {
702 afterLogEntriesGetManagedObjects(asyncResp, collectionStr, memberId,
703 logEntryDescriptor, ec, resp);
704 });
705 }
706
afterDBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & collectionStr,const std::string_view memberId,const std::string & logEntryDescriptor,const std::string & entryID,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & resp)707 inline void afterDBusEventLogEntryGet(
708 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
709 const std::string& collectionStr, const std::string_view memberId,
710 const std::string& logEntryDescriptor, const std::string& entryID,
711 const boost::system::error_code& ec,
712 const dbus::utility::DBusPropertiesMap& resp)
713 {
714 if (ec.value() == EBADR)
715 {
716 messages::resourceNotFound(asyncResp->res, "EventLogEntry", entryID);
717 return;
718 }
719 if (ec)
720 {
721 BMCWEB_LOG_ERROR("EventLogEntry (DBus) resp_handler got error {}", ec);
722 messages::internalError(asyncResp->res);
723 return;
724 }
725
726 std::optional<DbusEventLogEntry> optEntry =
727 fillDbusEventLogEntryFromPropertyMap(resp);
728
729 if (!optEntry.has_value())
730 {
731 messages::internalError(asyncResp->res);
732 return;
733 }
734
735 fillEventLogLogEntryFromDbusLogEntry(
736 *optEntry, asyncResp->res.jsonValue, collectionStr, memberId,
737 logEntryDescriptor);
738 }
739
dBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,LogServiceParentCollection collection,std::string entryID)740 inline void dBusEventLogEntryGet(
741 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
742 LogServiceParentCollection collection, std::string entryID)
743 {
744 const std::string collectionStr =
745 logServiceParentCollectionToString(collection);
746 const std::string_view memberId =
747 getMemberIdFromParentCollection(collection);
748 const std::string logEntryDescriptor =
749 getLogEntryDescriptorFromParentCollection(collection);
750
751 if (collectionStr.empty() || memberId.empty() || logEntryDescriptor.empty())
752 {
753 messages::internalError(asyncResp->res);
754 return;
755 }
756
757 dbus::utility::escapePathForDbus(entryID);
758
759 // DBus implementation of EventLog/Entries
760 // Make call to Logging Service to find all log entry objects
761 dbus::utility::getAllProperties(
762 "xyz.openbmc_project.Logging",
763 "/xyz/openbmc_project/logging/entry/" + entryID, "",
764 std::bind_front(afterDBusEventLogEntryGet, asyncResp, collectionStr,
765 memberId, logEntryDescriptor, entryID));
766 }
767
dBusEventLogEntryPatch(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryId)768 inline void dBusEventLogEntryPatch(
769 const crow::Request& req,
770 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
771 const std::string& entryId)
772 {
773 std::optional<bool> resolved;
774
775 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
776 {
777 return;
778 }
779 BMCWEB_LOG_DEBUG("Set Resolved");
780
781 setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
782 "/xyz/openbmc_project/logging/entry/" + entryId,
783 "xyz.openbmc_project.Logging.Entry", "Resolved",
784 resolved.value_or(false));
785 }
786
dBusEventLogEntryDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)787 inline void dBusEventLogEntryDelete(
788 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
789 {
790 BMCWEB_LOG_DEBUG("Do delete single event entries.");
791
792 dbus::utility::escapePathForDbus(entryID);
793
794 // Process response from Logging service.
795 auto respHandler = [asyncResp,
796 entryID](const boost::system::error_code& ec) {
797 BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
798 if (ec)
799 {
800 if (ec.value() == EBADR)
801 {
802 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
803 return;
804 }
805 // TODO Handle for specific error code
806 BMCWEB_LOG_ERROR(
807 "EventLogEntry (DBus) doDelete respHandler got error {}", ec);
808 asyncResp->res.result(
809 boost::beast::http::status::internal_server_error);
810 return;
811 }
812
813 messages::success(asyncResp->res);
814 };
815
816 // Make call to Logging service to request Delete Log
817 dbus::utility::async_method_call(
818 asyncResp, respHandler, "xyz.openbmc_project.Logging",
819 "/xyz/openbmc_project/logging/entry/" + entryID,
820 "xyz.openbmc_project.Object.Delete", "Delete");
821 }
822
dBusLogServiceActionsClear(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)823 inline void dBusLogServiceActionsClear(
824 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
825 {
826 BMCWEB_LOG_DEBUG("Do delete all entries.");
827
828 // Process response from Logging service.
829 auto respHandler = [asyncResp](const boost::system::error_code& ec) {
830 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
831 if (ec)
832 {
833 // TODO Handle for specific error code
834 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
835 asyncResp->res.result(
836 boost::beast::http::status::internal_server_error);
837 return;
838 }
839
840 messages::success(asyncResp->res);
841 };
842
843 // Make call to Logging service to request Clear Log
844 dbus::utility::async_method_call(
845 asyncResp, respHandler, "xyz.openbmc_project.Logging",
846 "/xyz/openbmc_project/logging",
847 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
848 }
849
downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const std::string & downloadEntryType)850 inline void downloadEventLogEntry(
851 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
852 const std::string& entryID, const std::string& downloadEntryType)
853 {
854 std::string entryPath =
855 sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
856 entryID;
857
858 auto downloadEventLogEntryHandler =
859 [asyncResp, entryID,
860 downloadEntryType](const boost::system::error_code& ec,
861 const sdbusplus::message::unix_fd& unixfd) {
862 log_services_utils::downloadEntryCallback(
863 asyncResp, entryID, downloadEntryType, ec, unixfd);
864 };
865
866 dbus::utility::async_method_call(
867 asyncResp, std::move(downloadEventLogEntryHandler),
868 "xyz.openbmc_project.Logging", entryPath,
869 "xyz.openbmc_project.Logging.Entry", "GetEntry");
870 }
871 } // namespace eventlog_utils
872 } // namespace redfish
873