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 "bmcweb_config.h"
7
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_utility.hpp"
11 #include "error_messages.hpp"
12 #include "http_body.hpp"
13 #include "http_request.hpp"
14 #include "http_response.hpp"
15 #include "http_utility.hpp"
16 #include "logging.hpp"
17 #include "query.hpp"
18 #include "registries/privilege_registry.hpp"
19 #include "utils/dbus_event_log_entry.hpp"
20 #include "utils/dbus_utils.hpp"
21 #include "utils/json_utils.hpp"
22 #include "utils/log_services_utils.hpp"
23 #include "utils/time_utils.hpp"
24
25 #include <asm-generic/errno.h>
26 #include <systemd/sd-bus.h>
27 #include <unistd.h>
28
29 #include <boost/beast/http/field.hpp>
30 #include <boost/beast/http/status.hpp>
31 #include <boost/beast/http/verb.hpp>
32 #include <boost/system/linux_error.hpp>
33 #include <boost/url/format.hpp>
34 #include <boost/url/url.hpp>
35 #include <sdbusplus/message.hpp>
36 #include <sdbusplus/message/native_types.hpp>
37 #include <sdbusplus/unpack_properties.hpp>
38
39 #include <algorithm>
40 #include <format>
41 #include <functional>
42 #include <memory>
43 #include <optional>
44 #include <string>
45 #include <string_view>
46 #include <utility>
47 #include <vector>
48
49 namespace redfish
50 {
51
getProviderNotifyAction(const std::string & notify)52 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
53 {
54 std::optional<bool> notifyAction;
55 if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
56 {
57 notifyAction = true;
58 }
59 else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
60 {
61 notifyAction = false;
62 }
63
64 return notifyAction;
65 }
66
translateSeverityDbusToRedfish(const std::string & s)67 inline std::string translateSeverityDbusToRedfish(const std::string& s)
68 {
69 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
70 (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
71 (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
72 (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
73 {
74 return "Critical";
75 }
76 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
77 (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
78 (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
79 {
80 return "OK";
81 }
82 if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
83 {
84 return "Warning";
85 }
86 return "";
87 }
88
fillEventLogLogEntryFromDbusLogEntry(const DbusEventLogEntry & entry,nlohmann::json & objectToFillOut)89 inline void fillEventLogLogEntryFromDbusLogEntry(
90 const DbusEventLogEntry& entry, nlohmann::json& objectToFillOut)
91 {
92 objectToFillOut["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
93 objectToFillOut["@odata.id"] = boost::urls::format(
94 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
95 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
96 objectToFillOut["Name"] = "System Event Log Entry";
97 objectToFillOut["Id"] = std::to_string(entry.Id);
98 objectToFillOut["Message"] = entry.Message;
99 objectToFillOut["Resolved"] = entry.Resolved;
100 std::optional<bool> notifyAction =
101 getProviderNotifyAction(entry.ServiceProviderNotify);
102 if (notifyAction)
103 {
104 objectToFillOut["ServiceProviderNotified"] = *notifyAction;
105 }
106 if ((entry.Resolution != nullptr) && !entry.Resolution->empty())
107 {
108 objectToFillOut["Resolution"] = *entry.Resolution;
109 }
110 objectToFillOut["EntryType"] = "Event";
111 objectToFillOut["Severity"] =
112 translateSeverityDbusToRedfish(entry.Severity);
113 objectToFillOut["Created"] =
114 redfish::time_utils::getDateTimeUintMs(entry.Timestamp);
115 objectToFillOut["Modified"] =
116 redfish::time_utils::getDateTimeUintMs(entry.UpdateTimestamp);
117 if (entry.Path != nullptr)
118 {
119 objectToFillOut["AdditionalDataURI"] = boost::urls::format(
120 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}/attachment",
121 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
122 }
123 }
124
afterLogEntriesGetManagedObjects(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const dbus::utility::ManagedObjectType & resp)125 inline void afterLogEntriesGetManagedObjects(
126 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
127 const boost::system::error_code& ec,
128 const dbus::utility::ManagedObjectType& resp)
129 {
130 if (ec)
131 {
132 // TODO Handle for specific error code
133 BMCWEB_LOG_ERROR("getLogEntriesIfaceData resp_handler got error {}",
134 ec);
135 messages::internalError(asyncResp->res);
136 return;
137 }
138 nlohmann::json::array_t entriesArray;
139 for (const auto& objectPath : resp)
140 {
141 dbus::utility::DBusPropertiesMap propsFlattened;
142 auto isEntry =
143 std::ranges::find_if(objectPath.second, [](const auto& object) {
144 return object.first == "xyz.openbmc_project.Logging.Entry";
145 });
146 if (isEntry == objectPath.second.end())
147 {
148 continue;
149 }
150
151 for (const auto& interfaceMap : objectPath.second)
152 {
153 for (const auto& propertyMap : interfaceMap.second)
154 {
155 propsFlattened.emplace_back(propertyMap.first,
156 propertyMap.second);
157 }
158 }
159 std::optional<DbusEventLogEntry> optEntry =
160 fillDbusEventLogEntryFromPropertyMap(propsFlattened);
161
162 if (!optEntry.has_value())
163 {
164 messages::internalError(asyncResp->res);
165 return;
166 }
167 fillEventLogLogEntryFromDbusLogEntry(*optEntry,
168 entriesArray.emplace_back());
169 }
170
171 redfish::json_util::sortJsonArrayByKey(entriesArray, "Id");
172 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
173 asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
174 }
175
dBusEventLogEntryCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)176 inline void dBusEventLogEntryCollection(
177 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
178 {
179 // Collections don't include the static data added by SubRoute
180 // because it has a duplicate entry for members
181 asyncResp->res.jsonValue["@odata.type"] =
182 "#LogEntryCollection.LogEntryCollection";
183 asyncResp->res.jsonValue["@odata.id"] =
184 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
185 BMCWEB_REDFISH_SYSTEM_URI_NAME);
186 asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
187 asyncResp->res.jsonValue["Description"] =
188 "Collection of System Event Log Entries";
189
190 // DBus implementation of EventLog/Entries
191 // Make call to Logging Service to find all log entry objects
192 sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
193 dbus::utility::getManagedObjects(
194 "xyz.openbmc_project.Logging", path,
195 [asyncResp](const boost::system::error_code& ec,
196 const dbus::utility::ManagedObjectType& resp) {
197 afterLogEntriesGetManagedObjects(asyncResp, ec, resp);
198 });
199 }
200
afterDBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryID,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & resp)201 inline void afterDBusEventLogEntryGet(
202 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
203 const std::string& entryID, const boost::system::error_code& ec,
204 const dbus::utility::DBusPropertiesMap& resp)
205 {
206 if (ec.value() == EBADR)
207 {
208 messages::resourceNotFound(asyncResp->res, "EventLogEntry", entryID);
209 return;
210 }
211 if (ec)
212 {
213 BMCWEB_LOG_ERROR("EventLogEntry (DBus) resp_handler got error {}", ec);
214 messages::internalError(asyncResp->res);
215 return;
216 }
217
218 std::optional<DbusEventLogEntry> optEntry =
219 fillDbusEventLogEntryFromPropertyMap(resp);
220
221 if (!optEntry.has_value())
222 {
223 messages::internalError(asyncResp->res);
224 return;
225 }
226
227 fillEventLogLogEntryFromDbusLogEntry(*optEntry, asyncResp->res.jsonValue);
228 }
229
dBusEventLogEntryGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)230 inline void dBusEventLogEntryGet(
231 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
232 {
233 dbus::utility::escapePathForDbus(entryID);
234
235 // DBus implementation of EventLog/Entries
236 // Make call to Logging Service to find all log entry objects
237 dbus::utility::getAllProperties(
238 "xyz.openbmc_project.Logging",
239 "/xyz/openbmc_project/logging/entry/" + entryID, "",
240 std::bind_front(afterDBusEventLogEntryGet, asyncResp, entryID));
241 }
242
dBusEventLogEntryPatch(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & entryId)243 inline void dBusEventLogEntryPatch(
244 const crow::Request& req,
245 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
246 const std::string& entryId)
247 {
248 std::optional<bool> resolved;
249
250 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
251 {
252 return;
253 }
254 BMCWEB_LOG_DEBUG("Set Resolved");
255
256 setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
257 "/xyz/openbmc_project/logging/entry/" + entryId,
258 "xyz.openbmc_project.Logging.Entry", "Resolved",
259 resolved.value_or(false));
260 }
261
dBusEventLogEntryDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string entryID)262 inline void dBusEventLogEntryDelete(
263 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
264 {
265 BMCWEB_LOG_DEBUG("Do delete single event entries.");
266
267 dbus::utility::escapePathForDbus(entryID);
268
269 // Process response from Logging service.
270 auto respHandler = [asyncResp,
271 entryID](const boost::system::error_code& ec) {
272 BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
273 if (ec)
274 {
275 if (ec.value() == EBADR)
276 {
277 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
278 return;
279 }
280 // TODO Handle for specific error code
281 BMCWEB_LOG_ERROR(
282 "EventLogEntry (DBus) doDelete respHandler got error {}", ec);
283 asyncResp->res.result(
284 boost::beast::http::status::internal_server_error);
285 return;
286 }
287
288 messages::success(asyncResp->res);
289 };
290
291 // Make call to Logging service to request Delete Log
292 dbus::utility::async_method_call(
293 asyncResp, respHandler, "xyz.openbmc_project.Logging",
294 "/xyz/openbmc_project/logging/entry/" + entryID,
295 "xyz.openbmc_project.Object.Delete", "Delete");
296 }
297
dBusLogServiceActionsClear(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)298 inline void dBusLogServiceActionsClear(
299 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
300 {
301 BMCWEB_LOG_DEBUG("Do delete all entries.");
302
303 // Process response from Logging service.
304 auto respHandler = [asyncResp](const boost::system::error_code& ec) {
305 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
306 if (ec)
307 {
308 // TODO Handle for specific error code
309 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
310 asyncResp->res.result(
311 boost::beast::http::status::internal_server_error);
312 return;
313 }
314
315 messages::success(asyncResp->res);
316 };
317
318 // Make call to Logging service to request Clear Log
319 dbus::utility::async_method_call(
320 asyncResp, respHandler, "xyz.openbmc_project.Logging",
321 "/xyz/openbmc_project/logging",
322 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
323 }
324
downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & systemName,const std::string & entryID,const std::string & dumpType)325 inline void downloadEventLogEntry(
326 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
327 const std::string& systemName, const std::string& entryID,
328 const std::string& dumpType)
329 {
330 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
331 {
332 // Option currently returns no systems. TBD
333 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
334 systemName);
335 return;
336 }
337 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
338 {
339 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
340 systemName);
341 return;
342 }
343
344 std::string entryPath =
345 sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
346 entryID;
347
348 auto downloadEventLogEntryHandler =
349 [asyncResp, entryID,
350 dumpType](const boost::system::error_code& ec,
351 const sdbusplus::message::unix_fd& unixfd) {
352 log_services_utils::downloadEntryCallback(asyncResp, entryID,
353 dumpType, ec, unixfd);
354 };
355
356 dbus::utility::async_method_call(
357 asyncResp, std::move(downloadEventLogEntryHandler),
358 "xyz.openbmc_project.Logging", entryPath,
359 "xyz.openbmc_project.Logging.Entry", "GetEntry");
360 }
361
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)362 inline void handleDBusEventLogEntryDownloadGet(
363 crow::App& app, const std::string& dumpType, const crow::Request& req,
364 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
365 const std::string& systemName, const std::string& entryID)
366 {
367 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
368 {
369 return;
370 }
371 if (!http_helpers::isContentTypeAllowed(
372 req.getHeaderValue("Accept"),
373 http_helpers::ContentType::OctetStream, true))
374 {
375 asyncResp->res.result(boost::beast::http::status::bad_request);
376 return;
377 }
378 downloadEventLogEntry(asyncResp, systemName, entryID, dumpType);
379 }
380
requestRoutesDBusEventLogEntryCollection(App & app)381 inline void requestRoutesDBusEventLogEntryCollection(App& app)
382 {
383 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
384 .privileges(redfish::privileges::getLogEntryCollection)
385 .methods(boost::beast::http::verb::get)(
386 [&app](const crow::Request& req,
387 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
388 const std::string& systemName) {
389 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
390 {
391 return;
392 }
393 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
394 {
395 // Option currently returns no systems. TBD
396 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
397 systemName);
398 return;
399 }
400 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
401 {
402 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
403 systemName);
404 return;
405 }
406 dBusEventLogEntryCollection(asyncResp);
407 });
408 }
409
requestRoutesDBusEventLogEntry(App & app)410 inline void requestRoutesDBusEventLogEntry(App& app)
411 {
412 BMCWEB_ROUTE(
413 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
414 .privileges(redfish::privileges::getLogEntry)
415 .methods(boost::beast::http::verb::get)(
416 [&app](const crow::Request& req,
417 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
418 const std::string& systemName, const std::string& entryId) {
419 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
420 {
421 return;
422 }
423 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
424 {
425 // Option currently returns no systems. TBD
426 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
427 systemName);
428 return;
429 }
430 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
431 {
432 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
433 systemName);
434 return;
435 }
436
437 dBusEventLogEntryGet(asyncResp, entryId);
438 });
439
440 BMCWEB_ROUTE(
441 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
442 .privileges(redfish::privileges::patchLogEntry)
443 .methods(boost::beast::http::verb::patch)(
444 [&app](const crow::Request& req,
445 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
446 const std::string& systemName, const std::string& entryId) {
447 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
448 {
449 return;
450 }
451 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
452 {
453 // Option currently returns no systems. TBD
454 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
455 systemName);
456 return;
457 }
458 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
459 {
460 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
461 systemName);
462 return;
463 }
464
465 dBusEventLogEntryPatch(req, asyncResp, entryId);
466 });
467
468 BMCWEB_ROUTE(
469 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
470 .privileges(
471 redfish::privileges::
472 deleteLogEntrySubOverComputerSystemLogServiceCollectionLogServiceLogEntryCollection)
473 .methods(boost::beast::http::verb::delete_)(
474 [&app](const crow::Request& req,
475 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
476 const std::string& systemName, const std::string& param) {
477 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
478 {
479 return;
480 }
481 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
482 {
483 // Option currently returns no systems. TBD
484 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
485 systemName);
486 return;
487 }
488 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
489 {
490 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
491 systemName);
492 return;
493 }
494 dBusEventLogEntryDelete(asyncResp, param);
495 });
496 }
497
498 /**
499 * DBusLogServiceActionsClear class supports POST method for ClearLog action.
500 */
requestRoutesDBusLogServiceActionsClear(App & app)501 inline void requestRoutesDBusLogServiceActionsClear(App& app)
502 {
503 /**
504 * Function handles POST method request.
505 * The Clear Log actions does not require any parameter.The action deletes
506 * all entries found in the Entries collection for this Log Service.
507 */
508
509 BMCWEB_ROUTE(
510 app,
511 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
512 .privileges(redfish::privileges::
513 postLogServiceSubOverComputerSystemLogServiceCollection)
514 .methods(boost::beast::http::verb::post)(
515 [&app](const crow::Request& req,
516 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
517 const std::string& systemName) {
518 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
519 {
520 return;
521 }
522 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
523 {
524 // Option currently returns no systems. TBD
525 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
526 systemName);
527 return;
528 }
529 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
530 {
531 messages::resourceNotFound(asyncResp->res, "ComputerSystem",
532 systemName);
533 return;
534 }
535 dBusLogServiceActionsClear(asyncResp);
536 });
537 }
538
requestRoutesDBusEventLogEntryDownload(App & app)539 inline void requestRoutesDBusEventLogEntryDownload(App& app)
540 {
541 BMCWEB_ROUTE(
542 app,
543 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/")
544 .privileges(redfish::privileges::getLogEntry)
545 .methods(boost::beast::http::verb::get)(std::bind_front(
546 handleDBusEventLogEntryDownloadGet, std::ref(app), "System"));
547 }
548 } // namespace redfish
549