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