1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "app.hpp" 19 #include "dbus_utility.hpp" 20 #include "error_messages.hpp" 21 #include "generated/enums/log_entry.hpp" 22 #include "gzfile.hpp" 23 #include "http_utility.hpp" 24 #include "human_sort.hpp" 25 #include "query.hpp" 26 #include "registries.hpp" 27 #include "registries/base_message_registry.hpp" 28 #include "registries/openbmc_message_registry.hpp" 29 #include "registries/privilege_registry.hpp" 30 #include "task.hpp" 31 #include "task_messages.hpp" 32 #include "utils/dbus_utils.hpp" 33 #include "utils/json_utils.hpp" 34 #include "utils/time_utils.hpp" 35 36 #include <systemd/sd-id128.h> 37 #include <systemd/sd-journal.h> 38 #include <tinyxml2.h> 39 #include <unistd.h> 40 41 #include <boost/beast/http/verb.hpp> 42 #include <boost/container/flat_map.hpp> 43 #include <boost/system/linux_error.hpp> 44 #include <boost/url/format.hpp> 45 #include <sdbusplus/asio/property.hpp> 46 #include <sdbusplus/unpack_properties.hpp> 47 48 #include <array> 49 #include <charconv> 50 #include <cstddef> 51 #include <filesystem> 52 #include <iterator> 53 #include <optional> 54 #include <ranges> 55 #include <span> 56 #include <string> 57 #include <string_view> 58 #include <variant> 59 60 namespace redfish 61 { 62 63 constexpr const char* crashdumpObject = "com.intel.crashdump"; 64 constexpr const char* crashdumpPath = "/com/intel/crashdump"; 65 constexpr const char* crashdumpInterface = "com.intel.crashdump"; 66 constexpr const char* deleteAllInterface = 67 "xyz.openbmc_project.Collection.DeleteAll"; 68 constexpr const char* crashdumpOnDemandInterface = 69 "com.intel.crashdump.OnDemand"; 70 constexpr const char* crashdumpTelemetryInterface = 71 "com.intel.crashdump.Telemetry"; 72 73 enum class DumpCreationProgress 74 { 75 DUMP_CREATE_SUCCESS, 76 DUMP_CREATE_FAILED, 77 DUMP_CREATE_INPROGRESS 78 }; 79 80 namespace fs = std::filesystem; 81 82 inline std::string translateSeverityDbusToRedfish(const std::string& s) 83 { 84 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") || 85 (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") || 86 (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") || 87 (s == "xyz.openbmc_project.Logging.Entry.Level.Error")) 88 { 89 return "Critical"; 90 } 91 if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") || 92 (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") || 93 (s == "xyz.openbmc_project.Logging.Entry.Level.Notice")) 94 { 95 return "OK"; 96 } 97 if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 98 { 99 return "Warning"; 100 } 101 return ""; 102 } 103 104 inline std::optional<bool> getProviderNotifyAction(const std::string& notify) 105 { 106 std::optional<bool> notifyAction; 107 if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify") 108 { 109 notifyAction = true; 110 } 111 else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit") 112 { 113 notifyAction = false; 114 } 115 116 return notifyAction; 117 } 118 119 inline std::string getDumpPath(std::string_view dumpType) 120 { 121 std::string dbusDumpPath = "/xyz/openbmc_project/dump/"; 122 std::ranges::transform(dumpType, std::back_inserter(dbusDumpPath), 123 bmcweb::asciiToLower); 124 125 return dbusDumpPath; 126 } 127 128 inline int getJournalMetadata(sd_journal* journal, std::string_view field, 129 std::string_view& contents) 130 { 131 const char* data = nullptr; 132 size_t length = 0; 133 int ret = 0; 134 // Get the metadata from the requested field of the journal entry 135 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 136 const void** dataVoid = reinterpret_cast<const void**>(&data); 137 138 ret = sd_journal_get_data(journal, field.data(), dataVoid, &length); 139 if (ret < 0) 140 { 141 return ret; 142 } 143 contents = std::string_view(data, length); 144 // Only use the content after the "=" character. 145 contents.remove_prefix(std::min(contents.find('=') + 1, contents.size())); 146 return ret; 147 } 148 149 inline int getJournalMetadata(sd_journal* journal, std::string_view field, 150 const int& base, long int& contents) 151 { 152 int ret = 0; 153 std::string_view metadata; 154 // Get the metadata from the requested field of the journal entry 155 ret = getJournalMetadata(journal, field, metadata); 156 if (ret < 0) 157 { 158 return ret; 159 } 160 contents = strtol(metadata.data(), nullptr, base); 161 return ret; 162 } 163 164 inline bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) 165 { 166 int ret = 0; 167 uint64_t timestamp = 0; 168 ret = sd_journal_get_realtime_usec(journal, ×tamp); 169 if (ret < 0) 170 { 171 BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); 172 return false; 173 } 174 entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp); 175 return true; 176 } 177 178 inline bool getUniqueEntryID(sd_journal* journal, std::string& entryID, 179 const bool firstEntry = true) 180 { 181 int ret = 0; 182 static sd_id128_t prevBootID{}; 183 static uint64_t prevTs = 0; 184 static int index = 0; 185 if (firstEntry) 186 { 187 prevBootID = {}; 188 prevTs = 0; 189 } 190 191 // Get the entry timestamp 192 uint64_t curTs = 0; 193 sd_id128_t curBootID{}; 194 ret = sd_journal_get_monotonic_usec(journal, &curTs, &curBootID); 195 if (ret < 0) 196 { 197 BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); 198 return false; 199 } 200 // If the timestamp isn't unique on the same boot, increment the index 201 bool sameBootIDs = sd_id128_equal(curBootID, prevBootID) != 0; 202 if (sameBootIDs && (curTs == prevTs)) 203 { 204 index++; 205 } 206 else 207 { 208 // Otherwise, reset it 209 index = 0; 210 } 211 212 if (!sameBootIDs) 213 { 214 // Save the bootID 215 prevBootID = curBootID; 216 } 217 // Save the timestamp 218 prevTs = curTs; 219 220 // make entryID as <bootID>_<timestamp>[_<index>] 221 std::array<char, SD_ID128_STRING_MAX> bootIDStr{}; 222 sd_id128_to_string(curBootID, bootIDStr.data()); 223 entryID = std::format("{}_{}", bootIDStr.data(), curTs); 224 if (index > 0) 225 { 226 entryID += "_" + std::to_string(index); 227 } 228 return true; 229 } 230 231 static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, 232 const bool firstEntry = true) 233 { 234 static time_t prevTs = 0; 235 static int index = 0; 236 if (firstEntry) 237 { 238 prevTs = 0; 239 } 240 241 // Get the entry timestamp 242 std::time_t curTs = 0; 243 std::tm timeStruct = {}; 244 std::istringstream entryStream(logEntry); 245 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 246 { 247 curTs = std::mktime(&timeStruct); 248 } 249 // If the timestamp isn't unique, increment the index 250 if (curTs == prevTs) 251 { 252 index++; 253 } 254 else 255 { 256 // Otherwise, reset it 257 index = 0; 258 } 259 // Save the timestamp 260 prevTs = curTs; 261 262 entryID = std::to_string(curTs); 263 if (index > 0) 264 { 265 entryID += "_" + std::to_string(index); 266 } 267 return true; 268 } 269 270 // Entry is formed like "BootID_timestamp" or "BootID_timestamp_index" 271 inline bool 272 getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 273 std::string_view entryIDStrView, sd_id128_t& bootID, 274 uint64_t& timestamp, uint64_t& index) 275 { 276 // Convert the unique ID back to a bootID + timestamp to find the entry 277 auto underscore1Pos = entryIDStrView.find('_'); 278 if (underscore1Pos == std::string_view::npos) 279 { 280 // EntryID has no bootID or timestamp 281 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 282 return false; 283 } 284 285 // EntryID has bootID + timestamp 286 287 // Convert entryIDViewString to BootID 288 // NOTE: bootID string which needs to be null-terminated for 289 // sd_id128_from_string() 290 std::string bootIDStr(entryIDStrView.substr(0, underscore1Pos)); 291 if (sd_id128_from_string(bootIDStr.c_str(), &bootID) < 0) 292 { 293 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 294 return false; 295 } 296 297 // Get the timestamp from entryID 298 entryIDStrView.remove_prefix(underscore1Pos + 1); 299 300 auto [timestampEnd, tstampEc] = std::from_chars( 301 entryIDStrView.begin(), entryIDStrView.end(), timestamp); 302 if (tstampEc != std::errc()) 303 { 304 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 305 return false; 306 } 307 entryIDStrView = std::string_view( 308 timestampEnd, 309 static_cast<size_t>(std::distance(timestampEnd, entryIDStrView.end()))); 310 if (entryIDStrView.empty()) 311 { 312 index = 0U; 313 return true; 314 } 315 // Timestamp might include optional index, if two events happened at the 316 // same "time". 317 if (entryIDStrView[0] != '_') 318 { 319 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 320 return false; 321 } 322 entryIDStrView.remove_prefix(1); 323 auto [ptr, indexEc] = std::from_chars(entryIDStrView.begin(), 324 entryIDStrView.end(), index); 325 if (indexEc != std::errc() || ptr != entryIDStrView.end()) 326 { 327 messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); 328 return false; 329 } 330 return true; 331 } 332 333 static bool 334 getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles) 335 { 336 static const std::filesystem::path redfishLogDir = "/var/log"; 337 static const std::string redfishLogFilename = "redfish"; 338 339 // Loop through the directory looking for redfish log files 340 for (const std::filesystem::directory_entry& dirEnt : 341 std::filesystem::directory_iterator(redfishLogDir)) 342 { 343 // If we find a redfish log file, save the path 344 std::string filename = dirEnt.path().filename(); 345 if (filename.starts_with(redfishLogFilename)) 346 { 347 redfishLogFiles.emplace_back(redfishLogDir / filename); 348 } 349 } 350 // As the log files rotate, they are appended with a ".#" that is higher for 351 // the older logs. Since we don't expect more than 10 log files, we 352 // can just sort the list to get them in order from newest to oldest 353 std::ranges::sort(redfishLogFiles); 354 355 return !redfishLogFiles.empty(); 356 } 357 358 inline log_entry::OriginatorTypes 359 mapDbusOriginatorTypeToRedfish(const std::string& originatorType) 360 { 361 if (originatorType == 362 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client") 363 { 364 return log_entry::OriginatorTypes::Client; 365 } 366 if (originatorType == 367 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Internal") 368 { 369 return log_entry::OriginatorTypes::Internal; 370 } 371 if (originatorType == 372 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.SupportingService") 373 { 374 return log_entry::OriginatorTypes::SupportingService; 375 } 376 return log_entry::OriginatorTypes::Invalid; 377 } 378 379 inline void parseDumpEntryFromDbusObject( 380 const dbus::utility::ManagedObjectType::value_type& object, 381 std::string& dumpStatus, uint64_t& size, uint64_t& timestampUs, 382 std::string& originatorId, log_entry::OriginatorTypes& originatorType, 383 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 384 { 385 for (const auto& interfaceMap : object.second) 386 { 387 if (interfaceMap.first == "xyz.openbmc_project.Common.Progress") 388 { 389 for (const auto& propertyMap : interfaceMap.second) 390 { 391 if (propertyMap.first == "Status") 392 { 393 const auto* status = 394 std::get_if<std::string>(&propertyMap.second); 395 if (status == nullptr) 396 { 397 messages::internalError(asyncResp->res); 398 break; 399 } 400 dumpStatus = *status; 401 } 402 } 403 } 404 else if (interfaceMap.first == "xyz.openbmc_project.Dump.Entry") 405 { 406 for (const auto& propertyMap : interfaceMap.second) 407 { 408 if (propertyMap.first == "Size") 409 { 410 const auto* sizePtr = 411 std::get_if<uint64_t>(&propertyMap.second); 412 if (sizePtr == nullptr) 413 { 414 messages::internalError(asyncResp->res); 415 break; 416 } 417 size = *sizePtr; 418 break; 419 } 420 } 421 } 422 else if (interfaceMap.first == "xyz.openbmc_project.Time.EpochTime") 423 { 424 for (const auto& propertyMap : interfaceMap.second) 425 { 426 if (propertyMap.first == "Elapsed") 427 { 428 const uint64_t* usecsTimeStamp = 429 std::get_if<uint64_t>(&propertyMap.second); 430 if (usecsTimeStamp == nullptr) 431 { 432 messages::internalError(asyncResp->res); 433 break; 434 } 435 timestampUs = *usecsTimeStamp; 436 break; 437 } 438 } 439 } 440 else if (interfaceMap.first == 441 "xyz.openbmc_project.Common.OriginatedBy") 442 { 443 for (const auto& propertyMap : interfaceMap.second) 444 { 445 if (propertyMap.first == "OriginatorId") 446 { 447 const std::string* id = 448 std::get_if<std::string>(&propertyMap.second); 449 if (id == nullptr) 450 { 451 messages::internalError(asyncResp->res); 452 break; 453 } 454 originatorId = *id; 455 } 456 457 if (propertyMap.first == "OriginatorType") 458 { 459 const std::string* type = 460 std::get_if<std::string>(&propertyMap.second); 461 if (type == nullptr) 462 { 463 messages::internalError(asyncResp->res); 464 break; 465 } 466 467 originatorType = mapDbusOriginatorTypeToRedfish(*type); 468 if (originatorType == log_entry::OriginatorTypes::Invalid) 469 { 470 messages::internalError(asyncResp->res); 471 break; 472 } 473 } 474 } 475 } 476 } 477 } 478 479 static std::string getDumpEntriesPath(const std::string& dumpType) 480 { 481 std::string entriesPath; 482 483 if (dumpType == "BMC") 484 { 485 entriesPath = 486 std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/", 487 BMCWEB_REDFISH_MANAGER_URI_NAME); 488 } 489 else if (dumpType == "FaultLog") 490 { 491 entriesPath = 492 std::format("/redfish/v1/Managers/{}/LogServices/FaultLog/Entries/", 493 BMCWEB_REDFISH_MANAGER_URI_NAME); 494 } 495 else if (dumpType == "System") 496 { 497 entriesPath = 498 std::format("/redfish/v1/Systems/{}/LogServices/Dump/Entries/", 499 BMCWEB_REDFISH_SYSTEM_URI_NAME); 500 } 501 else 502 { 503 BMCWEB_LOG_ERROR("getDumpEntriesPath() invalid dump type: {}", 504 dumpType); 505 } 506 507 // Returns empty string on error 508 return entriesPath; 509 } 510 511 inline void 512 getDumpEntryCollection(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 513 const std::string& dumpType) 514 { 515 std::string entriesPath = getDumpEntriesPath(dumpType); 516 if (entriesPath.empty()) 517 { 518 messages::internalError(asyncResp->res); 519 return; 520 } 521 522 sdbusplus::message::object_path path("/xyz/openbmc_project/dump"); 523 dbus::utility::getManagedObjects( 524 "xyz.openbmc_project.Dump.Manager", path, 525 [asyncResp, entriesPath, 526 dumpType](const boost::system::error_code& ec, 527 const dbus::utility::ManagedObjectType& objects) { 528 if (ec) 529 { 530 BMCWEB_LOG_ERROR("DumpEntry resp_handler got error {}", ec); 531 messages::internalError(asyncResp->res); 532 return; 533 } 534 535 // Remove ending slash 536 std::string odataIdStr = entriesPath; 537 if (!odataIdStr.empty()) 538 { 539 odataIdStr.pop_back(); 540 } 541 542 asyncResp->res.jsonValue["@odata.type"] = 543 "#LogEntryCollection.LogEntryCollection"; 544 asyncResp->res.jsonValue["@odata.id"] = std::move(odataIdStr); 545 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entries"; 546 asyncResp->res.jsonValue["Description"] = "Collection of " + dumpType + 547 " Dump Entries"; 548 549 nlohmann::json::array_t entriesArray; 550 std::string dumpEntryPath = getDumpPath(dumpType) + "/entry/"; 551 552 dbus::utility::ManagedObjectType resp(objects); 553 std::ranges::sort(resp, [](const auto& l, const auto& r) { 554 return AlphanumLess<std::string>()(l.first.filename(), 555 r.first.filename()); 556 }); 557 558 for (auto& object : resp) 559 { 560 if (object.first.str.find(dumpEntryPath) == std::string::npos) 561 { 562 continue; 563 } 564 uint64_t timestampUs = 0; 565 uint64_t size = 0; 566 std::string dumpStatus; 567 std::string originatorId; 568 log_entry::OriginatorTypes originatorType = 569 log_entry::OriginatorTypes::Internal; 570 nlohmann::json::object_t thisEntry; 571 572 std::string entryID = object.first.filename(); 573 if (entryID.empty()) 574 { 575 continue; 576 } 577 578 parseDumpEntryFromDbusObject(object, dumpStatus, size, timestampUs, 579 originatorId, originatorType, 580 asyncResp); 581 582 if (dumpStatus != 583 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" && 584 !dumpStatus.empty()) 585 { 586 // Dump status is not Complete, no need to enumerate 587 continue; 588 } 589 590 thisEntry["@odata.type"] = "#LogEntry.v1_11_0.LogEntry"; 591 thisEntry["@odata.id"] = entriesPath + entryID; 592 thisEntry["Id"] = entryID; 593 thisEntry["EntryType"] = "Event"; 594 thisEntry["Name"] = dumpType + " Dump Entry"; 595 thisEntry["Created"] = 596 redfish::time_utils::getDateTimeUintUs(timestampUs); 597 598 if (!originatorId.empty()) 599 { 600 thisEntry["Originator"] = originatorId; 601 thisEntry["OriginatorType"] = originatorType; 602 } 603 604 if (dumpType == "BMC") 605 { 606 thisEntry["DiagnosticDataType"] = "Manager"; 607 thisEntry["AdditionalDataURI"] = entriesPath + entryID + 608 "/attachment"; 609 thisEntry["AdditionalDataSizeBytes"] = size; 610 } 611 else if (dumpType == "System") 612 { 613 thisEntry["DiagnosticDataType"] = "OEM"; 614 thisEntry["OEMDiagnosticDataType"] = "System"; 615 thisEntry["AdditionalDataURI"] = entriesPath + entryID + 616 "/attachment"; 617 thisEntry["AdditionalDataSizeBytes"] = size; 618 } 619 entriesArray.emplace_back(std::move(thisEntry)); 620 } 621 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); 622 asyncResp->res.jsonValue["Members"] = std::move(entriesArray); 623 }); 624 } 625 626 inline void 627 getDumpEntryById(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 628 const std::string& entryID, const std::string& dumpType) 629 { 630 std::string entriesPath = getDumpEntriesPath(dumpType); 631 if (entriesPath.empty()) 632 { 633 messages::internalError(asyncResp->res); 634 return; 635 } 636 637 sdbusplus::message::object_path path("/xyz/openbmc_project/dump"); 638 dbus::utility::getManagedObjects( 639 "xyz.openbmc_project.Dump.Manager", path, 640 [asyncResp, entryID, dumpType, 641 entriesPath](const boost::system::error_code& ec, 642 const dbus::utility::ManagedObjectType& resp) { 643 if (ec) 644 { 645 BMCWEB_LOG_ERROR("DumpEntry resp_handler got error {}", ec); 646 messages::internalError(asyncResp->res); 647 return; 648 } 649 650 bool foundDumpEntry = false; 651 std::string dumpEntryPath = getDumpPath(dumpType) + "/entry/"; 652 653 for (const auto& objectPath : resp) 654 { 655 if (objectPath.first.str != dumpEntryPath + entryID) 656 { 657 continue; 658 } 659 660 foundDumpEntry = true; 661 uint64_t timestampUs = 0; 662 uint64_t size = 0; 663 std::string dumpStatus; 664 std::string originatorId; 665 log_entry::OriginatorTypes originatorType = 666 log_entry::OriginatorTypes::Internal; 667 668 parseDumpEntryFromDbusObject(objectPath, dumpStatus, size, 669 timestampUs, originatorId, 670 originatorType, asyncResp); 671 672 if (dumpStatus != 673 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" && 674 !dumpStatus.empty()) 675 { 676 // Dump status is not Complete 677 // return not found until status is changed to Completed 678 messages::resourceNotFound(asyncResp->res, dumpType + " dump", 679 entryID); 680 return; 681 } 682 683 asyncResp->res.jsonValue["@odata.type"] = 684 "#LogEntry.v1_11_0.LogEntry"; 685 asyncResp->res.jsonValue["@odata.id"] = entriesPath + entryID; 686 asyncResp->res.jsonValue["Id"] = entryID; 687 asyncResp->res.jsonValue["EntryType"] = "Event"; 688 asyncResp->res.jsonValue["Name"] = dumpType + " Dump Entry"; 689 asyncResp->res.jsonValue["Created"] = 690 redfish::time_utils::getDateTimeUintUs(timestampUs); 691 692 if (!originatorId.empty()) 693 { 694 asyncResp->res.jsonValue["Originator"] = originatorId; 695 asyncResp->res.jsonValue["OriginatorType"] = originatorType; 696 } 697 698 if (dumpType == "BMC") 699 { 700 asyncResp->res.jsonValue["DiagnosticDataType"] = "Manager"; 701 asyncResp->res.jsonValue["AdditionalDataURI"] = 702 entriesPath + entryID + "/attachment"; 703 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size; 704 } 705 else if (dumpType == "System") 706 { 707 asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM"; 708 asyncResp->res.jsonValue["OEMDiagnosticDataType"] = "System"; 709 asyncResp->res.jsonValue["AdditionalDataURI"] = 710 entriesPath + entryID + "/attachment"; 711 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size; 712 } 713 } 714 if (!foundDumpEntry) 715 { 716 BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID); 717 messages::resourceNotFound(asyncResp->res, dumpType + " dump", 718 entryID); 719 return; 720 } 721 }); 722 } 723 724 inline void deleteDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 725 const std::string& entryID, 726 const std::string& dumpType) 727 { 728 auto respHandler = [asyncResp, 729 entryID](const boost::system::error_code& ec) { 730 BMCWEB_LOG_DEBUG("Dump Entry doDelete callback: Done"); 731 if (ec) 732 { 733 if (ec.value() == EBADR) 734 { 735 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 736 return; 737 } 738 BMCWEB_LOG_ERROR( 739 "Dump (DBus) doDelete respHandler got error {} entryID={}", ec, 740 entryID); 741 messages::internalError(asyncResp->res); 742 return; 743 } 744 }; 745 746 crow::connections::systemBus->async_method_call( 747 respHandler, "xyz.openbmc_project.Dump.Manager", 748 std::format("{}/entry/{}", getDumpPath(dumpType), entryID), 749 "xyz.openbmc_project.Object.Delete", "Delete"); 750 } 751 inline bool checkSizeLimit(int fd, crow::Response& res) 752 { 753 long long int size = lseek(fd, 0, SEEK_END); 754 if (size <= 0) 755 { 756 BMCWEB_LOG_ERROR("Failed to get size of file, lseek() returned {}", 757 size); 758 messages::internalError(res); 759 return false; 760 } 761 762 // Arbitrary max size of 20MB to accommodate BMC dumps 763 constexpr long long int maxFileSize = 20LL * 1024LL * 1024LL; 764 if (size > maxFileSize) 765 { 766 BMCWEB_LOG_ERROR("File size {} exceeds maximum allowed size of {}", 767 size, maxFileSize); 768 messages::internalError(res); 769 return false; 770 } 771 off_t rc = lseek(fd, 0, SEEK_SET); 772 if (rc < 0) 773 { 774 BMCWEB_LOG_ERROR("Failed to reset file offset to 0"); 775 messages::internalError(res); 776 return false; 777 } 778 return true; 779 } 780 inline void 781 downloadEntryCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 782 const std::string& entryID, 783 const std::string& downloadEntryType, 784 const boost::system::error_code& ec, 785 const sdbusplus::message::unix_fd& unixfd) 786 { 787 if (ec.value() == EBADR) 788 { 789 messages::resourceNotFound(asyncResp->res, "EntryAttachment", entryID); 790 return; 791 } 792 if (ec) 793 { 794 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 795 messages::internalError(asyncResp->res); 796 return; 797 } 798 799 // Make sure we know how to process the retrieved entry attachment 800 if ((downloadEntryType != "BMC") && (downloadEntryType != "System")) 801 { 802 BMCWEB_LOG_ERROR("downloadEntryCallback() invalid entry type: {}", 803 downloadEntryType); 804 messages::internalError(asyncResp->res); 805 } 806 807 int fd = -1; 808 fd = dup(unixfd); 809 if (fd < 0) 810 { 811 BMCWEB_LOG_ERROR("Failed to open file"); 812 messages::internalError(asyncResp->res); 813 return; 814 } 815 if (!checkSizeLimit(fd, asyncResp->res)) 816 { 817 close(fd); 818 return; 819 } 820 if (downloadEntryType == "System") 821 { 822 if (!asyncResp->res.openFd(fd, bmcweb::EncodingType::Base64)) 823 { 824 messages::internalError(asyncResp->res); 825 close(fd); 826 return; 827 } 828 asyncResp->res.addHeader( 829 boost::beast::http::field::content_transfer_encoding, "Base64"); 830 return; 831 } 832 if (!asyncResp->res.openFd(fd)) 833 { 834 messages::internalError(asyncResp->res); 835 close(fd); 836 return; 837 } 838 asyncResp->res.addHeader(boost::beast::http::field::content_type, 839 "application/octet-stream"); 840 } 841 842 inline void 843 downloadDumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 844 const std::string& entryID, const std::string& dumpType) 845 { 846 if (dumpType != "BMC") 847 { 848 BMCWEB_LOG_WARNING("Can't find Dump Entry {}", entryID); 849 messages::resourceNotFound(asyncResp->res, dumpType + " dump", entryID); 850 return; 851 } 852 853 std::string dumpEntryPath = std::format("{}/entry/{}", 854 getDumpPath(dumpType), entryID); 855 856 auto downloadDumpEntryHandler = 857 [asyncResp, entryID, 858 dumpType](const boost::system::error_code& ec, 859 const sdbusplus::message::unix_fd& unixfd) { 860 downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd); 861 }; 862 863 crow::connections::systemBus->async_method_call( 864 std::move(downloadDumpEntryHandler), "xyz.openbmc_project.Dump.Manager", 865 dumpEntryPath, "xyz.openbmc_project.Dump.Entry", "GetFileHandle"); 866 } 867 868 inline void 869 downloadEventLogEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 870 const std::string& systemName, 871 const std::string& entryID, 872 const std::string& dumpType) 873 { 874 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 875 { 876 // Option currently returns no systems. TBD 877 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 878 systemName); 879 return; 880 } 881 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 882 { 883 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 884 systemName); 885 return; 886 } 887 888 std::string entryPath = 889 sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") / 890 entryID; 891 892 auto downloadEventLogEntryHandler = 893 [asyncResp, entryID, 894 dumpType](const boost::system::error_code& ec, 895 const sdbusplus::message::unix_fd& unixfd) { 896 downloadEntryCallback(asyncResp, entryID, dumpType, ec, unixfd); 897 }; 898 899 crow::connections::systemBus->async_method_call( 900 std::move(downloadEventLogEntryHandler), "xyz.openbmc_project.Logging", 901 entryPath, "xyz.openbmc_project.Logging.Entry", "GetEntry"); 902 } 903 904 inline DumpCreationProgress 905 mapDbusStatusToDumpProgress(const std::string& status) 906 { 907 if (status == 908 "xyz.openbmc_project.Common.Progress.OperationStatus.Failed" || 909 status == "xyz.openbmc_project.Common.Progress.OperationStatus.Aborted") 910 { 911 return DumpCreationProgress::DUMP_CREATE_FAILED; 912 } 913 if (status == 914 "xyz.openbmc_project.Common.Progress.OperationStatus.Completed") 915 { 916 return DumpCreationProgress::DUMP_CREATE_SUCCESS; 917 } 918 return DumpCreationProgress::DUMP_CREATE_INPROGRESS; 919 } 920 921 inline DumpCreationProgress 922 getDumpCompletionStatus(const dbus::utility::DBusPropertiesMap& values) 923 { 924 for (const auto& [key, val] : values) 925 { 926 if (key == "Status") 927 { 928 const std::string* value = std::get_if<std::string>(&val); 929 if (value == nullptr) 930 { 931 BMCWEB_LOG_ERROR("Status property value is null"); 932 return DumpCreationProgress::DUMP_CREATE_FAILED; 933 } 934 return mapDbusStatusToDumpProgress(*value); 935 } 936 } 937 return DumpCreationProgress::DUMP_CREATE_INPROGRESS; 938 } 939 940 inline std::string getDumpEntryPath(const std::string& dumpPath) 941 { 942 if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry") 943 { 944 return std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/", 945 BMCWEB_REDFISH_SYSTEM_URI_NAME); 946 } 947 if (dumpPath == "/xyz/openbmc_project/dump/system/entry") 948 { 949 return std::format("/redfish/v1/Systems/{}/LogServices/Dump/Entries/", 950 BMCWEB_REDFISH_SYSTEM_URI_NAME); 951 } 952 return ""; 953 } 954 955 inline void createDumpTaskCallback( 956 task::Payload&& payload, 957 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 958 const sdbusplus::message::object_path& createdObjPath) 959 { 960 const std::string dumpPath = createdObjPath.parent_path().str; 961 const std::string dumpId = createdObjPath.filename(); 962 963 std::string dumpEntryPath = getDumpEntryPath(dumpPath); 964 965 if (dumpEntryPath.empty()) 966 { 967 BMCWEB_LOG_ERROR("Invalid dump type received"); 968 messages::internalError(asyncResp->res); 969 return; 970 } 971 972 crow::connections::systemBus->async_method_call( 973 [asyncResp, payload = std::move(payload), createdObjPath, 974 dumpEntryPath{std::move(dumpEntryPath)}, 975 dumpId](const boost::system::error_code& ec, 976 const std::string& introspectXml) { 977 if (ec) 978 { 979 BMCWEB_LOG_ERROR("Introspect call failed with error: {}", 980 ec.message()); 981 messages::internalError(asyncResp->res); 982 return; 983 } 984 985 // Check if the created dump object has implemented Progress 986 // interface to track dump completion. If yes, fetch the "Status" 987 // property of the interface, modify the task state accordingly. 988 // Else, return task completed. 989 tinyxml2::XMLDocument doc; 990 991 doc.Parse(introspectXml.data(), introspectXml.size()); 992 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); 993 if (pRoot == nullptr) 994 { 995 BMCWEB_LOG_ERROR("XML document failed to parse"); 996 messages::internalError(asyncResp->res); 997 return; 998 } 999 tinyxml2::XMLElement* interfaceNode = 1000 pRoot->FirstChildElement("interface"); 1001 1002 bool isProgressIntfPresent = false; 1003 while (interfaceNode != nullptr) 1004 { 1005 const char* thisInterfaceName = interfaceNode->Attribute("name"); 1006 if (thisInterfaceName != nullptr) 1007 { 1008 if (thisInterfaceName == 1009 std::string_view("xyz.openbmc_project.Common.Progress")) 1010 { 1011 interfaceNode = 1012 interfaceNode->NextSiblingElement("interface"); 1013 continue; 1014 } 1015 isProgressIntfPresent = true; 1016 break; 1017 } 1018 interfaceNode = interfaceNode->NextSiblingElement("interface"); 1019 } 1020 1021 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 1022 [createdObjPath, dumpEntryPath, dumpId, isProgressIntfPresent]( 1023 const boost::system::error_code& ec2, sdbusplus::message_t& msg, 1024 const std::shared_ptr<task::TaskData>& taskData) { 1025 if (ec2) 1026 { 1027 BMCWEB_LOG_ERROR("{}: Error in creating dump", 1028 createdObjPath.str); 1029 taskData->messages.emplace_back(messages::internalError()); 1030 taskData->state = "Cancelled"; 1031 return task::completed; 1032 } 1033 1034 if (isProgressIntfPresent) 1035 { 1036 dbus::utility::DBusPropertiesMap values; 1037 std::string prop; 1038 msg.read(prop, values); 1039 1040 DumpCreationProgress dumpStatus = 1041 getDumpCompletionStatus(values); 1042 if (dumpStatus == DumpCreationProgress::DUMP_CREATE_FAILED) 1043 { 1044 BMCWEB_LOG_ERROR("{}: Error in creating dump", 1045 createdObjPath.str); 1046 taskData->state = "Cancelled"; 1047 return task::completed; 1048 } 1049 1050 if (dumpStatus == DumpCreationProgress::DUMP_CREATE_INPROGRESS) 1051 { 1052 BMCWEB_LOG_DEBUG("{}: Dump creation task is in progress", 1053 createdObjPath.str); 1054 return !task::completed; 1055 } 1056 } 1057 1058 nlohmann::json retMessage = messages::success(); 1059 taskData->messages.emplace_back(retMessage); 1060 1061 boost::urls::url url = boost::urls::format( 1062 "/redfish/v1/Managers/{}/LogServices/Dump/Entries/{}", 1063 BMCWEB_REDFISH_MANAGER_URI_NAME, dumpId); 1064 1065 std::string headerLoc = "Location: "; 1066 headerLoc += url.buffer(); 1067 1068 taskData->payload->httpHeaders.emplace_back(std::move(headerLoc)); 1069 1070 BMCWEB_LOG_DEBUG("{}: Dump creation task completed", 1071 createdObjPath.str); 1072 taskData->state = "Completed"; 1073 return task::completed; 1074 }, 1075 "type='signal',interface='org.freedesktop.DBus.Properties'," 1076 "member='PropertiesChanged',path='" + 1077 createdObjPath.str + "'"); 1078 1079 // The task timer is set to max time limit within which the 1080 // requested dump will be collected. 1081 task->startTimer(std::chrono::minutes(6)); 1082 task->populateResp(asyncResp->res); 1083 task->payload.emplace(payload); 1084 }, 1085 "xyz.openbmc_project.Dump.Manager", createdObjPath, 1086 "org.freedesktop.DBus.Introspectable", "Introspect"); 1087 } 1088 1089 inline void createDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1090 const crow::Request& req, const std::string& dumpType) 1091 { 1092 std::string dumpPath = getDumpEntriesPath(dumpType); 1093 if (dumpPath.empty()) 1094 { 1095 messages::internalError(asyncResp->res); 1096 return; 1097 } 1098 1099 std::optional<std::string> diagnosticDataType; 1100 std::optional<std::string> oemDiagnosticDataType; 1101 1102 if (!redfish::json_util::readJsonAction( 1103 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType, 1104 "OEMDiagnosticDataType", oemDiagnosticDataType)) 1105 { 1106 return; 1107 } 1108 1109 if (dumpType == "System") 1110 { 1111 if (!oemDiagnosticDataType || !diagnosticDataType) 1112 { 1113 BMCWEB_LOG_ERROR( 1114 "CreateDump action parameter 'DiagnosticDataType'/'OEMDiagnosticDataType' value not found!"); 1115 messages::actionParameterMissing( 1116 asyncResp->res, "CollectDiagnosticData", 1117 "DiagnosticDataType & OEMDiagnosticDataType"); 1118 return; 1119 } 1120 if ((*oemDiagnosticDataType != "System") || 1121 (*diagnosticDataType != "OEM")) 1122 { 1123 BMCWEB_LOG_ERROR("Wrong parameter values passed"); 1124 messages::internalError(asyncResp->res); 1125 return; 1126 } 1127 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump/", 1128 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1129 } 1130 else if (dumpType == "BMC") 1131 { 1132 if (!diagnosticDataType) 1133 { 1134 BMCWEB_LOG_ERROR( 1135 "CreateDump action parameter 'DiagnosticDataType' not found!"); 1136 messages::actionParameterMissing( 1137 asyncResp->res, "CollectDiagnosticData", "DiagnosticDataType"); 1138 return; 1139 } 1140 if (*diagnosticDataType != "Manager") 1141 { 1142 BMCWEB_LOG_ERROR( 1143 "Wrong parameter value passed for 'DiagnosticDataType'"); 1144 messages::internalError(asyncResp->res); 1145 return; 1146 } 1147 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump/", 1148 BMCWEB_REDFISH_MANAGER_URI_NAME); 1149 } 1150 else 1151 { 1152 BMCWEB_LOG_ERROR("CreateDump failed. Unknown dump type"); 1153 messages::internalError(asyncResp->res); 1154 return; 1155 } 1156 1157 std::vector<std::pair<std::string, std::variant<std::string, uint64_t>>> 1158 createDumpParamVec; 1159 1160 if (req.session != nullptr) 1161 { 1162 createDumpParamVec.emplace_back( 1163 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorId", 1164 req.session->clientIp); 1165 createDumpParamVec.emplace_back( 1166 "xyz.openbmc_project.Dump.Create.CreateParameters.OriginatorType", 1167 "xyz.openbmc_project.Common.OriginatedBy.OriginatorTypes.Client"); 1168 } 1169 1170 crow::connections::systemBus->async_method_call( 1171 [asyncResp, payload(task::Payload(req)), 1172 dumpPath](const boost::system::error_code& ec, 1173 const sdbusplus::message_t& msg, 1174 const sdbusplus::message::object_path& objPath) mutable { 1175 if (ec) 1176 { 1177 BMCWEB_LOG_ERROR("CreateDump resp_handler got error {}", ec); 1178 const sd_bus_error* dbusError = msg.get_error(); 1179 if (dbusError == nullptr) 1180 { 1181 messages::internalError(asyncResp->res); 1182 return; 1183 } 1184 1185 BMCWEB_LOG_ERROR("CreateDump DBus error: {} and error msg: {}", 1186 dbusError->name, dbusError->message); 1187 if (std::string_view( 1188 "xyz.openbmc_project.Common.Error.NotAllowed") == 1189 dbusError->name) 1190 { 1191 messages::resourceInStandby(asyncResp->res); 1192 return; 1193 } 1194 if (std::string_view( 1195 "xyz.openbmc_project.Dump.Create.Error.Disabled") == 1196 dbusError->name) 1197 { 1198 messages::serviceDisabled(asyncResp->res, dumpPath); 1199 return; 1200 } 1201 if (std::string_view( 1202 "xyz.openbmc_project.Common.Error.Unavailable") == 1203 dbusError->name) 1204 { 1205 messages::resourceInUse(asyncResp->res); 1206 return; 1207 } 1208 // Other Dbus errors such as: 1209 // xyz.openbmc_project.Common.Error.InvalidArgument & 1210 // org.freedesktop.DBus.Error.InvalidArgs are all related to 1211 // the dbus call that is made here in the bmcweb 1212 // implementation and has nothing to do with the client's 1213 // input in the request. Hence, returning internal error 1214 // back to the client. 1215 messages::internalError(asyncResp->res); 1216 return; 1217 } 1218 BMCWEB_LOG_DEBUG("Dump Created. Path: {}", objPath.str); 1219 createDumpTaskCallback(std::move(payload), asyncResp, objPath); 1220 }, 1221 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType), 1222 "xyz.openbmc_project.Dump.Create", "CreateDump", createDumpParamVec); 1223 } 1224 1225 inline void clearDump(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1226 const std::string& dumpType) 1227 { 1228 crow::connections::systemBus->async_method_call( 1229 [asyncResp](const boost::system::error_code& ec) { 1230 if (ec) 1231 { 1232 BMCWEB_LOG_ERROR("clearDump resp_handler got error {}", ec); 1233 messages::internalError(asyncResp->res); 1234 return; 1235 } 1236 }, 1237 "xyz.openbmc_project.Dump.Manager", getDumpPath(dumpType), 1238 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 1239 } 1240 1241 inline void 1242 parseCrashdumpParameters(const dbus::utility::DBusPropertiesMap& params, 1243 std::string& filename, std::string& timestamp, 1244 std::string& logfile) 1245 { 1246 const std::string* filenamePtr = nullptr; 1247 const std::string* timestampPtr = nullptr; 1248 const std::string* logfilePtr = nullptr; 1249 1250 const bool success = sdbusplus::unpackPropertiesNoThrow( 1251 dbus_utils::UnpackErrorPrinter(), params, "Timestamp", timestampPtr, 1252 "Filename", filenamePtr, "Log", logfilePtr); 1253 1254 if (!success) 1255 { 1256 return; 1257 } 1258 1259 if (filenamePtr != nullptr) 1260 { 1261 filename = *filenamePtr; 1262 } 1263 1264 if (timestampPtr != nullptr) 1265 { 1266 timestamp = *timestampPtr; 1267 } 1268 1269 if (logfilePtr != nullptr) 1270 { 1271 logfile = *logfilePtr; 1272 } 1273 } 1274 1275 inline void requestRoutesSystemLogServiceCollection(App& app) 1276 { 1277 /** 1278 * Functions triggers appropriate requests on DBus 1279 */ 1280 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/") 1281 .privileges(redfish::privileges::getLogServiceCollection) 1282 .methods(boost::beast::http::verb::get)( 1283 [&app](const crow::Request& req, 1284 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1285 const std::string& systemName) { 1286 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1287 { 1288 return; 1289 } 1290 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 1291 { 1292 // Option currently returns no systems. TBD 1293 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1294 systemName); 1295 return; 1296 } 1297 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1298 { 1299 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1300 systemName); 1301 return; 1302 } 1303 1304 // Collections don't include the static data added by SubRoute 1305 // because it has a duplicate entry for members 1306 asyncResp->res.jsonValue["@odata.type"] = 1307 "#LogServiceCollection.LogServiceCollection"; 1308 asyncResp->res.jsonValue["@odata.id"] = 1309 std::format("/redfish/v1/Systems/{}/LogServices", 1310 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1311 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 1312 asyncResp->res.jsonValue["Description"] = 1313 "Collection of LogServices for this Computer System"; 1314 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; 1315 logServiceArray = nlohmann::json::array(); 1316 nlohmann::json::object_t eventLog; 1317 eventLog["@odata.id"] = 1318 std::format("/redfish/v1/Systems/{}/LogServices/EventLog", 1319 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1320 logServiceArray.emplace_back(std::move(eventLog)); 1321 if constexpr (BMCWEB_REDFISH_DUMP_LOG) 1322 { 1323 nlohmann::json::object_t dumpLog; 1324 dumpLog["@odata.id"] = 1325 std::format("/redfish/v1/Systems/{}/LogServices/Dump", 1326 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1327 logServiceArray.emplace_back(std::move(dumpLog)); 1328 } 1329 1330 if constexpr (BMCWEB_REDFISH_CPU_LOG) 1331 { 1332 nlohmann::json::object_t crashdump; 1333 crashdump["@odata.id"] = 1334 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump", 1335 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1336 logServiceArray.emplace_back(std::move(crashdump)); 1337 } 1338 1339 if constexpr (BMCWEB_REDFISH_HOST_LOGGER) 1340 { 1341 nlohmann::json::object_t hostlogger; 1342 hostlogger["@odata.id"] = 1343 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger", 1344 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1345 logServiceArray.emplace_back(std::move(hostlogger)); 1346 } 1347 asyncResp->res.jsonValue["Members@odata.count"] = 1348 logServiceArray.size(); 1349 1350 constexpr std::array<std::string_view, 1> interfaces = { 1351 "xyz.openbmc_project.State.Boot.PostCode"}; 1352 dbus::utility::getSubTreePaths( 1353 "/", 0, interfaces, 1354 [asyncResp](const boost::system::error_code& ec, 1355 const dbus::utility::MapperGetSubTreePathsResponse& 1356 subtreePath) { 1357 if (ec) 1358 { 1359 BMCWEB_LOG_ERROR("{}", ec); 1360 return; 1361 } 1362 1363 for (const auto& pathStr : subtreePath) 1364 { 1365 if (pathStr.find("PostCode") != std::string::npos) 1366 { 1367 nlohmann::json& logServiceArrayLocal = 1368 asyncResp->res.jsonValue["Members"]; 1369 nlohmann::json::object_t member; 1370 member["@odata.id"] = std::format( 1371 "/redfish/v1/Systems/{}/LogServices/PostCodes", 1372 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1373 1374 logServiceArrayLocal.emplace_back(std::move(member)); 1375 1376 asyncResp->res.jsonValue["Members@odata.count"] = 1377 logServiceArrayLocal.size(); 1378 return; 1379 } 1380 } 1381 }); 1382 }); 1383 } 1384 1385 inline void requestRoutesEventLogService(App& app) 1386 { 1387 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/") 1388 .privileges(redfish::privileges::getLogService) 1389 .methods(boost::beast::http::verb::get)( 1390 [&app](const crow::Request& req, 1391 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1392 const std::string& systemName) { 1393 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1394 { 1395 return; 1396 } 1397 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1398 { 1399 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1400 systemName); 1401 return; 1402 } 1403 asyncResp->res.jsonValue["@odata.id"] = 1404 std::format("/redfish/v1/Systems/{}/LogServices/EventLog", 1405 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1406 asyncResp->res.jsonValue["@odata.type"] = 1407 "#LogService.v1_2_0.LogService"; 1408 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 1409 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 1410 asyncResp->res.jsonValue["Id"] = "EventLog"; 1411 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1412 1413 std::pair<std::string, std::string> redfishDateTimeOffset = 1414 redfish::time_utils::getDateTimeOffsetNow(); 1415 1416 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 1417 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 1418 redfishDateTimeOffset.second; 1419 1420 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 1421 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries", 1422 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1423 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] 1424 1425 = std::format( 1426 "/redfish/v1/Systems/{}/LogServices/EventLog/Actions/LogService.ClearLog", 1427 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1428 }); 1429 } 1430 1431 inline void requestRoutesJournalEventLogClear(App& app) 1432 { 1433 BMCWEB_ROUTE( 1434 app, 1435 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/") 1436 .privileges({{"ConfigureComponents"}}) 1437 .methods(boost::beast::http::verb::post)( 1438 [&app](const crow::Request& req, 1439 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1440 const std::string& systemName) { 1441 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1442 { 1443 return; 1444 } 1445 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1446 { 1447 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1448 systemName); 1449 return; 1450 } 1451 // Clear the EventLog by deleting the log files 1452 std::vector<std::filesystem::path> redfishLogFiles; 1453 if (getRedfishLogFiles(redfishLogFiles)) 1454 { 1455 for (const std::filesystem::path& file : redfishLogFiles) 1456 { 1457 std::error_code ec; 1458 std::filesystem::remove(file, ec); 1459 } 1460 } 1461 1462 // Reload rsyslog so it knows to start new log files 1463 crow::connections::systemBus->async_method_call( 1464 [asyncResp](const boost::system::error_code& ec) { 1465 if (ec) 1466 { 1467 BMCWEB_LOG_ERROR("Failed to reload rsyslog: {}", ec); 1468 messages::internalError(asyncResp->res); 1469 return; 1470 } 1471 1472 messages::success(asyncResp->res); 1473 }, 1474 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1475 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 1476 "replace"); 1477 }); 1478 } 1479 1480 enum class LogParseError 1481 { 1482 success, 1483 parseFailed, 1484 messageIdNotInRegistry, 1485 }; 1486 1487 static LogParseError 1488 fillEventLogEntryJson(const std::string& logEntryID, 1489 const std::string& logEntry, 1490 nlohmann::json::object_t& logEntryJson) 1491 { 1492 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 1493 // First get the Timestamp 1494 size_t space = logEntry.find_first_of(' '); 1495 if (space == std::string::npos) 1496 { 1497 return LogParseError::parseFailed; 1498 } 1499 std::string timestamp = logEntry.substr(0, space); 1500 // Then get the log contents 1501 size_t entryStart = logEntry.find_first_not_of(' ', space); 1502 if (entryStart == std::string::npos) 1503 { 1504 return LogParseError::parseFailed; 1505 } 1506 std::string_view entry(logEntry); 1507 entry.remove_prefix(entryStart); 1508 // Use split to separate the entry into its fields 1509 std::vector<std::string> logEntryFields; 1510 bmcweb::split(logEntryFields, entry, ','); 1511 // We need at least a MessageId to be valid 1512 auto logEntryIter = logEntryFields.begin(); 1513 if (logEntryIter == logEntryFields.end()) 1514 { 1515 return LogParseError::parseFailed; 1516 } 1517 std::string& messageID = *logEntryIter; 1518 // Get the Message from the MessageRegistry 1519 const registries::Message* message = registries::getMessage(messageID); 1520 1521 logEntryIter++; 1522 if (message == nullptr) 1523 { 1524 BMCWEB_LOG_WARNING("Log entry not found in registry: {}", logEntry); 1525 return LogParseError::messageIdNotInRegistry; 1526 } 1527 1528 std::vector<std::string_view> messageArgs(logEntryIter, 1529 logEntryFields.end()); 1530 messageArgs.resize(message->numberOfArgs); 1531 1532 std::string msg = redfish::registries::fillMessageArgs(messageArgs, 1533 message->message); 1534 if (msg.empty()) 1535 { 1536 return LogParseError::parseFailed; 1537 } 1538 1539 // Get the Created time from the timestamp. The log timestamp is in RFC3339 1540 // format which matches the Redfish format except for the fractional seconds 1541 // between the '.' and the '+', so just remove them. 1542 std::size_t dot = timestamp.find_first_of('.'); 1543 std::size_t plus = timestamp.find_first_of('+'); 1544 if (dot != std::string::npos && plus != std::string::npos) 1545 { 1546 timestamp.erase(dot, plus - dot); 1547 } 1548 1549 // Fill in the log entry with the gathered data 1550 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 1551 logEntryJson["@odata.id"] = boost::urls::format( 1552 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}", 1553 BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID); 1554 logEntryJson["Name"] = "System Event Log Entry"; 1555 logEntryJson["Id"] = logEntryID; 1556 logEntryJson["Message"] = std::move(msg); 1557 logEntryJson["MessageId"] = std::move(messageID); 1558 logEntryJson["MessageArgs"] = messageArgs; 1559 logEntryJson["EntryType"] = "Event"; 1560 logEntryJson["Severity"] = message->messageSeverity; 1561 logEntryJson["Created"] = std::move(timestamp); 1562 return LogParseError::success; 1563 } 1564 1565 inline void requestRoutesJournalEventLogEntryCollection(App& app) 1566 { 1567 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/") 1568 .privileges(redfish::privileges::getLogEntryCollection) 1569 .methods(boost::beast::http::verb::get)( 1570 [&app](const crow::Request& req, 1571 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1572 const std::string& systemName) { 1573 query_param::QueryCapabilities capabilities = { 1574 .canDelegateTop = true, 1575 .canDelegateSkip = true, 1576 }; 1577 query_param::Query delegatedQuery; 1578 if (!redfish::setUpRedfishRouteWithDelegation( 1579 app, req, asyncResp, delegatedQuery, capabilities)) 1580 { 1581 return; 1582 } 1583 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 1584 { 1585 // Option currently returns no systems. TBD 1586 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1587 systemName); 1588 return; 1589 } 1590 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1591 { 1592 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1593 systemName); 1594 return; 1595 } 1596 1597 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 1598 size_t skip = delegatedQuery.skip.value_or(0); 1599 1600 // Collections don't include the static data added by SubRoute 1601 // because it has a duplicate entry for members 1602 asyncResp->res.jsonValue["@odata.type"] = 1603 "#LogEntryCollection.LogEntryCollection"; 1604 asyncResp->res.jsonValue["@odata.id"] = 1605 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries", 1606 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1607 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1608 asyncResp->res.jsonValue["Description"] = 1609 "Collection of System Event Log Entries"; 1610 1611 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 1612 logEntryArray = nlohmann::json::array(); 1613 // Go through the log files and create a unique ID for each 1614 // entry 1615 std::vector<std::filesystem::path> redfishLogFiles; 1616 getRedfishLogFiles(redfishLogFiles); 1617 uint64_t entryCount = 0; 1618 std::string logEntry; 1619 1620 // Oldest logs are in the last file, so start there and loop 1621 // backwards 1622 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 1623 it++) 1624 { 1625 std::ifstream logStream(*it); 1626 if (!logStream.is_open()) 1627 { 1628 continue; 1629 } 1630 1631 // Reset the unique ID on the first entry 1632 bool firstEntry = true; 1633 while (std::getline(logStream, logEntry)) 1634 { 1635 std::string idStr; 1636 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1637 { 1638 continue; 1639 } 1640 firstEntry = false; 1641 1642 nlohmann::json::object_t bmcLogEntry; 1643 LogParseError status = fillEventLogEntryJson(idStr, logEntry, 1644 bmcLogEntry); 1645 if (status == LogParseError::messageIdNotInRegistry) 1646 { 1647 continue; 1648 } 1649 if (status != LogParseError::success) 1650 { 1651 messages::internalError(asyncResp->res); 1652 return; 1653 } 1654 1655 entryCount++; 1656 // Handle paging using skip (number of entries to skip from the 1657 // start) and top (number of entries to display) 1658 if (entryCount <= skip || entryCount > skip + top) 1659 { 1660 continue; 1661 } 1662 1663 logEntryArray.emplace_back(std::move(bmcLogEntry)); 1664 } 1665 } 1666 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1667 if (skip + top < entryCount) 1668 { 1669 asyncResp->res 1670 .jsonValue["Members@odata.nextLink"] = boost::urls::format( 1671 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries?$skip={}", 1672 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(skip + top)); 1673 } 1674 }); 1675 } 1676 1677 inline void requestRoutesJournalEventLogEntry(App& app) 1678 { 1679 BMCWEB_ROUTE( 1680 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 1681 .privileges(redfish::privileges::getLogEntry) 1682 .methods(boost::beast::http::verb::get)( 1683 [&app](const crow::Request& req, 1684 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1685 const std::string& systemName, const std::string& param) { 1686 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1687 { 1688 return; 1689 } 1690 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 1691 { 1692 // Option currently returns no systems. TBD 1693 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1694 systemName); 1695 return; 1696 } 1697 1698 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1699 { 1700 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1701 systemName); 1702 return; 1703 } 1704 1705 const std::string& targetID = param; 1706 1707 // Go through the log files and check the unique ID for each 1708 // entry to find the target entry 1709 std::vector<std::filesystem::path> redfishLogFiles; 1710 getRedfishLogFiles(redfishLogFiles); 1711 std::string logEntry; 1712 1713 // Oldest logs are in the last file, so start there and loop 1714 // backwards 1715 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 1716 it++) 1717 { 1718 std::ifstream logStream(*it); 1719 if (!logStream.is_open()) 1720 { 1721 continue; 1722 } 1723 1724 // Reset the unique ID on the first entry 1725 bool firstEntry = true; 1726 while (std::getline(logStream, logEntry)) 1727 { 1728 std::string idStr; 1729 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1730 { 1731 continue; 1732 } 1733 firstEntry = false; 1734 1735 if (idStr == targetID) 1736 { 1737 nlohmann::json::object_t bmcLogEntry; 1738 LogParseError status = 1739 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry); 1740 if (status != LogParseError::success) 1741 { 1742 messages::internalError(asyncResp->res); 1743 return; 1744 } 1745 asyncResp->res.jsonValue.update(bmcLogEntry); 1746 return; 1747 } 1748 } 1749 } 1750 // Requested ID was not found 1751 messages::resourceNotFound(asyncResp->res, "LogEntry", targetID); 1752 }); 1753 } 1754 1755 inline void requestRoutesDBusEventLogEntryCollection(App& app) 1756 { 1757 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/") 1758 .privileges(redfish::privileges::getLogEntryCollection) 1759 .methods(boost::beast::http::verb::get)( 1760 [&app](const crow::Request& req, 1761 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1762 const std::string& systemName) { 1763 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1764 { 1765 return; 1766 } 1767 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 1768 { 1769 // Option currently returns no systems. TBD 1770 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1771 systemName); 1772 return; 1773 } 1774 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1775 { 1776 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1777 systemName); 1778 return; 1779 } 1780 1781 // Collections don't include the static data added by SubRoute 1782 // because it has a duplicate entry for members 1783 asyncResp->res.jsonValue["@odata.type"] = 1784 "#LogEntryCollection.LogEntryCollection"; 1785 asyncResp->res.jsonValue["@odata.id"] = 1786 std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries", 1787 BMCWEB_REDFISH_SYSTEM_URI_NAME); 1788 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1789 asyncResp->res.jsonValue["Description"] = 1790 "Collection of System Event Log Entries"; 1791 1792 // DBus implementation of EventLog/Entries 1793 // Make call to Logging Service to find all log entry objects 1794 sdbusplus::message::object_path path("/xyz/openbmc_project/logging"); 1795 dbus::utility::getManagedObjects( 1796 "xyz.openbmc_project.Logging", path, 1797 [asyncResp](const boost::system::error_code& ec, 1798 const dbus::utility::ManagedObjectType& resp) { 1799 if (ec) 1800 { 1801 // TODO Handle for specific error code 1802 BMCWEB_LOG_ERROR( 1803 "getLogEntriesIfaceData resp_handler got error {}", ec); 1804 messages::internalError(asyncResp->res); 1805 return; 1806 } 1807 nlohmann::json::array_t entriesArray; 1808 for (const auto& objectPath : resp) 1809 { 1810 const uint32_t* id = nullptr; 1811 const uint64_t* timestamp = nullptr; 1812 const uint64_t* updateTimestamp = nullptr; 1813 const std::string* severity = nullptr; 1814 const std::string* message = nullptr; 1815 const std::string* filePath = nullptr; 1816 const std::string* resolution = nullptr; 1817 bool resolved = false; 1818 const std::string* notify = nullptr; 1819 1820 for (const auto& interfaceMap : objectPath.second) 1821 { 1822 if (interfaceMap.first == 1823 "xyz.openbmc_project.Logging.Entry") 1824 { 1825 for (const auto& propertyMap : interfaceMap.second) 1826 { 1827 if (propertyMap.first == "Id") 1828 { 1829 id = std::get_if<uint32_t>(&propertyMap.second); 1830 } 1831 else if (propertyMap.first == "Timestamp") 1832 { 1833 timestamp = 1834 std::get_if<uint64_t>(&propertyMap.second); 1835 } 1836 else if (propertyMap.first == "UpdateTimestamp") 1837 { 1838 updateTimestamp = 1839 std::get_if<uint64_t>(&propertyMap.second); 1840 } 1841 else if (propertyMap.first == "Severity") 1842 { 1843 severity = std::get_if<std::string>( 1844 &propertyMap.second); 1845 } 1846 else if (propertyMap.first == "Resolution") 1847 { 1848 resolution = std::get_if<std::string>( 1849 &propertyMap.second); 1850 } 1851 else if (propertyMap.first == "Message") 1852 { 1853 message = std::get_if<std::string>( 1854 &propertyMap.second); 1855 } 1856 else if (propertyMap.first == "Resolved") 1857 { 1858 const bool* resolveptr = 1859 std::get_if<bool>(&propertyMap.second); 1860 if (resolveptr == nullptr) 1861 { 1862 messages::internalError(asyncResp->res); 1863 return; 1864 } 1865 resolved = *resolveptr; 1866 } 1867 else if (propertyMap.first == 1868 "ServiceProviderNotify") 1869 { 1870 notify = std::get_if<std::string>( 1871 &propertyMap.second); 1872 if (notify == nullptr) 1873 { 1874 messages::internalError(asyncResp->res); 1875 return; 1876 } 1877 } 1878 } 1879 if (id == nullptr || message == nullptr || 1880 severity == nullptr) 1881 { 1882 messages::internalError(asyncResp->res); 1883 return; 1884 } 1885 } 1886 else if (interfaceMap.first == 1887 "xyz.openbmc_project.Common.FilePath") 1888 { 1889 for (const auto& propertyMap : interfaceMap.second) 1890 { 1891 if (propertyMap.first == "Path") 1892 { 1893 filePath = std::get_if<std::string>( 1894 &propertyMap.second); 1895 } 1896 } 1897 } 1898 } 1899 // Object path without the 1900 // xyz.openbmc_project.Logging.Entry interface, ignore 1901 // and continue. 1902 if (id == nullptr || message == nullptr || 1903 severity == nullptr || timestamp == nullptr || 1904 updateTimestamp == nullptr) 1905 { 1906 continue; 1907 } 1908 nlohmann::json& thisEntry = entriesArray.emplace_back(); 1909 thisEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 1910 thisEntry["@odata.id"] = boost::urls::format( 1911 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}", 1912 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(*id)); 1913 thisEntry["Name"] = "System Event Log Entry"; 1914 thisEntry["Id"] = std::to_string(*id); 1915 thisEntry["Message"] = *message; 1916 thisEntry["Resolved"] = resolved; 1917 if ((resolution != nullptr) && (!(*resolution).empty())) 1918 { 1919 thisEntry["Resolution"] = *resolution; 1920 } 1921 std::optional<bool> notifyAction = 1922 getProviderNotifyAction(*notify); 1923 if (notifyAction) 1924 { 1925 thisEntry["ServiceProviderNotified"] = *notifyAction; 1926 } 1927 thisEntry["EntryType"] = "Event"; 1928 thisEntry["Severity"] = 1929 translateSeverityDbusToRedfish(*severity); 1930 thisEntry["Created"] = 1931 redfish::time_utils::getDateTimeUintMs(*timestamp); 1932 thisEntry["Modified"] = 1933 redfish::time_utils::getDateTimeUintMs(*updateTimestamp); 1934 if (filePath != nullptr) 1935 { 1936 thisEntry["AdditionalDataURI"] = 1937 std::format( 1938 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/", 1939 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 1940 std::to_string(*id) + "/attachment"; 1941 } 1942 } 1943 std::ranges::sort(entriesArray, [](const nlohmann::json& left, 1944 const nlohmann::json& right) { 1945 return (left["Id"] <= right["Id"]); 1946 }); 1947 asyncResp->res.jsonValue["Members@odata.count"] = 1948 entriesArray.size(); 1949 asyncResp->res.jsonValue["Members"] = std::move(entriesArray); 1950 }); 1951 }); 1952 } 1953 1954 inline void requestRoutesDBusEventLogEntry(App& app) 1955 { 1956 BMCWEB_ROUTE( 1957 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 1958 .privileges(redfish::privileges::getLogEntry) 1959 .methods(boost::beast::http::verb::get)( 1960 [&app](const crow::Request& req, 1961 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1962 const std::string& systemName, const std::string& param) { 1963 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1964 { 1965 return; 1966 } 1967 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 1968 { 1969 // Option currently returns no systems. TBD 1970 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1971 systemName); 1972 return; 1973 } 1974 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 1975 { 1976 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1977 systemName); 1978 return; 1979 } 1980 1981 std::string entryID = param; 1982 dbus::utility::escapePathForDbus(entryID); 1983 1984 // DBus implementation of EventLog/Entries 1985 // Make call to Logging Service to find all log entry objects 1986 sdbusplus::asio::getAllProperties( 1987 *crow::connections::systemBus, "xyz.openbmc_project.Logging", 1988 "/xyz/openbmc_project/logging/entry/" + entryID, "", 1989 [asyncResp, entryID](const boost::system::error_code& ec, 1990 const dbus::utility::DBusPropertiesMap& resp) { 1991 if (ec.value() == EBADR) 1992 { 1993 messages::resourceNotFound(asyncResp->res, "EventLogEntry", 1994 entryID); 1995 return; 1996 } 1997 if (ec) 1998 { 1999 BMCWEB_LOG_ERROR( 2000 "EventLogEntry (DBus) resp_handler got error {}", ec); 2001 messages::internalError(asyncResp->res); 2002 return; 2003 } 2004 const uint32_t* id = nullptr; 2005 const uint64_t* timestamp = nullptr; 2006 const uint64_t* updateTimestamp = nullptr; 2007 const std::string* severity = nullptr; 2008 const std::string* message = nullptr; 2009 const std::string* filePath = nullptr; 2010 const std::string* resolution = nullptr; 2011 bool resolved = false; 2012 const std::string* notify = nullptr; 2013 2014 const bool success = sdbusplus::unpackPropertiesNoThrow( 2015 dbus_utils::UnpackErrorPrinter(), resp, "Id", id, "Timestamp", 2016 timestamp, "UpdateTimestamp", updateTimestamp, "Severity", 2017 severity, "Message", message, "Resolved", resolved, 2018 "Resolution", resolution, "Path", filePath, 2019 "ServiceProviderNotify", notify); 2020 2021 if (!success) 2022 { 2023 messages::internalError(asyncResp->res); 2024 return; 2025 } 2026 2027 if (id == nullptr || message == nullptr || severity == nullptr || 2028 timestamp == nullptr || updateTimestamp == nullptr || 2029 notify == nullptr) 2030 { 2031 messages::internalError(asyncResp->res); 2032 return; 2033 } 2034 2035 asyncResp->res.jsonValue["@odata.type"] = 2036 "#LogEntry.v1_9_0.LogEntry"; 2037 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2038 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}", 2039 BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(*id)); 2040 asyncResp->res.jsonValue["Name"] = "System Event Log Entry"; 2041 asyncResp->res.jsonValue["Id"] = std::to_string(*id); 2042 asyncResp->res.jsonValue["Message"] = *message; 2043 asyncResp->res.jsonValue["Resolved"] = resolved; 2044 std::optional<bool> notifyAction = getProviderNotifyAction(*notify); 2045 if (notifyAction) 2046 { 2047 asyncResp->res.jsonValue["ServiceProviderNotified"] = 2048 *notifyAction; 2049 } 2050 if ((resolution != nullptr) && (!(*resolution).empty())) 2051 { 2052 asyncResp->res.jsonValue["Resolution"] = *resolution; 2053 } 2054 asyncResp->res.jsonValue["EntryType"] = "Event"; 2055 asyncResp->res.jsonValue["Severity"] = 2056 translateSeverityDbusToRedfish(*severity); 2057 asyncResp->res.jsonValue["Created"] = 2058 redfish::time_utils::getDateTimeUintMs(*timestamp); 2059 asyncResp->res.jsonValue["Modified"] = 2060 redfish::time_utils::getDateTimeUintMs(*updateTimestamp); 2061 if (filePath != nullptr) 2062 { 2063 asyncResp->res.jsonValue["AdditionalDataURI"] = 2064 std::format( 2065 "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/", 2066 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 2067 std::to_string(*id) + "/attachment"; 2068 } 2069 }); 2070 }); 2071 2072 BMCWEB_ROUTE( 2073 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 2074 .privileges(redfish::privileges::patchLogEntry) 2075 .methods(boost::beast::http::verb::patch)( 2076 [&app](const crow::Request& req, 2077 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2078 const std::string& systemName, const std::string& entryId) { 2079 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2080 { 2081 return; 2082 } 2083 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 2084 { 2085 // Option currently returns no systems. TBD 2086 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2087 systemName); 2088 return; 2089 } 2090 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2091 { 2092 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2093 systemName); 2094 return; 2095 } 2096 std::optional<bool> resolved; 2097 2098 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", 2099 resolved)) 2100 { 2101 return; 2102 } 2103 BMCWEB_LOG_DEBUG("Set Resolved"); 2104 2105 setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging", 2106 "/xyz/openbmc_project/logging/entry/" + entryId, 2107 "xyz.openbmc_project.Logging.Entry", "Resolved", 2108 *resolved); 2109 }); 2110 2111 BMCWEB_ROUTE( 2112 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 2113 .privileges(redfish::privileges::deleteLogEntry) 2114 2115 .methods(boost::beast::http::verb::delete_)( 2116 [&app](const crow::Request& req, 2117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2118 const std::string& systemName, const std::string& param) { 2119 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2120 { 2121 return; 2122 } 2123 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 2124 { 2125 // Option currently returns no systems. TBD 2126 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2127 systemName); 2128 return; 2129 } 2130 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2131 { 2132 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2133 systemName); 2134 return; 2135 } 2136 BMCWEB_LOG_DEBUG("Do delete single event entries."); 2137 2138 std::string entryID = param; 2139 2140 dbus::utility::escapePathForDbus(entryID); 2141 2142 // Process response from Logging service. 2143 auto respHandler = [asyncResp, 2144 entryID](const boost::system::error_code& ec) { 2145 BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done"); 2146 if (ec) 2147 { 2148 if (ec.value() == EBADR) 2149 { 2150 messages::resourceNotFound(asyncResp->res, "LogEntry", 2151 entryID); 2152 return; 2153 } 2154 // TODO Handle for specific error code 2155 BMCWEB_LOG_ERROR( 2156 "EventLogEntry (DBus) doDelete respHandler got error {}", 2157 ec); 2158 asyncResp->res.result( 2159 boost::beast::http::status::internal_server_error); 2160 return; 2161 } 2162 2163 asyncResp->res.result(boost::beast::http::status::ok); 2164 }; 2165 2166 // Make call to Logging service to request Delete Log 2167 crow::connections::systemBus->async_method_call( 2168 respHandler, "xyz.openbmc_project.Logging", 2169 "/xyz/openbmc_project/logging/entry/" + entryID, 2170 "xyz.openbmc_project.Object.Delete", "Delete"); 2171 }); 2172 } 2173 2174 constexpr const char* hostLoggerFolderPath = "/var/log/console"; 2175 2176 inline bool 2177 getHostLoggerFiles(const std::string& hostLoggerFilePath, 2178 std::vector<std::filesystem::path>& hostLoggerFiles) 2179 { 2180 std::error_code ec; 2181 std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec); 2182 if (ec) 2183 { 2184 BMCWEB_LOG_WARNING("{}", ec.message()); 2185 return false; 2186 } 2187 for (const std::filesystem::directory_entry& it : logPath) 2188 { 2189 std::string filename = it.path().filename(); 2190 // Prefix of each log files is "log". Find the file and save the 2191 // path 2192 if (filename.starts_with("log")) 2193 { 2194 hostLoggerFiles.emplace_back(it.path()); 2195 } 2196 } 2197 // As the log files rotate, they are appended with a ".#" that is higher for 2198 // the older logs. Since we start from oldest logs, sort the name in 2199 // descending order. 2200 std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(), 2201 AlphanumLess<std::string>()); 2202 2203 return true; 2204 } 2205 2206 inline bool getHostLoggerEntries( 2207 const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip, 2208 uint64_t top, std::vector<std::string>& logEntries, size_t& logCount) 2209 { 2210 GzFileReader logFile; 2211 2212 // Go though all log files and expose host logs. 2213 for (const std::filesystem::path& it : hostLoggerFiles) 2214 { 2215 if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount)) 2216 { 2217 BMCWEB_LOG_ERROR("fail to expose host logs"); 2218 return false; 2219 } 2220 } 2221 // Get lastMessage from constructor by getter 2222 std::string lastMessage = logFile.getLastMessage(); 2223 if (!lastMessage.empty()) 2224 { 2225 logCount++; 2226 if (logCount > skip && logCount <= (skip + top)) 2227 { 2228 logEntries.push_back(lastMessage); 2229 } 2230 } 2231 return true; 2232 } 2233 2234 inline void fillHostLoggerEntryJson(std::string_view logEntryID, 2235 std::string_view msg, 2236 nlohmann::json::object_t& logEntryJson) 2237 { 2238 // Fill in the log entry with the gathered data. 2239 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 2240 logEntryJson["@odata.id"] = boost::urls::format( 2241 "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries/{}", 2242 BMCWEB_REDFISH_SYSTEM_URI_NAME, logEntryID); 2243 logEntryJson["Name"] = "Host Logger Entry"; 2244 logEntryJson["Id"] = logEntryID; 2245 logEntryJson["Message"] = msg; 2246 logEntryJson["EntryType"] = "Oem"; 2247 logEntryJson["Severity"] = "OK"; 2248 logEntryJson["OemRecordFormat"] = "Host Logger Entry"; 2249 } 2250 2251 inline void requestRoutesSystemHostLogger(App& app) 2252 { 2253 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/") 2254 .privileges(redfish::privileges::getLogService) 2255 .methods(boost::beast::http::verb::get)( 2256 [&app](const crow::Request& req, 2257 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2258 const std::string& systemName) { 2259 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2260 { 2261 return; 2262 } 2263 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 2264 { 2265 // Option currently returns no systems. TBD 2266 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2267 systemName); 2268 return; 2269 } 2270 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2271 { 2272 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2273 systemName); 2274 return; 2275 } 2276 asyncResp->res.jsonValue["@odata.id"] = 2277 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger", 2278 BMCWEB_REDFISH_SYSTEM_URI_NAME); 2279 asyncResp->res.jsonValue["@odata.type"] = 2280 "#LogService.v1_2_0.LogService"; 2281 asyncResp->res.jsonValue["Name"] = "Host Logger Service"; 2282 asyncResp->res.jsonValue["Description"] = "Host Logger Service"; 2283 asyncResp->res.jsonValue["Id"] = "HostLogger"; 2284 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 2285 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries", 2286 BMCWEB_REDFISH_SYSTEM_URI_NAME); 2287 }); 2288 } 2289 2290 inline void requestRoutesSystemHostLoggerCollection(App& app) 2291 { 2292 BMCWEB_ROUTE(app, 2293 "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/") 2294 .privileges(redfish::privileges::getLogEntry) 2295 .methods(boost::beast::http::verb::get)( 2296 [&app](const crow::Request& req, 2297 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2298 const std::string& systemName) { 2299 query_param::QueryCapabilities capabilities = { 2300 .canDelegateTop = true, 2301 .canDelegateSkip = true, 2302 }; 2303 query_param::Query delegatedQuery; 2304 if (!redfish::setUpRedfishRouteWithDelegation( 2305 app, req, asyncResp, delegatedQuery, capabilities)) 2306 { 2307 return; 2308 } 2309 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 2310 { 2311 // Option currently returns no systems. TBD 2312 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2313 systemName); 2314 return; 2315 } 2316 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2317 { 2318 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2319 systemName); 2320 return; 2321 } 2322 asyncResp->res.jsonValue["@odata.id"] = 2323 std::format("/redfish/v1/Systems/{}/LogServices/HostLogger/Entries", 2324 BMCWEB_REDFISH_SYSTEM_URI_NAME); 2325 asyncResp->res.jsonValue["@odata.type"] = 2326 "#LogEntryCollection.LogEntryCollection"; 2327 asyncResp->res.jsonValue["Name"] = "HostLogger Entries"; 2328 asyncResp->res.jsonValue["Description"] = 2329 "Collection of HostLogger Entries"; 2330 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2331 logEntryArray = nlohmann::json::array(); 2332 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2333 2334 std::vector<std::filesystem::path> hostLoggerFiles; 2335 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2336 { 2337 BMCWEB_LOG_DEBUG("Failed to get host log file path"); 2338 return; 2339 } 2340 // If we weren't provided top and skip limits, use the defaults. 2341 size_t skip = delegatedQuery.skip.value_or(0); 2342 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 2343 size_t logCount = 0; 2344 // This vector only store the entries we want to expose that 2345 // control by skip and top. 2346 std::vector<std::string> logEntries; 2347 if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, 2348 logCount)) 2349 { 2350 messages::internalError(asyncResp->res); 2351 return; 2352 } 2353 // If vector is empty, that means skip value larger than total 2354 // log count 2355 if (logEntries.empty()) 2356 { 2357 asyncResp->res.jsonValue["Members@odata.count"] = logCount; 2358 return; 2359 } 2360 if (!logEntries.empty()) 2361 { 2362 for (size_t i = 0; i < logEntries.size(); i++) 2363 { 2364 nlohmann::json::object_t hostLogEntry; 2365 fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i], 2366 hostLogEntry); 2367 logEntryArray.emplace_back(std::move(hostLogEntry)); 2368 } 2369 2370 asyncResp->res.jsonValue["Members@odata.count"] = logCount; 2371 if (skip + top < logCount) 2372 { 2373 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2374 std::format( 2375 "/redfish/v1/Systems/{}/LogServices/HostLogger/Entries?$skip=", 2376 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 2377 std::to_string(skip + top); 2378 } 2379 } 2380 }); 2381 } 2382 2383 inline void requestRoutesSystemHostLoggerLogEntry(App& app) 2384 { 2385 BMCWEB_ROUTE( 2386 app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/") 2387 .privileges(redfish::privileges::getLogEntry) 2388 .methods(boost::beast::http::verb::get)( 2389 [&app](const crow::Request& req, 2390 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2391 const std::string& systemName, const std::string& param) { 2392 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2393 { 2394 return; 2395 } 2396 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 2397 { 2398 // Option currently returns no systems. TBD 2399 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2400 systemName); 2401 return; 2402 } 2403 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2404 { 2405 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2406 systemName); 2407 return; 2408 } 2409 std::string_view targetID = param; 2410 2411 uint64_t idInt = 0; 2412 2413 auto [ptr, ec] = std::from_chars(targetID.begin(), targetID.end(), 2414 idInt); 2415 if (ec != std::errc{} || ptr != targetID.end()) 2416 { 2417 messages::resourceNotFound(asyncResp->res, "LogEntry", param); 2418 return; 2419 } 2420 2421 std::vector<std::filesystem::path> hostLoggerFiles; 2422 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2423 { 2424 BMCWEB_LOG_DEBUG("Failed to get host log file path"); 2425 return; 2426 } 2427 2428 size_t logCount = 0; 2429 size_t top = 1; 2430 std::vector<std::string> logEntries; 2431 // We can get specific entry by skip and top. For example, if we 2432 // want to get nth entry, we can set skip = n-1 and top = 1 to 2433 // get that entry 2434 if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries, 2435 logCount)) 2436 { 2437 messages::internalError(asyncResp->res); 2438 return; 2439 } 2440 2441 if (!logEntries.empty()) 2442 { 2443 nlohmann::json::object_t hostLogEntry; 2444 fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry); 2445 asyncResp->res.jsonValue.update(hostLogEntry); 2446 return; 2447 } 2448 2449 // Requested ID was not found 2450 messages::resourceNotFound(asyncResp->res, "LogEntry", param); 2451 }); 2452 } 2453 2454 inline void handleBMCLogServicesCollectionGet( 2455 crow::App& app, const crow::Request& req, 2456 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2457 const std::string& managerId) 2458 { 2459 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2460 { 2461 return; 2462 } 2463 2464 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2465 { 2466 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2467 return; 2468 } 2469 2470 // Collections don't include the static data added by SubRoute 2471 // because it has a duplicate entry for members 2472 asyncResp->res.jsonValue["@odata.type"] = 2473 "#LogServiceCollection.LogServiceCollection"; 2474 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2475 "/redfish/v1/Managers/{}/LogServices", BMCWEB_REDFISH_MANAGER_URI_NAME); 2476 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 2477 asyncResp->res.jsonValue["Description"] = 2478 "Collection of LogServices for this Manager"; 2479 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; 2480 logServiceArray = nlohmann::json::array(); 2481 2482 if constexpr (BMCWEB_REDFISH_BMC_JOURNAL) 2483 { 2484 nlohmann::json::object_t journal; 2485 journal["@odata.id"] = 2486 boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 2487 BMCWEB_REDFISH_MANAGER_URI_NAME); 2488 logServiceArray.emplace_back(std::move(journal)); 2489 } 2490 2491 asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size(); 2492 2493 if constexpr (BMCWEB_REDFISH_DUMP_LOG) 2494 { 2495 constexpr std::array<std::string_view, 1> interfaces = { 2496 "xyz.openbmc_project.Collection.DeleteAll"}; 2497 dbus::utility::getSubTreePaths( 2498 "/xyz/openbmc_project/dump", 0, interfaces, 2499 [asyncResp](const boost::system::error_code& ec, 2500 const dbus::utility::MapperGetSubTreePathsResponse& 2501 subTreePaths) { 2502 if (ec) 2503 { 2504 BMCWEB_LOG_ERROR( 2505 "handleBMCLogServicesCollectionGet respHandler got error {}", 2506 ec); 2507 // Assume that getting an error simply means there are no dump 2508 // LogServices. Return without adding any error response. 2509 return; 2510 } 2511 2512 nlohmann::json& logServiceArrayLocal = 2513 asyncResp->res.jsonValue["Members"]; 2514 2515 for (const std::string& path : subTreePaths) 2516 { 2517 if (path == "/xyz/openbmc_project/dump/bmc") 2518 { 2519 nlohmann::json::object_t member; 2520 member["@odata.id"] = boost::urls::format( 2521 "/redfish/v1/Managers/{}/LogServices/Dump", 2522 BMCWEB_REDFISH_MANAGER_URI_NAME); 2523 logServiceArrayLocal.emplace_back(std::move(member)); 2524 } 2525 else if (path == "/xyz/openbmc_project/dump/faultlog") 2526 { 2527 nlohmann::json::object_t member; 2528 member["@odata.id"] = boost::urls::format( 2529 "/redfish/v1/Managers/{}/LogServices/FaultLog", 2530 BMCWEB_REDFISH_MANAGER_URI_NAME); 2531 logServiceArrayLocal.emplace_back(std::move(member)); 2532 } 2533 } 2534 2535 asyncResp->res.jsonValue["Members@odata.count"] = 2536 logServiceArrayLocal.size(); 2537 }); 2538 } 2539 } 2540 2541 inline void requestRoutesBMCLogServiceCollection(App& app) 2542 { 2543 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/") 2544 .privileges(redfish::privileges::getLogServiceCollection) 2545 .methods(boost::beast::http::verb::get)( 2546 std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app))); 2547 } 2548 2549 inline void requestRoutesBMCJournalLogService(App& app) 2550 { 2551 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 2552 .privileges(redfish::privileges::getLogService) 2553 .methods(boost::beast::http::verb::get)( 2554 [&app](const crow::Request& req, 2555 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2556 const std::string& managerId) { 2557 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2558 { 2559 return; 2560 } 2561 2562 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2563 { 2564 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2565 return; 2566 } 2567 2568 asyncResp->res.jsonValue["@odata.type"] = 2569 "#LogService.v1_2_0.LogService"; 2570 asyncResp->res.jsonValue["@odata.id"] = 2571 boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 2572 BMCWEB_REDFISH_MANAGER_URI_NAME); 2573 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 2574 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 2575 asyncResp->res.jsonValue["Id"] = "Journal"; 2576 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2577 2578 std::pair<std::string, std::string> redfishDateTimeOffset = 2579 redfish::time_utils::getDateTimeOffsetNow(); 2580 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2581 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2582 redfishDateTimeOffset.second; 2583 2584 asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 2585 "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 2586 BMCWEB_REDFISH_MANAGER_URI_NAME); 2587 }); 2588 } 2589 2590 static int 2591 fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 2592 sd_journal* journal, 2593 nlohmann::json::object_t& bmcJournalLogEntryJson) 2594 { 2595 // Get the Log Entry contents 2596 int ret = 0; 2597 2598 std::string message; 2599 std::string_view syslogID; 2600 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 2601 if (ret < 0) 2602 { 2603 BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}", 2604 strerror(-ret)); 2605 } 2606 if (!syslogID.empty()) 2607 { 2608 message += std::string(syslogID) + ": "; 2609 } 2610 2611 std::string_view msg; 2612 ret = getJournalMetadata(journal, "MESSAGE", msg); 2613 if (ret < 0) 2614 { 2615 BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret)); 2616 return 1; 2617 } 2618 message += std::string(msg); 2619 2620 // Get the severity from the PRIORITY field 2621 long int severity = 8; // Default to an invalid priority 2622 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 2623 if (ret < 0) 2624 { 2625 BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret)); 2626 } 2627 2628 // Get the Created time from the timestamp 2629 std::string entryTimeStr; 2630 if (!getEntryTimestamp(journal, entryTimeStr)) 2631 { 2632 return 1; 2633 } 2634 2635 // Fill in the log entry with the gathered data 2636 bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 2637 bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( 2638 "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}", 2639 BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID); 2640 bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; 2641 bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; 2642 bmcJournalLogEntryJson["Message"] = std::move(message); 2643 bmcJournalLogEntryJson["EntryType"] = "Oem"; 2644 log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; 2645 if (severity <= 2) 2646 { 2647 severityEnum = log_entry::EventSeverity::Critical; 2648 } 2649 else if (severity <= 4) 2650 { 2651 severityEnum = log_entry::EventSeverity::Warning; 2652 } 2653 2654 bmcJournalLogEntryJson["Severity"] = severityEnum; 2655 bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; 2656 bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); 2657 return 0; 2658 } 2659 2660 inline void requestRoutesBMCJournalLogEntryCollection(App& app) 2661 { 2662 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 2663 .privileges(redfish::privileges::getLogEntryCollection) 2664 .methods(boost::beast::http::verb::get)( 2665 [&app](const crow::Request& req, 2666 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2667 const std::string& managerId) { 2668 query_param::QueryCapabilities capabilities = { 2669 .canDelegateTop = true, 2670 .canDelegateSkip = true, 2671 }; 2672 query_param::Query delegatedQuery; 2673 if (!redfish::setUpRedfishRouteWithDelegation( 2674 app, req, asyncResp, delegatedQuery, capabilities)) 2675 { 2676 return; 2677 } 2678 2679 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2680 { 2681 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2682 return; 2683 } 2684 2685 size_t skip = delegatedQuery.skip.value_or(0); 2686 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 2687 2688 // Collections don't include the static data added by SubRoute 2689 // because it has a duplicate entry for members 2690 asyncResp->res.jsonValue["@odata.type"] = 2691 "#LogEntryCollection.LogEntryCollection"; 2692 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2693 "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 2694 BMCWEB_REDFISH_MANAGER_URI_NAME); 2695 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 2696 asyncResp->res.jsonValue["Description"] = 2697 "Collection of BMC Journal Entries"; 2698 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2699 logEntryArray = nlohmann::json::array(); 2700 2701 // Go through the journal and use the timestamp to create a 2702 // unique ID for each entry 2703 sd_journal* journalTmp = nullptr; 2704 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2705 if (ret < 0) 2706 { 2707 BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 2708 messages::internalError(asyncResp->res); 2709 return; 2710 } 2711 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2712 journalTmp, sd_journal_close); 2713 journalTmp = nullptr; 2714 uint64_t entryCount = 0; 2715 // Reset the unique ID on the first entry 2716 bool firstEntry = true; 2717 SD_JOURNAL_FOREACH(journal.get()) 2718 { 2719 entryCount++; 2720 // Handle paging using skip (number of entries to skip from 2721 // the start) and top (number of entries to display) 2722 if (entryCount <= skip || entryCount > skip + top) 2723 { 2724 continue; 2725 } 2726 2727 std::string idStr; 2728 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2729 { 2730 continue; 2731 } 2732 firstEntry = false; 2733 2734 nlohmann::json::object_t bmcJournalLogEntry; 2735 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 2736 bmcJournalLogEntry) != 0) 2737 { 2738 messages::internalError(asyncResp->res); 2739 return; 2740 } 2741 logEntryArray.emplace_back(std::move(bmcJournalLogEntry)); 2742 } 2743 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 2744 if (skip + top < entryCount) 2745 { 2746 asyncResp->res 2747 .jsonValue["Members@odata.nextLink"] = boost::urls::format( 2748 "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 2749 BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 2750 } 2751 }); 2752 } 2753 2754 inline void requestRoutesBMCJournalLogEntry(App& app) 2755 { 2756 BMCWEB_ROUTE( 2757 app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 2758 .privileges(redfish::privileges::getLogEntry) 2759 .methods(boost::beast::http::verb::get)( 2760 [&app](const crow::Request& req, 2761 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2762 const std::string& managerId, const std::string& entryID) { 2763 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2764 { 2765 return; 2766 } 2767 2768 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2769 { 2770 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2771 return; 2772 } 2773 2774 // Convert the unique ID back to a timestamp to find the entry 2775 sd_id128_t bootID{}; 2776 uint64_t ts = 0; 2777 uint64_t index = 0; 2778 if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) 2779 { 2780 return; 2781 } 2782 2783 sd_journal* journalTmp = nullptr; 2784 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2785 if (ret < 0) 2786 { 2787 BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 2788 messages::internalError(asyncResp->res); 2789 return; 2790 } 2791 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2792 journalTmp, sd_journal_close); 2793 journalTmp = nullptr; 2794 // Go to the timestamp in the log and move to the entry at the 2795 // index tracking the unique ID 2796 std::string idStr; 2797 bool firstEntry = true; 2798 ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); 2799 if (ret < 0) 2800 { 2801 BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", 2802 strerror(-ret)); 2803 messages::internalError(asyncResp->res); 2804 return; 2805 } 2806 for (uint64_t i = 0; i <= index; i++) 2807 { 2808 sd_journal_next(journal.get()); 2809 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2810 { 2811 messages::internalError(asyncResp->res); 2812 return; 2813 } 2814 firstEntry = false; 2815 } 2816 // Confirm that the entry ID matches what was requested 2817 if (idStr != entryID) 2818 { 2819 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 2820 return; 2821 } 2822 2823 nlohmann::json::object_t bmcJournalLogEntry; 2824 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 2825 bmcJournalLogEntry) != 0) 2826 { 2827 messages::internalError(asyncResp->res); 2828 return; 2829 } 2830 asyncResp->res.jsonValue.update(bmcJournalLogEntry); 2831 }); 2832 } 2833 2834 inline void 2835 getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2836 const std::string& dumpType) 2837 { 2838 std::string dumpPath; 2839 std::string overWritePolicy; 2840 bool collectDiagnosticDataSupported = false; 2841 2842 if (dumpType == "BMC") 2843 { 2844 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/Dump", 2845 BMCWEB_REDFISH_MANAGER_URI_NAME); 2846 overWritePolicy = "WrapsWhenFull"; 2847 collectDiagnosticDataSupported = true; 2848 } 2849 else if (dumpType == "FaultLog") 2850 { 2851 dumpPath = std::format("/redfish/v1/Managers/{}/LogServices/FaultLog", 2852 BMCWEB_REDFISH_MANAGER_URI_NAME); 2853 overWritePolicy = "Unknown"; 2854 collectDiagnosticDataSupported = false; 2855 } 2856 else if (dumpType == "System") 2857 { 2858 dumpPath = std::format("/redfish/v1/Systems/{}/LogServices/Dump", 2859 BMCWEB_REDFISH_SYSTEM_URI_NAME); 2860 overWritePolicy = "WrapsWhenFull"; 2861 collectDiagnosticDataSupported = true; 2862 } 2863 else 2864 { 2865 BMCWEB_LOG_ERROR("getDumpServiceInfo() invalid dump type: {}", 2866 dumpType); 2867 messages::internalError(asyncResp->res); 2868 return; 2869 } 2870 2871 asyncResp->res.jsonValue["@odata.id"] = dumpPath; 2872 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 2873 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2874 asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService"; 2875 asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename(); 2876 asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy); 2877 2878 std::pair<std::string, std::string> redfishDateTimeOffset = 2879 redfish::time_utils::getDateTimeOffsetNow(); 2880 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2881 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2882 redfishDateTimeOffset.second; 2883 2884 asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries"; 2885 2886 if (collectDiagnosticDataSupported) 2887 { 2888 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] 2889 ["target"] = 2890 dumpPath + "/Actions/LogService.CollectDiagnosticData"; 2891 } 2892 2893 constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface}; 2894 dbus::utility::getSubTreePaths( 2895 "/xyz/openbmc_project/dump", 0, interfaces, 2896 [asyncResp, dumpType, dumpPath]( 2897 const boost::system::error_code& ec, 2898 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) { 2899 if (ec) 2900 { 2901 BMCWEB_LOG_ERROR("getDumpServiceInfo respHandler got error {}", ec); 2902 // Assume that getting an error simply means there are no dump 2903 // LogServices. Return without adding any error response. 2904 return; 2905 } 2906 std::string dbusDumpPath = getDumpPath(dumpType); 2907 for (const std::string& path : subTreePaths) 2908 { 2909 if (path == dbusDumpPath) 2910 { 2911 asyncResp->res 2912 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = 2913 dumpPath + "/Actions/LogService.ClearLog"; 2914 break; 2915 } 2916 } 2917 }); 2918 } 2919 2920 inline void handleLogServicesDumpServiceGet( 2921 crow::App& app, const std::string& dumpType, const crow::Request& req, 2922 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2923 const std::string& managerId) 2924 { 2925 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2926 { 2927 return; 2928 } 2929 2930 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2931 { 2932 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2933 return; 2934 } 2935 2936 getDumpServiceInfo(asyncResp, dumpType); 2937 } 2938 2939 inline void handleLogServicesDumpServiceComputerSystemGet( 2940 crow::App& app, const crow::Request& req, 2941 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2942 const std::string& chassisId) 2943 { 2944 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2945 { 2946 return; 2947 } 2948 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2949 { 2950 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2951 return; 2952 } 2953 getDumpServiceInfo(asyncResp, "System"); 2954 } 2955 2956 inline void handleLogServicesDumpEntriesCollectionGet( 2957 crow::App& app, const std::string& dumpType, const crow::Request& req, 2958 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2959 const std::string& managerId) 2960 { 2961 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2962 { 2963 return; 2964 } 2965 2966 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2967 { 2968 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 2969 return; 2970 } 2971 getDumpEntryCollection(asyncResp, dumpType); 2972 } 2973 2974 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet( 2975 crow::App& app, const crow::Request& req, 2976 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2977 const std::string& chassisId) 2978 { 2979 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2980 { 2981 return; 2982 } 2983 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME) 2984 { 2985 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2986 return; 2987 } 2988 getDumpEntryCollection(asyncResp, "System"); 2989 } 2990 2991 inline void handleLogServicesDumpEntryGet( 2992 crow::App& app, const std::string& dumpType, const crow::Request& req, 2993 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2994 const std::string& managerId, const std::string& dumpId) 2995 { 2996 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2997 { 2998 return; 2999 } 3000 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 3001 { 3002 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 3003 return; 3004 } 3005 getDumpEntryById(asyncResp, dumpId, dumpType); 3006 } 3007 3008 inline void handleLogServicesDumpEntryComputerSystemGet( 3009 crow::App& app, const crow::Request& req, 3010 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3011 const std::string& chassisId, const std::string& dumpId) 3012 { 3013 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3014 { 3015 return; 3016 } 3017 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3018 { 3019 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 3020 return; 3021 } 3022 getDumpEntryById(asyncResp, dumpId, "System"); 3023 } 3024 3025 inline void handleLogServicesDumpEntryDelete( 3026 crow::App& app, const std::string& dumpType, const crow::Request& req, 3027 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3028 const std::string& managerId, const std::string& dumpId) 3029 { 3030 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3031 { 3032 return; 3033 } 3034 3035 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 3036 { 3037 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 3038 return; 3039 } 3040 deleteDumpEntry(asyncResp, dumpId, dumpType); 3041 } 3042 3043 inline void handleLogServicesDumpEntryComputerSystemDelete( 3044 crow::App& app, const crow::Request& req, 3045 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3046 const std::string& chassisId, const std::string& dumpId) 3047 { 3048 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3049 { 3050 return; 3051 } 3052 if (chassisId != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3053 { 3054 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 3055 return; 3056 } 3057 deleteDumpEntry(asyncResp, dumpId, "System"); 3058 } 3059 3060 inline void handleLogServicesDumpEntryDownloadGet( 3061 crow::App& app, const std::string& dumpType, const crow::Request& req, 3062 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3063 const std::string& managerId, const std::string& dumpId) 3064 { 3065 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3066 { 3067 return; 3068 } 3069 3070 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 3071 { 3072 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 3073 return; 3074 } 3075 downloadDumpEntry(asyncResp, dumpId, dumpType); 3076 } 3077 3078 inline void handleDBusEventLogEntryDownloadGet( 3079 crow::App& app, const std::string& dumpType, const crow::Request& req, 3080 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3081 const std::string& systemName, const std::string& entryID) 3082 { 3083 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3084 { 3085 return; 3086 } 3087 if (!http_helpers::isContentTypeAllowed( 3088 req.getHeaderValue("Accept"), 3089 http_helpers::ContentType::OctetStream, true)) 3090 { 3091 asyncResp->res.result(boost::beast::http::status::bad_request); 3092 return; 3093 } 3094 downloadEventLogEntry(asyncResp, systemName, entryID, dumpType); 3095 } 3096 3097 inline void handleLogServicesDumpCollectDiagnosticDataPost( 3098 crow::App& app, const std::string& dumpType, const crow::Request& req, 3099 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3100 const std::string& managerId) 3101 { 3102 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3103 { 3104 return; 3105 } 3106 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 3107 { 3108 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 3109 return; 3110 } 3111 3112 createDump(asyncResp, req, dumpType); 3113 } 3114 3115 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost( 3116 crow::App& app, const crow::Request& req, 3117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3118 const std::string& systemName) 3119 { 3120 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3121 { 3122 return; 3123 } 3124 3125 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3126 { 3127 // Option currently returns no systems. TBD 3128 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3129 systemName); 3130 return; 3131 } 3132 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3133 { 3134 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3135 systemName); 3136 return; 3137 } 3138 createDump(asyncResp, req, "System"); 3139 } 3140 3141 inline void handleLogServicesDumpClearLogPost( 3142 crow::App& app, const std::string& dumpType, const crow::Request& req, 3143 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3144 const std::string& managerId) 3145 { 3146 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3147 { 3148 return; 3149 } 3150 3151 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 3152 { 3153 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 3154 return; 3155 } 3156 clearDump(asyncResp, dumpType); 3157 } 3158 3159 inline void handleLogServicesDumpClearLogComputerSystemPost( 3160 crow::App& app, const crow::Request& req, 3161 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3162 const std::string& systemName) 3163 { 3164 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3165 { 3166 return; 3167 } 3168 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3169 { 3170 // Option currently returns no systems. TBD 3171 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3172 systemName); 3173 return; 3174 } 3175 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3176 { 3177 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3178 systemName); 3179 return; 3180 } 3181 clearDump(asyncResp, "System"); 3182 } 3183 3184 inline void requestRoutesBMCDumpService(App& app) 3185 { 3186 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/") 3187 .privileges(redfish::privileges::getLogService) 3188 .methods(boost::beast::http::verb::get)(std::bind_front( 3189 handleLogServicesDumpServiceGet, std::ref(app), "BMC")); 3190 } 3191 3192 inline void requestRoutesBMCDumpEntryCollection(App& app) 3193 { 3194 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/") 3195 .privileges(redfish::privileges::getLogEntryCollection) 3196 .methods(boost::beast::http::verb::get)(std::bind_front( 3197 handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC")); 3198 } 3199 3200 inline void requestRoutesBMCDumpEntry(App& app) 3201 { 3202 BMCWEB_ROUTE(app, 3203 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/") 3204 .privileges(redfish::privileges::getLogEntry) 3205 .methods(boost::beast::http::verb::get)(std::bind_front( 3206 handleLogServicesDumpEntryGet, std::ref(app), "BMC")); 3207 3208 BMCWEB_ROUTE(app, 3209 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/") 3210 .privileges(redfish::privileges::deleteLogEntry) 3211 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3212 handleLogServicesDumpEntryDelete, std::ref(app), "BMC")); 3213 } 3214 3215 inline void requestRoutesBMCDumpEntryDownload(App& app) 3216 { 3217 BMCWEB_ROUTE( 3218 app, 3219 "/redfish/v1/Managers/<str>/LogServices/Dump/Entries/<str>/attachment/") 3220 .privileges(redfish::privileges::getLogEntry) 3221 .methods(boost::beast::http::verb::get)(std::bind_front( 3222 handleLogServicesDumpEntryDownloadGet, std::ref(app), "BMC")); 3223 } 3224 3225 inline void requestRoutesBMCDumpCreate(App& app) 3226 { 3227 BMCWEB_ROUTE( 3228 app, 3229 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 3230 .privileges(redfish::privileges::postLogService) 3231 .methods(boost::beast::http::verb::post)( 3232 std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost, 3233 std::ref(app), "BMC")); 3234 } 3235 3236 inline void requestRoutesBMCDumpClear(App& app) 3237 { 3238 BMCWEB_ROUTE( 3239 app, 3240 "/redfish/v1/Managers/<str>/LogServices/Dump/Actions/LogService.ClearLog/") 3241 .privileges(redfish::privileges::postLogService) 3242 .methods(boost::beast::http::verb::post)(std::bind_front( 3243 handleLogServicesDumpClearLogPost, std::ref(app), "BMC")); 3244 } 3245 3246 inline void requestRoutesDBusEventLogEntryDownload(App& app) 3247 { 3248 BMCWEB_ROUTE( 3249 app, 3250 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/") 3251 .privileges(redfish::privileges::getLogEntry) 3252 .methods(boost::beast::http::verb::get)(std::bind_front( 3253 handleDBusEventLogEntryDownloadGet, std::ref(app), "System")); 3254 } 3255 3256 inline void requestRoutesFaultLogDumpService(App& app) 3257 { 3258 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/") 3259 .privileges(redfish::privileges::getLogService) 3260 .methods(boost::beast::http::verb::get)(std::bind_front( 3261 handleLogServicesDumpServiceGet, std::ref(app), "FaultLog")); 3262 } 3263 3264 inline void requestRoutesFaultLogDumpEntryCollection(App& app) 3265 { 3266 BMCWEB_ROUTE(app, 3267 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/") 3268 .privileges(redfish::privileges::getLogEntryCollection) 3269 .methods(boost::beast::http::verb::get)( 3270 std::bind_front(handleLogServicesDumpEntriesCollectionGet, 3271 std::ref(app), "FaultLog")); 3272 } 3273 3274 inline void requestRoutesFaultLogDumpEntry(App& app) 3275 { 3276 BMCWEB_ROUTE( 3277 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/") 3278 .privileges(redfish::privileges::getLogEntry) 3279 .methods(boost::beast::http::verb::get)(std::bind_front( 3280 handleLogServicesDumpEntryGet, std::ref(app), "FaultLog")); 3281 3282 BMCWEB_ROUTE( 3283 app, "/redfish/v1/Managers/<str>/LogServices/FaultLog/Entries/<str>/") 3284 .privileges(redfish::privileges::deleteLogEntry) 3285 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3286 handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog")); 3287 } 3288 3289 inline void requestRoutesFaultLogDumpClear(App& app) 3290 { 3291 BMCWEB_ROUTE( 3292 app, 3293 "/redfish/v1/Managers/<str>/LogServices/FaultLog/Actions/LogService.ClearLog/") 3294 .privileges(redfish::privileges::postLogService) 3295 .methods(boost::beast::http::verb::post)(std::bind_front( 3296 handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog")); 3297 } 3298 3299 inline void requestRoutesSystemDumpService(App& app) 3300 { 3301 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/") 3302 .privileges(redfish::privileges::getLogService) 3303 .methods(boost::beast::http::verb::get)(std::bind_front( 3304 handleLogServicesDumpServiceComputerSystemGet, std::ref(app))); 3305 } 3306 3307 inline void requestRoutesSystemDumpEntryCollection(App& app) 3308 { 3309 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/") 3310 .privileges(redfish::privileges::getLogEntryCollection) 3311 .methods(boost::beast::http::verb::get)(std::bind_front( 3312 handleLogServicesDumpEntriesCollectionComputerSystemGet, 3313 std::ref(app))); 3314 } 3315 3316 inline void requestRoutesSystemDumpEntry(App& app) 3317 { 3318 BMCWEB_ROUTE(app, 3319 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/") 3320 .privileges(redfish::privileges::getLogEntry) 3321 .methods(boost::beast::http::verb::get)(std::bind_front( 3322 handleLogServicesDumpEntryComputerSystemGet, std::ref(app))); 3323 3324 BMCWEB_ROUTE(app, 3325 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/") 3326 .privileges(redfish::privileges::deleteLogEntry) 3327 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3328 handleLogServicesDumpEntryComputerSystemDelete, std::ref(app))); 3329 } 3330 3331 inline void requestRoutesSystemDumpCreate(App& app) 3332 { 3333 BMCWEB_ROUTE( 3334 app, 3335 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 3336 .privileges(redfish::privileges::postLogService) 3337 .methods(boost::beast::http::verb::post)(std::bind_front( 3338 handleLogServicesDumpCollectDiagnosticDataComputerSystemPost, 3339 std::ref(app))); 3340 } 3341 3342 inline void requestRoutesSystemDumpClear(App& app) 3343 { 3344 BMCWEB_ROUTE( 3345 app, 3346 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/") 3347 .privileges(redfish::privileges::postLogService) 3348 .methods(boost::beast::http::verb::post)(std::bind_front( 3349 handleLogServicesDumpClearLogComputerSystemPost, std::ref(app))); 3350 } 3351 3352 inline void requestRoutesCrashdumpService(App& app) 3353 { 3354 // Note: Deviated from redfish privilege registry for GET & HEAD 3355 // method for security reasons. 3356 /** 3357 * Functions triggers appropriate requests on DBus 3358 */ 3359 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/") 3360 // This is incorrect, should be: 3361 //.privileges(redfish::privileges::getLogService) 3362 .privileges({{"ConfigureManager"}}) 3363 .methods(boost::beast::http::verb::get)( 3364 [&app](const crow::Request& req, 3365 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3366 const std::string& systemName) { 3367 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3368 { 3369 return; 3370 } 3371 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3372 { 3373 // Option currently returns no systems. TBD 3374 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3375 systemName); 3376 return; 3377 } 3378 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3379 { 3380 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3381 systemName); 3382 return; 3383 } 3384 3385 // Copy over the static data to include the entries added by 3386 // SubRoute 3387 asyncResp->res.jsonValue["@odata.id"] = 3388 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump", 3389 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3390 asyncResp->res.jsonValue["@odata.type"] = 3391 "#LogService.v1_2_0.LogService"; 3392 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 3393 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 3394 asyncResp->res.jsonValue["Id"] = "Crashdump"; 3395 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 3396 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 3397 3398 std::pair<std::string, std::string> redfishDateTimeOffset = 3399 redfish::time_utils::getDateTimeOffsetNow(); 3400 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3401 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3402 redfishDateTimeOffset.second; 3403 3404 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 3405 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump/Entries", 3406 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3407 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] 3408 ["target"] = std::format( 3409 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.ClearLog", 3410 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3411 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] 3412 ["target"] = std::format( 3413 "/redfish/v1/Systems/{}/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData", 3414 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3415 }); 3416 } 3417 3418 void inline requestRoutesCrashdumpClear(App& app) 3419 { 3420 BMCWEB_ROUTE( 3421 app, 3422 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/") 3423 // This is incorrect, should be: 3424 //.privileges(redfish::privileges::postLogService) 3425 .privileges({{"ConfigureComponents"}}) 3426 .methods(boost::beast::http::verb::post)( 3427 [&app](const crow::Request& req, 3428 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3429 const std::string& systemName) { 3430 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3431 { 3432 return; 3433 } 3434 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3435 { 3436 // Option currently returns no systems. TBD 3437 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3438 systemName); 3439 return; 3440 } 3441 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3442 { 3443 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3444 systemName); 3445 return; 3446 } 3447 crow::connections::systemBus->async_method_call( 3448 [asyncResp](const boost::system::error_code& ec, 3449 const std::string&) { 3450 if (ec) 3451 { 3452 messages::internalError(asyncResp->res); 3453 return; 3454 } 3455 messages::success(asyncResp->res); 3456 }, 3457 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 3458 }); 3459 } 3460 3461 static void 3462 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3463 const std::string& logID, nlohmann::json& logEntryJson) 3464 { 3465 auto getStoredLogCallback = 3466 [asyncResp, logID, 3467 &logEntryJson](const boost::system::error_code& ec, 3468 const dbus::utility::DBusPropertiesMap& params) { 3469 if (ec) 3470 { 3471 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message()); 3472 if (ec.value() == 3473 boost::system::linux_error::bad_request_descriptor) 3474 { 3475 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3476 } 3477 else 3478 { 3479 messages::internalError(asyncResp->res); 3480 } 3481 return; 3482 } 3483 3484 std::string timestamp{}; 3485 std::string filename{}; 3486 std::string logfile{}; 3487 parseCrashdumpParameters(params, filename, timestamp, logfile); 3488 3489 if (filename.empty() || timestamp.empty()) 3490 { 3491 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3492 return; 3493 } 3494 3495 std::string crashdumpURI = 3496 std::format("/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/", 3497 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 3498 logID + "/" + filename; 3499 nlohmann::json::object_t logEntry; 3500 logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 3501 logEntry["@odata.id"] = boost::urls::format( 3502 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries/{}", 3503 BMCWEB_REDFISH_SYSTEM_URI_NAME, logID); 3504 logEntry["Name"] = "CPU Crashdump"; 3505 logEntry["Id"] = logID; 3506 logEntry["EntryType"] = "Oem"; 3507 logEntry["AdditionalDataURI"] = std::move(crashdumpURI); 3508 logEntry["DiagnosticDataType"] = "OEM"; 3509 logEntry["OEMDiagnosticDataType"] = "PECICrashdump"; 3510 logEntry["Created"] = std::move(timestamp); 3511 3512 // If logEntryJson references an array of LogEntry resources 3513 // ('Members' list), then push this as a new entry, otherwise set it 3514 // directly 3515 if (logEntryJson.is_array()) 3516 { 3517 logEntryJson.push_back(logEntry); 3518 asyncResp->res.jsonValue["Members@odata.count"] = 3519 logEntryJson.size(); 3520 } 3521 else 3522 { 3523 logEntryJson.update(logEntry); 3524 } 3525 }; 3526 sdbusplus::asio::getAllProperties( 3527 *crow::connections::systemBus, crashdumpObject, 3528 crashdumpPath + std::string("/") + logID, crashdumpInterface, 3529 std::move(getStoredLogCallback)); 3530 } 3531 3532 inline void requestRoutesCrashdumpEntryCollection(App& app) 3533 { 3534 // Note: Deviated from redfish privilege registry for GET & HEAD 3535 // method for security reasons. 3536 /** 3537 * Functions triggers appropriate requests on DBus 3538 */ 3539 BMCWEB_ROUTE(app, 3540 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/") 3541 // This is incorrect, should be. 3542 //.privileges(redfish::privileges::postLogEntryCollection) 3543 .privileges({{"ConfigureComponents"}}) 3544 .methods(boost::beast::http::verb::get)( 3545 [&app](const crow::Request& req, 3546 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3547 const std::string& systemName) { 3548 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3549 { 3550 return; 3551 } 3552 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3553 { 3554 // Option currently returns no systems. TBD 3555 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3556 systemName); 3557 return; 3558 } 3559 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3560 { 3561 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3562 systemName); 3563 return; 3564 } 3565 3566 constexpr std::array<std::string_view, 1> interfaces = { 3567 crashdumpInterface}; 3568 dbus::utility::getSubTreePaths( 3569 "/", 0, interfaces, 3570 [asyncResp](const boost::system::error_code& ec, 3571 const std::vector<std::string>& resp) { 3572 if (ec) 3573 { 3574 if (ec.value() != 3575 boost::system::errc::no_such_file_or_directory) 3576 { 3577 BMCWEB_LOG_DEBUG("failed to get entries ec: {}", 3578 ec.message()); 3579 messages::internalError(asyncResp->res); 3580 return; 3581 } 3582 } 3583 asyncResp->res.jsonValue["@odata.type"] = 3584 "#LogEntryCollection.LogEntryCollection"; 3585 asyncResp->res.jsonValue["@odata.id"] = std::format( 3586 "/redfish/v1/Systems/{}/LogServices/Crashdump/Entries", 3587 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3588 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 3589 asyncResp->res.jsonValue["Description"] = 3590 "Collection of Crashdump Entries"; 3591 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3592 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3593 3594 for (const std::string& path : resp) 3595 { 3596 const sdbusplus::message::object_path objPath(path); 3597 // Get the log ID 3598 std::string logID = objPath.filename(); 3599 if (logID.empty()) 3600 { 3601 continue; 3602 } 3603 // Add the log entry to the array 3604 logCrashdumpEntry(asyncResp, logID, 3605 asyncResp->res.jsonValue["Members"]); 3606 } 3607 }); 3608 }); 3609 } 3610 3611 inline void requestRoutesCrashdumpEntry(App& app) 3612 { 3613 // Note: Deviated from redfish privilege registry for GET & HEAD 3614 // method for security reasons. 3615 3616 BMCWEB_ROUTE( 3617 app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/") 3618 // this is incorrect, should be 3619 // .privileges(redfish::privileges::getLogEntry) 3620 .privileges({{"ConfigureComponents"}}) 3621 .methods(boost::beast::http::verb::get)( 3622 [&app](const crow::Request& req, 3623 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3624 const std::string& systemName, const std::string& param) { 3625 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3626 { 3627 return; 3628 } 3629 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3630 { 3631 // Option currently returns no systems. TBD 3632 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3633 systemName); 3634 return; 3635 } 3636 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3637 { 3638 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3639 systemName); 3640 return; 3641 } 3642 const std::string& logID = param; 3643 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 3644 }); 3645 } 3646 3647 inline void requestRoutesCrashdumpFile(App& app) 3648 { 3649 // Note: Deviated from redfish privilege registry for GET & HEAD 3650 // method for security reasons. 3651 BMCWEB_ROUTE( 3652 app, 3653 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/") 3654 .privileges(redfish::privileges::getLogEntry) 3655 .methods(boost::beast::http::verb::get)( 3656 [](const crow::Request& req, 3657 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3658 const std::string& systemName, const std::string& logID, 3659 const std::string& fileName) { 3660 // Do not call getRedfishRoute here since the crashdump file is not a 3661 // Redfish resource. 3662 3663 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3664 { 3665 // Option currently returns no systems. TBD 3666 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3667 systemName); 3668 return; 3669 } 3670 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3671 { 3672 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3673 systemName); 3674 return; 3675 } 3676 3677 auto getStoredLogCallback = 3678 [asyncResp, logID, fileName, url(boost::urls::url(req.url()))]( 3679 const boost::system::error_code& ec, 3680 const std::vector< 3681 std::pair<std::string, dbus::utility::DbusVariantType>>& 3682 resp) { 3683 if (ec) 3684 { 3685 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message()); 3686 messages::internalError(asyncResp->res); 3687 return; 3688 } 3689 3690 std::string dbusFilename{}; 3691 std::string dbusTimestamp{}; 3692 std::string dbusFilepath{}; 3693 3694 parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 3695 dbusFilepath); 3696 3697 if (dbusFilename.empty() || dbusTimestamp.empty() || 3698 dbusFilepath.empty()) 3699 { 3700 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3701 return; 3702 } 3703 3704 // Verify the file name parameter is correct 3705 if (fileName != dbusFilename) 3706 { 3707 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3708 return; 3709 } 3710 3711 if (!asyncResp->res.openFile(dbusFilepath)) 3712 { 3713 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3714 return; 3715 } 3716 3717 // Configure this to be a file download when accessed 3718 // from a browser 3719 asyncResp->res.addHeader( 3720 boost::beast::http::field::content_disposition, "attachment"); 3721 }; 3722 sdbusplus::asio::getAllProperties( 3723 *crow::connections::systemBus, crashdumpObject, 3724 crashdumpPath + std::string("/") + logID, crashdumpInterface, 3725 std::move(getStoredLogCallback)); 3726 }); 3727 } 3728 3729 enum class OEMDiagnosticType 3730 { 3731 onDemand, 3732 telemetry, 3733 invalid, 3734 }; 3735 3736 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr) 3737 { 3738 if (oemDiagStr == "OnDemand") 3739 { 3740 return OEMDiagnosticType::onDemand; 3741 } 3742 if (oemDiagStr == "Telemetry") 3743 { 3744 return OEMDiagnosticType::telemetry; 3745 } 3746 3747 return OEMDiagnosticType::invalid; 3748 } 3749 3750 inline void requestRoutesCrashdumpCollect(App& app) 3751 { 3752 // Note: Deviated from redfish privilege registry for GET & HEAD 3753 // method for security reasons. 3754 BMCWEB_ROUTE( 3755 app, 3756 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/") 3757 // The below is incorrect; Should be ConfigureManager 3758 //.privileges(redfish::privileges::postLogService) 3759 .privileges({{"ConfigureComponents"}}) 3760 .methods(boost::beast::http::verb::post)( 3761 [&app](const crow::Request& req, 3762 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3763 const std::string& systemName) { 3764 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3765 { 3766 return; 3767 } 3768 3769 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3770 { 3771 // Option currently returns no systems. TBD 3772 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3773 systemName); 3774 return; 3775 } 3776 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3777 { 3778 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3779 systemName); 3780 return; 3781 } 3782 3783 std::string diagnosticDataType; 3784 std::string oemDiagnosticDataType; 3785 if (!redfish::json_util::readJsonAction( 3786 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType, 3787 "OEMDiagnosticDataType", oemDiagnosticDataType)) 3788 { 3789 return; 3790 } 3791 3792 if (diagnosticDataType != "OEM") 3793 { 3794 BMCWEB_LOG_ERROR( 3795 "Only OEM DiagnosticDataType supported for Crashdump"); 3796 messages::actionParameterValueFormatError( 3797 asyncResp->res, diagnosticDataType, "DiagnosticDataType", 3798 "CollectDiagnosticData"); 3799 return; 3800 } 3801 3802 OEMDiagnosticType oemDiagType = 3803 getOEMDiagnosticType(oemDiagnosticDataType); 3804 3805 std::string iface; 3806 std::string method; 3807 std::string taskMatchStr; 3808 if (oemDiagType == OEMDiagnosticType::onDemand) 3809 { 3810 iface = crashdumpOnDemandInterface; 3811 method = "GenerateOnDemandLog"; 3812 taskMatchStr = "type='signal'," 3813 "interface='org.freedesktop.DBus.Properties'," 3814 "member='PropertiesChanged'," 3815 "arg0namespace='com.intel.crashdump'"; 3816 } 3817 else if (oemDiagType == OEMDiagnosticType::telemetry) 3818 { 3819 iface = crashdumpTelemetryInterface; 3820 method = "GenerateTelemetryLog"; 3821 taskMatchStr = "type='signal'," 3822 "interface='org.freedesktop.DBus.Properties'," 3823 "member='PropertiesChanged'," 3824 "arg0namespace='com.intel.crashdump'"; 3825 } 3826 else 3827 { 3828 BMCWEB_LOG_ERROR("Unsupported OEMDiagnosticDataType: {}", 3829 oemDiagnosticDataType); 3830 messages::actionParameterValueFormatError( 3831 asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType", 3832 "CollectDiagnosticData"); 3833 return; 3834 } 3835 3836 auto collectCrashdumpCallback = 3837 [asyncResp, payload(task::Payload(req)), 3838 taskMatchStr](const boost::system::error_code& ec, 3839 const std::string&) mutable { 3840 if (ec) 3841 { 3842 if (ec.value() == boost::system::errc::operation_not_supported) 3843 { 3844 messages::resourceInStandby(asyncResp->res); 3845 } 3846 else if (ec.value() == 3847 boost::system::errc::device_or_resource_busy) 3848 { 3849 messages::serviceTemporarilyUnavailable(asyncResp->res, 3850 "60"); 3851 } 3852 else 3853 { 3854 messages::internalError(asyncResp->res); 3855 } 3856 return; 3857 } 3858 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 3859 [](const boost::system::error_code& ec2, sdbusplus::message_t&, 3860 const std::shared_ptr<task::TaskData>& taskData) { 3861 if (!ec2) 3862 { 3863 taskData->messages.emplace_back(messages::taskCompletedOK( 3864 std::to_string(taskData->index))); 3865 taskData->state = "Completed"; 3866 } 3867 return task::completed; 3868 }, 3869 taskMatchStr); 3870 3871 task->startTimer(std::chrono::minutes(5)); 3872 task->populateResp(asyncResp->res); 3873 task->payload.emplace(std::move(payload)); 3874 }; 3875 3876 crow::connections::systemBus->async_method_call( 3877 std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath, 3878 iface, method); 3879 }); 3880 } 3881 3882 /** 3883 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 3884 */ 3885 inline void requestRoutesDBusLogServiceActionsClear(App& app) 3886 { 3887 /** 3888 * Function handles POST method request. 3889 * The Clear Log actions does not require any parameter.The action deletes 3890 * all entries found in the Entries collection for this Log Service. 3891 */ 3892 3893 BMCWEB_ROUTE( 3894 app, 3895 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/") 3896 .privileges(redfish::privileges::postLogService) 3897 .methods(boost::beast::http::verb::post)( 3898 [&app](const crow::Request& req, 3899 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3900 const std::string& systemName) { 3901 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3902 { 3903 return; 3904 } 3905 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3906 { 3907 // Option currently returns no systems. TBD 3908 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3909 systemName); 3910 return; 3911 } 3912 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3913 { 3914 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3915 systemName); 3916 return; 3917 } 3918 BMCWEB_LOG_DEBUG("Do delete all entries."); 3919 3920 // Process response from Logging service. 3921 auto respHandler = [asyncResp](const boost::system::error_code& ec) { 3922 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done"); 3923 if (ec) 3924 { 3925 // TODO Handle for specific error code 3926 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec); 3927 asyncResp->res.result( 3928 boost::beast::http::status::internal_server_error); 3929 return; 3930 } 3931 3932 asyncResp->res.result(boost::beast::http::status::no_content); 3933 }; 3934 3935 // Make call to Logging service to request Clear Log 3936 crow::connections::systemBus->async_method_call( 3937 respHandler, "xyz.openbmc_project.Logging", 3938 "/xyz/openbmc_project/logging", 3939 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3940 }); 3941 } 3942 3943 /**************************************************** 3944 * Redfish PostCode interfaces 3945 * using DBUS interface: getPostCodesTS 3946 ******************************************************/ 3947 inline void requestRoutesPostCodesLogService(App& app) 3948 { 3949 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/") 3950 .privileges(redfish::privileges::getLogService) 3951 .methods(boost::beast::http::verb::get)( 3952 [&app](const crow::Request& req, 3953 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3954 const std::string& systemName) { 3955 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3956 { 3957 return; 3958 } 3959 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 3960 { 3961 // Option currently returns no systems. TBD 3962 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3963 systemName); 3964 return; 3965 } 3966 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 3967 { 3968 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3969 systemName); 3970 return; 3971 } 3972 asyncResp->res.jsonValue["@odata.id"] = 3973 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes", 3974 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3975 asyncResp->res.jsonValue["@odata.type"] = 3976 "#LogService.v1_2_0.LogService"; 3977 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 3978 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 3979 asyncResp->res.jsonValue["Id"] = "PostCodes"; 3980 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 3981 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 3982 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 3983 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3984 3985 std::pair<std::string, std::string> redfishDateTimeOffset = 3986 redfish::time_utils::getDateTimeOffsetNow(); 3987 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3988 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3989 redfishDateTimeOffset.second; 3990 3991 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] 3992 ["target"] = std::format( 3993 "/redfish/v1/Systems/{}/LogServices/PostCodes/Actions/LogService.ClearLog", 3994 BMCWEB_REDFISH_SYSTEM_URI_NAME); 3995 }); 3996 } 3997 3998 inline void requestRoutesPostCodesClear(App& app) 3999 { 4000 BMCWEB_ROUTE( 4001 app, 4002 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/") 4003 // The following privilege is incorrect; It should be ConfigureManager 4004 //.privileges(redfish::privileges::postLogService) 4005 .privileges({{"ConfigureComponents"}}) 4006 .methods(boost::beast::http::verb::post)( 4007 [&app](const crow::Request& req, 4008 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4009 const std::string& systemName) { 4010 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4011 { 4012 return; 4013 } 4014 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 4015 { 4016 // Option currently returns no systems. TBD 4017 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4018 systemName); 4019 return; 4020 } 4021 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 4022 { 4023 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4024 systemName); 4025 return; 4026 } 4027 BMCWEB_LOG_DEBUG("Do delete all postcodes entries."); 4028 4029 // Make call to post-code service to request clear all 4030 crow::connections::systemBus->async_method_call( 4031 [asyncResp](const boost::system::error_code& ec) { 4032 if (ec) 4033 { 4034 // TODO Handle for specific error code 4035 BMCWEB_LOG_ERROR("doClearPostCodes resp_handler got error {}", 4036 ec); 4037 asyncResp->res.result( 4038 boost::beast::http::status::internal_server_error); 4039 messages::internalError(asyncResp->res); 4040 return; 4041 } 4042 messages::success(asyncResp->res); 4043 }, 4044 "xyz.openbmc_project.State.Boot.PostCode0", 4045 "/xyz/openbmc_project/State/Boot/PostCode0", 4046 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 4047 }); 4048 } 4049 4050 /** 4051 * @brief Parse post code ID and get the current value and index value 4052 * eg: postCodeID=B1-2, currentValue=1, index=2 4053 * 4054 * @param[in] postCodeID Post Code ID 4055 * @param[out] currentValue Current value 4056 * @param[out] index Index value 4057 * 4058 * @return bool true if the parsing is successful, false the parsing fails 4059 */ 4060 inline bool parsePostCode(std::string_view postCodeID, uint64_t& currentValue, 4061 uint16_t& index) 4062 { 4063 std::vector<std::string> split; 4064 bmcweb::split(split, postCodeID, '-'); 4065 if (split.size() != 2) 4066 { 4067 return false; 4068 } 4069 std::string_view postCodeNumber = split[0]; 4070 if (postCodeNumber.size() < 2) 4071 { 4072 return false; 4073 } 4074 if (postCodeNumber[0] != 'B') 4075 { 4076 return false; 4077 } 4078 postCodeNumber.remove_prefix(1); 4079 auto [ptrIndex, ecIndex] = std::from_chars(postCodeNumber.begin(), 4080 postCodeNumber.end(), index); 4081 if (ptrIndex != postCodeNumber.end() || ecIndex != std::errc()) 4082 { 4083 return false; 4084 } 4085 4086 std::string_view postCodeIndex = split[1]; 4087 4088 auto [ptrValue, ecValue] = std::from_chars( 4089 postCodeIndex.begin(), postCodeIndex.end(), currentValue); 4090 4091 return ptrValue == postCodeIndex.end() && ecValue == std::errc(); 4092 } 4093 4094 static bool fillPostCodeEntry( 4095 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4096 const boost::container::flat_map< 4097 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 4098 const uint16_t bootIndex, const uint64_t codeIndex = 0, 4099 const uint64_t skip = 0, const uint64_t top = 0) 4100 { 4101 // Get the Message from the MessageRegistry 4102 const registries::Message* message = 4103 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 4104 if (message == nullptr) 4105 { 4106 BMCWEB_LOG_ERROR("Couldn't find known message?"); 4107 return false; 4108 } 4109 uint64_t currentCodeIndex = 0; 4110 uint64_t firstCodeTimeUs = 0; 4111 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 4112 code : postcode) 4113 { 4114 currentCodeIndex++; 4115 std::string postcodeEntryID = 4116 "B" + std::to_string(bootIndex) + "-" + 4117 std::to_string(currentCodeIndex); // 1 based index in EntryID string 4118 4119 uint64_t usecSinceEpoch = code.first; 4120 uint64_t usTimeOffset = 0; 4121 4122 if (1 == currentCodeIndex) 4123 { // already incremented 4124 firstCodeTimeUs = code.first; 4125 } 4126 else 4127 { 4128 usTimeOffset = code.first - firstCodeTimeUs; 4129 } 4130 4131 // skip if no specific codeIndex is specified and currentCodeIndex does 4132 // not fall between top and skip 4133 if ((codeIndex == 0) && 4134 (currentCodeIndex <= skip || currentCodeIndex > top)) 4135 { 4136 continue; 4137 } 4138 4139 // skip if a specific codeIndex is specified and does not match the 4140 // currentIndex 4141 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 4142 { 4143 // This is done for simplicity. 1st entry is needed to calculate 4144 // time offset. To improve efficiency, one can get to the entry 4145 // directly (possibly with flatmap's nth method) 4146 continue; 4147 } 4148 4149 // currentCodeIndex is within top and skip or equal to specified code 4150 // index 4151 4152 // Get the Created time from the timestamp 4153 std::string entryTimeStr; 4154 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch); 4155 4156 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 4157 std::ostringstream hexCode; 4158 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 4159 << std::get<0>(code.second); 4160 std::ostringstream timeOffsetStr; 4161 // Set Fixed -Point Notation 4162 timeOffsetStr << std::fixed; 4163 // Set precision to 4 digits 4164 timeOffsetStr << std::setprecision(4); 4165 // Add double to stream 4166 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 4167 4168 std::string bootIndexStr = std::to_string(bootIndex); 4169 std::string timeOffsetString = timeOffsetStr.str(); 4170 std::string hexCodeStr = hexCode.str(); 4171 4172 std::array<std::string_view, 3> messageArgs = { 4173 bootIndexStr, timeOffsetString, hexCodeStr}; 4174 4175 std::string msg = 4176 redfish::registries::fillMessageArgs(messageArgs, message->message); 4177 if (msg.empty()) 4178 { 4179 messages::internalError(asyncResp->res); 4180 return false; 4181 } 4182 4183 // Get Severity template from message registry 4184 std::string severity; 4185 if (message != nullptr) 4186 { 4187 severity = message->messageSeverity; 4188 } 4189 4190 // Format entry 4191 nlohmann::json::object_t bmcLogEntry; 4192 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 4193 bmcLogEntry["@odata.id"] = boost::urls::format( 4194 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/{}", 4195 BMCWEB_REDFISH_SYSTEM_URI_NAME, postcodeEntryID); 4196 bmcLogEntry["Name"] = "POST Code Log Entry"; 4197 bmcLogEntry["Id"] = postcodeEntryID; 4198 bmcLogEntry["Message"] = std::move(msg); 4199 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode"; 4200 bmcLogEntry["MessageArgs"] = messageArgs; 4201 bmcLogEntry["EntryType"] = "Event"; 4202 bmcLogEntry["Severity"] = std::move(severity); 4203 bmcLogEntry["Created"] = entryTimeStr; 4204 if (!std::get<std::vector<uint8_t>>(code.second).empty()) 4205 { 4206 bmcLogEntry["AdditionalDataURI"] = 4207 std::format( 4208 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries/", 4209 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 4210 postcodeEntryID + "/attachment"; 4211 } 4212 4213 // codeIndex is only specified when querying single entry, return only 4214 // that entry in this case 4215 if (codeIndex != 0) 4216 { 4217 asyncResp->res.jsonValue.update(bmcLogEntry); 4218 return true; 4219 } 4220 4221 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 4222 logEntryArray.emplace_back(std::move(bmcLogEntry)); 4223 } 4224 4225 // Return value is always false when querying multiple entries 4226 return false; 4227 } 4228 4229 static void 4230 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4231 const std::string& entryId) 4232 { 4233 uint16_t bootIndex = 0; 4234 uint64_t codeIndex = 0; 4235 if (!parsePostCode(entryId, codeIndex, bootIndex)) 4236 { 4237 // Requested ID was not found 4238 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4239 return; 4240 } 4241 4242 if (bootIndex == 0 || codeIndex == 0) 4243 { 4244 // 0 is an invalid index 4245 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4246 return; 4247 } 4248 4249 crow::connections::systemBus->async_method_call( 4250 [asyncResp, entryId, bootIndex, 4251 codeIndex](const boost::system::error_code& ec, 4252 const boost::container::flat_map< 4253 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 4254 postcode) { 4255 if (ec) 4256 { 4257 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 4258 messages::internalError(asyncResp->res); 4259 return; 4260 } 4261 4262 if (postcode.empty()) 4263 { 4264 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4265 return; 4266 } 4267 4268 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 4269 { 4270 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4271 return; 4272 } 4273 }, 4274 "xyz.openbmc_project.State.Boot.PostCode0", 4275 "/xyz/openbmc_project/State/Boot/PostCode0", 4276 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 4277 bootIndex); 4278 } 4279 4280 static void 4281 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4282 const uint16_t bootIndex, const uint16_t bootCount, 4283 const uint64_t entryCount, size_t skip, size_t top) 4284 { 4285 crow::connections::systemBus->async_method_call( 4286 [asyncResp, bootIndex, bootCount, entryCount, skip, 4287 top](const boost::system::error_code& ec, 4288 const boost::container::flat_map< 4289 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 4290 postcode) { 4291 if (ec) 4292 { 4293 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 4294 messages::internalError(asyncResp->res); 4295 return; 4296 } 4297 4298 uint64_t endCount = entryCount; 4299 if (!postcode.empty()) 4300 { 4301 endCount = entryCount + postcode.size(); 4302 if (skip < endCount && (top + skip) > entryCount) 4303 { 4304 uint64_t thisBootSkip = std::max(static_cast<uint64_t>(skip), 4305 entryCount) - 4306 entryCount; 4307 uint64_t thisBootTop = 4308 std::min(static_cast<uint64_t>(top + skip), endCount) - 4309 entryCount; 4310 4311 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 4312 thisBootSkip, thisBootTop); 4313 } 4314 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 4315 } 4316 4317 // continue to previous bootIndex 4318 if (bootIndex < bootCount) 4319 { 4320 getPostCodeForBoot(asyncResp, static_cast<uint16_t>(bootIndex + 1), 4321 bootCount, endCount, skip, top); 4322 } 4323 else if (skip + top < endCount) 4324 { 4325 asyncResp->res.jsonValue["Members@odata.nextLink"] = 4326 std::format( 4327 "/redfish/v1/Systems/{}/LogServices/PostCodes/Entries?$skip=", 4328 BMCWEB_REDFISH_SYSTEM_URI_NAME) + 4329 std::to_string(skip + top); 4330 } 4331 }, 4332 "xyz.openbmc_project.State.Boot.PostCode0", 4333 "/xyz/openbmc_project/State/Boot/PostCode0", 4334 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 4335 bootIndex); 4336 } 4337 4338 static void 4339 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4340 size_t skip, size_t top) 4341 { 4342 uint64_t entryCount = 0; 4343 sdbusplus::asio::getProperty<uint16_t>( 4344 *crow::connections::systemBus, 4345 "xyz.openbmc_project.State.Boot.PostCode0", 4346 "/xyz/openbmc_project/State/Boot/PostCode0", 4347 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 4348 [asyncResp, entryCount, skip, top](const boost::system::error_code& ec, 4349 const uint16_t bootCount) { 4350 if (ec) 4351 { 4352 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 4353 messages::internalError(asyncResp->res); 4354 return; 4355 } 4356 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top); 4357 }); 4358 } 4359 4360 inline void requestRoutesPostCodesEntryCollection(App& app) 4361 { 4362 BMCWEB_ROUTE(app, 4363 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/") 4364 .privileges(redfish::privileges::getLogEntryCollection) 4365 .methods(boost::beast::http::verb::get)( 4366 [&app](const crow::Request& req, 4367 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4368 const std::string& systemName) { 4369 query_param::QueryCapabilities capabilities = { 4370 .canDelegateTop = true, 4371 .canDelegateSkip = true, 4372 }; 4373 query_param::Query delegatedQuery; 4374 if (!redfish::setUpRedfishRouteWithDelegation( 4375 app, req, asyncResp, delegatedQuery, capabilities)) 4376 { 4377 return; 4378 } 4379 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 4380 { 4381 // Option currently returns no systems. TBD 4382 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4383 systemName); 4384 return; 4385 } 4386 4387 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 4388 { 4389 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4390 systemName); 4391 return; 4392 } 4393 asyncResp->res.jsonValue["@odata.type"] = 4394 "#LogEntryCollection.LogEntryCollection"; 4395 asyncResp->res.jsonValue["@odata.id"] = 4396 std::format("/redfish/v1/Systems/{}/LogServices/PostCodes/Entries", 4397 BMCWEB_REDFISH_SYSTEM_URI_NAME); 4398 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 4399 asyncResp->res.jsonValue["Description"] = 4400 "Collection of POST Code Log Entries"; 4401 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 4402 asyncResp->res.jsonValue["Members@odata.count"] = 0; 4403 size_t skip = delegatedQuery.skip.value_or(0); 4404 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 4405 getCurrentBootNumber(asyncResp, skip, top); 4406 }); 4407 } 4408 4409 inline void requestRoutesPostCodesEntryAdditionalData(App& app) 4410 { 4411 BMCWEB_ROUTE( 4412 app, 4413 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/") 4414 .privileges(redfish::privileges::getLogEntry) 4415 .methods(boost::beast::http::verb::get)( 4416 [&app](const crow::Request& req, 4417 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4418 const std::string& systemName, 4419 const std::string& postCodeID) { 4420 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4421 { 4422 return; 4423 } 4424 if (!http_helpers::isContentTypeAllowed( 4425 req.getHeaderValue("Accept"), 4426 http_helpers::ContentType::OctetStream, true)) 4427 { 4428 asyncResp->res.result(boost::beast::http::status::bad_request); 4429 return; 4430 } 4431 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 4432 { 4433 // Option currently returns no systems. TBD 4434 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4435 systemName); 4436 return; 4437 } 4438 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 4439 { 4440 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4441 systemName); 4442 return; 4443 } 4444 4445 uint64_t currentValue = 0; 4446 uint16_t index = 0; 4447 if (!parsePostCode(postCodeID, currentValue, index)) 4448 { 4449 messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); 4450 return; 4451 } 4452 4453 crow::connections::systemBus->async_method_call( 4454 [asyncResp, postCodeID, currentValue]( 4455 const boost::system::error_code& ec, 4456 const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>& 4457 postcodes) { 4458 if (ec.value() == EBADR) 4459 { 4460 messages::resourceNotFound(asyncResp->res, "LogEntry", 4461 postCodeID); 4462 return; 4463 } 4464 if (ec) 4465 { 4466 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 4467 messages::internalError(asyncResp->res); 4468 return; 4469 } 4470 4471 size_t value = static_cast<size_t>(currentValue) - 1; 4472 if (value == std::string::npos || postcodes.size() < currentValue) 4473 { 4474 BMCWEB_LOG_WARNING("Wrong currentValue value"); 4475 messages::resourceNotFound(asyncResp->res, "LogEntry", 4476 postCodeID); 4477 return; 4478 } 4479 4480 const auto& [tID, c] = postcodes[value]; 4481 if (c.empty()) 4482 { 4483 BMCWEB_LOG_WARNING("No found post code data"); 4484 messages::resourceNotFound(asyncResp->res, "LogEntry", 4485 postCodeID); 4486 return; 4487 } 4488 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 4489 const char* d = reinterpret_cast<const char*>(c.data()); 4490 std::string_view strData(d, c.size()); 4491 4492 asyncResp->res.addHeader(boost::beast::http::field::content_type, 4493 "application/octet-stream"); 4494 asyncResp->res.addHeader( 4495 boost::beast::http::field::content_transfer_encoding, "Base64"); 4496 asyncResp->res.write(crow::utility::base64encode(strData)); 4497 }, 4498 "xyz.openbmc_project.State.Boot.PostCode0", 4499 "/xyz/openbmc_project/State/Boot/PostCode0", 4500 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index); 4501 }); 4502 } 4503 4504 inline void requestRoutesPostCodesEntry(App& app) 4505 { 4506 BMCWEB_ROUTE( 4507 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/") 4508 .privileges(redfish::privileges::getLogEntry) 4509 .methods(boost::beast::http::verb::get)( 4510 [&app](const crow::Request& req, 4511 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4512 const std::string& systemName, const std::string& targetID) { 4513 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4514 { 4515 return; 4516 } 4517 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM) 4518 { 4519 // Option currently returns no systems. TBD 4520 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4521 systemName); 4522 return; 4523 } 4524 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME) 4525 { 4526 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4527 systemName); 4528 return; 4529 } 4530 4531 getPostCodeForEntry(asyncResp, targetID); 4532 }); 4533 } 4534 4535 } // namespace redfish 4536