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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 */ 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 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