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