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