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 log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; 2598 if (severity <= 2) 2599 { 2600 severityEnum = log_entry::EventSeverity::Critical; 2601 } 2602 else if (severity <= 4) 2603 { 2604 severityEnum = log_entry::EventSeverity::Warning; 2605 } 2606 2607 bmcJournalLogEntryJson["Severity"] = severityEnum; 2608 bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; 2609 bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); 2610 return 0; 2611 } 2612 2613 inline void requestRoutesBMCJournalLogEntryCollection(App& app) 2614 { 2615 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 2616 .privileges(redfish::privileges::getLogEntryCollection) 2617 .methods(boost::beast::http::verb::get)( 2618 [&app](const crow::Request& req, 2619 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2620 query_param::QueryCapabilities capabilities = { 2621 .canDelegateTop = true, 2622 .canDelegateSkip = true, 2623 }; 2624 query_param::Query delegatedQuery; 2625 if (!redfish::setUpRedfishRouteWithDelegation( 2626 app, req, asyncResp, delegatedQuery, capabilities)) 2627 { 2628 return; 2629 } 2630 2631 size_t skip = delegatedQuery.skip.value_or(0); 2632 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 2633 2634 // Collections don't include the static data added by SubRoute 2635 // because it has a duplicate entry for members 2636 asyncResp->res.jsonValue["@odata.type"] = 2637 "#LogEntryCollection.LogEntryCollection"; 2638 asyncResp->res.jsonValue["@odata.id"] = 2639 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 2640 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 2641 asyncResp->res.jsonValue["Description"] = 2642 "Collection of BMC Journal Entries"; 2643 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2644 logEntryArray = nlohmann::json::array(); 2645 2646 // Go through the journal and use the timestamp to create a 2647 // unique ID for each entry 2648 sd_journal* journalTmp = nullptr; 2649 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2650 if (ret < 0) 2651 { 2652 BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 2653 messages::internalError(asyncResp->res); 2654 return; 2655 } 2656 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2657 journalTmp, sd_journal_close); 2658 journalTmp = nullptr; 2659 uint64_t entryCount = 0; 2660 // Reset the unique ID on the first entry 2661 bool firstEntry = true; 2662 SD_JOURNAL_FOREACH(journal.get()) 2663 { 2664 entryCount++; 2665 // Handle paging using skip (number of entries to skip from 2666 // the start) and top (number of entries to display) 2667 if (entryCount <= skip || entryCount > skip + top) 2668 { 2669 continue; 2670 } 2671 2672 std::string idStr; 2673 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2674 { 2675 continue; 2676 } 2677 firstEntry = false; 2678 2679 nlohmann::json::object_t bmcJournalLogEntry; 2680 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 2681 bmcJournalLogEntry) != 0) 2682 { 2683 messages::internalError(asyncResp->res); 2684 return; 2685 } 2686 logEntryArray.emplace_back(std::move(bmcJournalLogEntry)); 2687 } 2688 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 2689 if (skip + top < entryCount) 2690 { 2691 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2692 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 2693 std::to_string(skip + top); 2694 } 2695 }); 2696 } 2697 2698 inline void requestRoutesBMCJournalLogEntry(App& app) 2699 { 2700 BMCWEB_ROUTE(app, 2701 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/") 2702 .privileges(redfish::privileges::getLogEntry) 2703 .methods(boost::beast::http::verb::get)( 2704 [&app](const crow::Request& req, 2705 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2706 const std::string& entryID) { 2707 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2708 { 2709 return; 2710 } 2711 // Convert the unique ID back to a timestamp to find the entry 2712 sd_id128_t bootID{}; 2713 uint64_t ts = 0; 2714 uint64_t index = 0; 2715 if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) 2716 { 2717 return; 2718 } 2719 2720 sd_journal* journalTmp = nullptr; 2721 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2722 if (ret < 0) 2723 { 2724 BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); 2725 messages::internalError(asyncResp->res); 2726 return; 2727 } 2728 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2729 journalTmp, sd_journal_close); 2730 journalTmp = nullptr; 2731 // Go to the timestamp in the log and move to the entry at the 2732 // index tracking the unique ID 2733 std::string idStr; 2734 bool firstEntry = true; 2735 ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); 2736 if (ret < 0) 2737 { 2738 BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", 2739 strerror(-ret)); 2740 messages::internalError(asyncResp->res); 2741 return; 2742 } 2743 for (uint64_t i = 0; i <= index; i++) 2744 { 2745 sd_journal_next(journal.get()); 2746 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2747 { 2748 messages::internalError(asyncResp->res); 2749 return; 2750 } 2751 firstEntry = false; 2752 } 2753 // Confirm that the entry ID matches what was requested 2754 if (idStr != entryID) 2755 { 2756 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 2757 return; 2758 } 2759 2760 nlohmann::json::object_t bmcJournalLogEntry; 2761 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 2762 bmcJournalLogEntry) != 0) 2763 { 2764 messages::internalError(asyncResp->res); 2765 return; 2766 } 2767 asyncResp->res.jsonValue.update(bmcJournalLogEntry); 2768 }); 2769 } 2770 2771 inline void 2772 getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2773 const std::string& dumpType) 2774 { 2775 std::string dumpPath; 2776 std::string overWritePolicy; 2777 bool collectDiagnosticDataSupported = false; 2778 2779 if (dumpType == "BMC") 2780 { 2781 dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump"; 2782 overWritePolicy = "WrapsWhenFull"; 2783 collectDiagnosticDataSupported = true; 2784 } 2785 else if (dumpType == "FaultLog") 2786 { 2787 dumpPath = "/redfish/v1/Managers/bmc/LogServices/FaultLog"; 2788 overWritePolicy = "Unknown"; 2789 collectDiagnosticDataSupported = false; 2790 } 2791 else if (dumpType == "System") 2792 { 2793 dumpPath = "/redfish/v1/Systems/system/LogServices/Dump"; 2794 overWritePolicy = "WrapsWhenFull"; 2795 collectDiagnosticDataSupported = true; 2796 } 2797 else 2798 { 2799 BMCWEB_LOG_ERROR("getDumpServiceInfo() invalid dump type: {}", 2800 dumpType); 2801 messages::internalError(asyncResp->res); 2802 return; 2803 } 2804 2805 asyncResp->res.jsonValue["@odata.id"] = dumpPath; 2806 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 2807 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2808 asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService"; 2809 asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename(); 2810 asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy); 2811 2812 std::pair<std::string, std::string> redfishDateTimeOffset = 2813 redfish::time_utils::getDateTimeOffsetNow(); 2814 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2815 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2816 redfishDateTimeOffset.second; 2817 2818 asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries"; 2819 2820 if (collectDiagnosticDataSupported) 2821 { 2822 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] 2823 ["target"] = 2824 dumpPath + "/Actions/LogService.CollectDiagnosticData"; 2825 } 2826 2827 constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface}; 2828 dbus::utility::getSubTreePaths( 2829 "/xyz/openbmc_project/dump", 0, interfaces, 2830 [asyncResp, dumpType, dumpPath]( 2831 const boost::system::error_code& ec, 2832 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) { 2833 if (ec) 2834 { 2835 BMCWEB_LOG_ERROR("getDumpServiceInfo respHandler got error {}", ec); 2836 // Assume that getting an error simply means there are no dump 2837 // LogServices. Return without adding any error response. 2838 return; 2839 } 2840 std::string dbusDumpPath = getDumpPath(dumpType); 2841 for (const std::string& path : subTreePaths) 2842 { 2843 if (path == dbusDumpPath) 2844 { 2845 asyncResp->res 2846 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = 2847 dumpPath + "/Actions/LogService.ClearLog"; 2848 break; 2849 } 2850 } 2851 }); 2852 } 2853 2854 inline void handleLogServicesDumpServiceGet( 2855 crow::App& app, const std::string& dumpType, const crow::Request& req, 2856 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2857 { 2858 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2859 { 2860 return; 2861 } 2862 getDumpServiceInfo(asyncResp, dumpType); 2863 } 2864 2865 inline void handleLogServicesDumpServiceComputerSystemGet( 2866 crow::App& app, const crow::Request& req, 2867 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2868 const std::string& chassisId) 2869 { 2870 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2871 { 2872 return; 2873 } 2874 if (chassisId != "system") 2875 { 2876 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2877 return; 2878 } 2879 getDumpServiceInfo(asyncResp, "System"); 2880 } 2881 2882 inline void handleLogServicesDumpEntriesCollectionGet( 2883 crow::App& app, const std::string& dumpType, const crow::Request& req, 2884 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2885 { 2886 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2887 { 2888 return; 2889 } 2890 getDumpEntryCollection(asyncResp, dumpType); 2891 } 2892 2893 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet( 2894 crow::App& app, const crow::Request& req, 2895 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2896 const std::string& chassisId) 2897 { 2898 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2899 { 2900 return; 2901 } 2902 if (chassisId != "system") 2903 { 2904 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2905 return; 2906 } 2907 getDumpEntryCollection(asyncResp, "System"); 2908 } 2909 2910 inline void handleLogServicesDumpEntryGet( 2911 crow::App& app, const std::string& dumpType, const crow::Request& req, 2912 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2913 const std::string& dumpId) 2914 { 2915 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2916 { 2917 return; 2918 } 2919 getDumpEntryById(asyncResp, dumpId, dumpType); 2920 } 2921 2922 inline void handleLogServicesDumpEntryComputerSystemGet( 2923 crow::App& app, const crow::Request& req, 2924 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2925 const std::string& chassisId, const std::string& dumpId) 2926 { 2927 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2928 { 2929 return; 2930 } 2931 if (chassisId != "system") 2932 { 2933 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2934 return; 2935 } 2936 getDumpEntryById(asyncResp, dumpId, "System"); 2937 } 2938 2939 inline void handleLogServicesDumpEntryDelete( 2940 crow::App& app, const std::string& dumpType, const crow::Request& req, 2941 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2942 const std::string& dumpId) 2943 { 2944 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2945 { 2946 return; 2947 } 2948 deleteDumpEntry(asyncResp, dumpId, dumpType); 2949 } 2950 2951 inline void handleLogServicesDumpEntryComputerSystemDelete( 2952 crow::App& app, const crow::Request& req, 2953 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2954 const std::string& chassisId, const std::string& dumpId) 2955 { 2956 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2957 { 2958 return; 2959 } 2960 if (chassisId != "system") 2961 { 2962 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2963 return; 2964 } 2965 deleteDumpEntry(asyncResp, dumpId, "System"); 2966 } 2967 2968 inline void handleLogServicesDumpEntryDownloadGet( 2969 crow::App& app, const std::string& dumpType, const crow::Request& req, 2970 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2971 const std::string& dumpId) 2972 { 2973 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2974 { 2975 return; 2976 } 2977 downloadDumpEntry(asyncResp, dumpId, dumpType); 2978 } 2979 2980 inline void handleDBusEventLogEntryDownloadGet( 2981 crow::App& app, const std::string& dumpType, const crow::Request& req, 2982 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2983 const std::string& systemName, const std::string& entryID) 2984 { 2985 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2986 { 2987 return; 2988 } 2989 if (!http_helpers::isContentTypeAllowed( 2990 req.getHeaderValue("Accept"), 2991 http_helpers::ContentType::OctetStream, true)) 2992 { 2993 asyncResp->res.result(boost::beast::http::status::bad_request); 2994 return; 2995 } 2996 downloadEventLogEntry(asyncResp, systemName, entryID, dumpType); 2997 } 2998 2999 inline void handleLogServicesDumpCollectDiagnosticDataPost( 3000 crow::App& app, const std::string& dumpType, const crow::Request& req, 3001 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 3002 { 3003 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3004 { 3005 return; 3006 } 3007 createDump(asyncResp, req, dumpType); 3008 } 3009 3010 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost( 3011 crow::App& app, const crow::Request& req, 3012 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3013 const std::string& systemName) 3014 { 3015 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3016 { 3017 return; 3018 } 3019 3020 if constexpr (bmcwebEnableMultiHost) 3021 { 3022 // Option currently returns no systems. TBD 3023 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3024 systemName); 3025 return; 3026 } 3027 if (systemName != "system") 3028 { 3029 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3030 systemName); 3031 return; 3032 } 3033 createDump(asyncResp, req, "System"); 3034 } 3035 3036 inline void handleLogServicesDumpClearLogPost( 3037 crow::App& app, const std::string& dumpType, const crow::Request& req, 3038 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 3039 { 3040 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3041 { 3042 return; 3043 } 3044 clearDump(asyncResp, dumpType); 3045 } 3046 3047 inline void handleLogServicesDumpClearLogComputerSystemPost( 3048 crow::App& app, const crow::Request& req, 3049 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3050 const std::string& systemName) 3051 { 3052 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3053 { 3054 return; 3055 } 3056 if constexpr (bmcwebEnableMultiHost) 3057 { 3058 // Option currently returns no systems. TBD 3059 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3060 systemName); 3061 return; 3062 } 3063 if (systemName != "system") 3064 { 3065 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3066 systemName); 3067 return; 3068 } 3069 clearDump(asyncResp, "System"); 3070 } 3071 3072 inline void requestRoutesBMCDumpService(App& app) 3073 { 3074 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 3075 .privileges(redfish::privileges::getLogService) 3076 .methods(boost::beast::http::verb::get)(std::bind_front( 3077 handleLogServicesDumpServiceGet, std::ref(app), "BMC")); 3078 } 3079 3080 inline void requestRoutesBMCDumpEntryCollection(App& app) 3081 { 3082 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 3083 .privileges(redfish::privileges::getLogEntryCollection) 3084 .methods(boost::beast::http::verb::get)(std::bind_front( 3085 handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC")); 3086 } 3087 3088 inline void requestRoutesBMCDumpEntry(App& app) 3089 { 3090 BMCWEB_ROUTE(app, 3091 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 3092 .privileges(redfish::privileges::getLogEntry) 3093 .methods(boost::beast::http::verb::get)(std::bind_front( 3094 handleLogServicesDumpEntryGet, std::ref(app), "BMC")); 3095 3096 BMCWEB_ROUTE(app, 3097 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 3098 .privileges(redfish::privileges::deleteLogEntry) 3099 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3100 handleLogServicesDumpEntryDelete, std::ref(app), "BMC")); 3101 } 3102 3103 inline void requestRoutesBMCDumpEntryDownload(App& app) 3104 { 3105 BMCWEB_ROUTE( 3106 app, 3107 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/attachment/") 3108 .privileges(redfish::privileges::getLogEntry) 3109 .methods(boost::beast::http::verb::get)(std::bind_front( 3110 handleLogServicesDumpEntryDownloadGet, std::ref(app), "BMC")); 3111 } 3112 3113 inline void requestRoutesBMCDumpCreate(App& app) 3114 { 3115 BMCWEB_ROUTE( 3116 app, 3117 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 3118 .privileges(redfish::privileges::postLogService) 3119 .methods(boost::beast::http::verb::post)( 3120 std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost, 3121 std::ref(app), "BMC")); 3122 } 3123 3124 inline void requestRoutesBMCDumpClear(App& app) 3125 { 3126 BMCWEB_ROUTE( 3127 app, 3128 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/") 3129 .privileges(redfish::privileges::postLogService) 3130 .methods(boost::beast::http::verb::post)(std::bind_front( 3131 handleLogServicesDumpClearLogPost, std::ref(app), "BMC")); 3132 } 3133 3134 inline void requestRoutesDBusEventLogEntryDownload(App& app) 3135 { 3136 BMCWEB_ROUTE( 3137 app, 3138 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/") 3139 .privileges(redfish::privileges::getLogEntry) 3140 .methods(boost::beast::http::verb::get)(std::bind_front( 3141 handleDBusEventLogEntryDownloadGet, std::ref(app), "System")); 3142 } 3143 3144 inline void requestRoutesFaultLogDumpService(App& app) 3145 { 3146 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/") 3147 .privileges(redfish::privileges::getLogService) 3148 .methods(boost::beast::http::verb::get)(std::bind_front( 3149 handleLogServicesDumpServiceGet, std::ref(app), "FaultLog")); 3150 } 3151 3152 inline void requestRoutesFaultLogDumpEntryCollection(App& app) 3153 { 3154 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/") 3155 .privileges(redfish::privileges::getLogEntryCollection) 3156 .methods(boost::beast::http::verb::get)( 3157 std::bind_front(handleLogServicesDumpEntriesCollectionGet, 3158 std::ref(app), "FaultLog")); 3159 } 3160 3161 inline void requestRoutesFaultLogDumpEntry(App& app) 3162 { 3163 BMCWEB_ROUTE(app, 3164 "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/") 3165 .privileges(redfish::privileges::getLogEntry) 3166 .methods(boost::beast::http::verb::get)(std::bind_front( 3167 handleLogServicesDumpEntryGet, std::ref(app), "FaultLog")); 3168 3169 BMCWEB_ROUTE(app, 3170 "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/") 3171 .privileges(redfish::privileges::deleteLogEntry) 3172 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3173 handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog")); 3174 } 3175 3176 inline void requestRoutesFaultLogDumpClear(App& app) 3177 { 3178 BMCWEB_ROUTE( 3179 app, 3180 "/redfish/v1/Managers/bmc/LogServices/FaultLog/Actions/LogService.ClearLog/") 3181 .privileges(redfish::privileges::postLogService) 3182 .methods(boost::beast::http::verb::post)(std::bind_front( 3183 handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog")); 3184 } 3185 3186 inline void requestRoutesSystemDumpService(App& app) 3187 { 3188 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/") 3189 .privileges(redfish::privileges::getLogService) 3190 .methods(boost::beast::http::verb::get)(std::bind_front( 3191 handleLogServicesDumpServiceComputerSystemGet, std::ref(app))); 3192 } 3193 3194 inline void requestRoutesSystemDumpEntryCollection(App& app) 3195 { 3196 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/") 3197 .privileges(redfish::privileges::getLogEntryCollection) 3198 .methods(boost::beast::http::verb::get)(std::bind_front( 3199 handleLogServicesDumpEntriesCollectionComputerSystemGet, 3200 std::ref(app))); 3201 } 3202 3203 inline void requestRoutesSystemDumpEntry(App& app) 3204 { 3205 BMCWEB_ROUTE(app, 3206 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/") 3207 .privileges(redfish::privileges::getLogEntry) 3208 .methods(boost::beast::http::verb::get)(std::bind_front( 3209 handleLogServicesDumpEntryComputerSystemGet, std::ref(app))); 3210 3211 BMCWEB_ROUTE(app, 3212 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/") 3213 .privileges(redfish::privileges::deleteLogEntry) 3214 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3215 handleLogServicesDumpEntryComputerSystemDelete, std::ref(app))); 3216 } 3217 3218 inline void requestRoutesSystemDumpCreate(App& app) 3219 { 3220 BMCWEB_ROUTE( 3221 app, 3222 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 3223 .privileges(redfish::privileges::postLogService) 3224 .methods(boost::beast::http::verb::post)(std::bind_front( 3225 handleLogServicesDumpCollectDiagnosticDataComputerSystemPost, 3226 std::ref(app))); 3227 } 3228 3229 inline void requestRoutesSystemDumpClear(App& app) 3230 { 3231 BMCWEB_ROUTE( 3232 app, 3233 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/") 3234 .privileges(redfish::privileges::postLogService) 3235 .methods(boost::beast::http::verb::post)(std::bind_front( 3236 handleLogServicesDumpClearLogComputerSystemPost, std::ref(app))); 3237 } 3238 3239 inline void requestRoutesCrashdumpService(App& app) 3240 { 3241 // Note: Deviated from redfish privilege registry for GET & HEAD 3242 // method for security reasons. 3243 /** 3244 * Functions triggers appropriate requests on DBus 3245 */ 3246 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/") 3247 // This is incorrect, should be: 3248 //.privileges(redfish::privileges::getLogService) 3249 .privileges({{"ConfigureManager"}}) 3250 .methods(boost::beast::http::verb::get)( 3251 [&app](const crow::Request& req, 3252 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3253 const std::string& systemName) { 3254 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3255 { 3256 return; 3257 } 3258 if constexpr (bmcwebEnableMultiHost) 3259 { 3260 // Option currently returns no systems. TBD 3261 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3262 systemName); 3263 return; 3264 } 3265 if (systemName != "system") 3266 { 3267 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3268 systemName); 3269 return; 3270 } 3271 3272 // Copy over the static data to include the entries added by 3273 // SubRoute 3274 asyncResp->res.jsonValue["@odata.id"] = 3275 "/redfish/v1/Systems/system/LogServices/Crashdump"; 3276 asyncResp->res.jsonValue["@odata.type"] = 3277 "#LogService.v1_2_0.LogService"; 3278 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 3279 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 3280 asyncResp->res.jsonValue["Id"] = "Crashdump"; 3281 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 3282 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 3283 3284 std::pair<std::string, std::string> redfishDateTimeOffset = 3285 redfish::time_utils::getDateTimeOffsetNow(); 3286 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3287 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3288 redfishDateTimeOffset.second; 3289 3290 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 3291 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 3292 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] = 3293 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog"; 3294 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] 3295 ["target"] = 3296 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData"; 3297 }); 3298 } 3299 3300 void inline requestRoutesCrashdumpClear(App& app) 3301 { 3302 BMCWEB_ROUTE( 3303 app, 3304 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/") 3305 // This is incorrect, should be: 3306 //.privileges(redfish::privileges::postLogService) 3307 .privileges({{"ConfigureComponents"}}) 3308 .methods(boost::beast::http::verb::post)( 3309 [&app](const crow::Request& req, 3310 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3311 const std::string& systemName) { 3312 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3313 { 3314 return; 3315 } 3316 if constexpr (bmcwebEnableMultiHost) 3317 { 3318 // Option currently returns no systems. TBD 3319 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3320 systemName); 3321 return; 3322 } 3323 if (systemName != "system") 3324 { 3325 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3326 systemName); 3327 return; 3328 } 3329 crow::connections::systemBus->async_method_call( 3330 [asyncResp](const boost::system::error_code& ec, 3331 const std::string&) { 3332 if (ec) 3333 { 3334 messages::internalError(asyncResp->res); 3335 return; 3336 } 3337 messages::success(asyncResp->res); 3338 }, 3339 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 3340 }); 3341 } 3342 3343 static void 3344 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3345 const std::string& logID, nlohmann::json& logEntryJson) 3346 { 3347 auto getStoredLogCallback = 3348 [asyncResp, logID, 3349 &logEntryJson](const boost::system::error_code& ec, 3350 const dbus::utility::DBusPropertiesMap& params) { 3351 if (ec) 3352 { 3353 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message()); 3354 if (ec.value() == 3355 boost::system::linux_error::bad_request_descriptor) 3356 { 3357 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3358 } 3359 else 3360 { 3361 messages::internalError(asyncResp->res); 3362 } 3363 return; 3364 } 3365 3366 std::string timestamp{}; 3367 std::string filename{}; 3368 std::string logfile{}; 3369 parseCrashdumpParameters(params, filename, timestamp, logfile); 3370 3371 if (filename.empty() || timestamp.empty()) 3372 { 3373 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3374 return; 3375 } 3376 3377 std::string crashdumpURI = 3378 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 3379 logID + "/" + filename; 3380 nlohmann::json::object_t logEntry; 3381 logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 3382 logEntry["@odata.id"] = boost::urls::format( 3383 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/{}", 3384 logID); 3385 logEntry["Name"] = "CPU Crashdump"; 3386 logEntry["Id"] = logID; 3387 logEntry["EntryType"] = "Oem"; 3388 logEntry["AdditionalDataURI"] = std::move(crashdumpURI); 3389 logEntry["DiagnosticDataType"] = "OEM"; 3390 logEntry["OEMDiagnosticDataType"] = "PECICrashdump"; 3391 logEntry["Created"] = std::move(timestamp); 3392 3393 // If logEntryJson references an array of LogEntry resources 3394 // ('Members' list), then push this as a new entry, otherwise set it 3395 // directly 3396 if (logEntryJson.is_array()) 3397 { 3398 logEntryJson.push_back(logEntry); 3399 asyncResp->res.jsonValue["Members@odata.count"] = 3400 logEntryJson.size(); 3401 } 3402 else 3403 { 3404 logEntryJson.update(logEntry); 3405 } 3406 }; 3407 sdbusplus::asio::getAllProperties( 3408 *crow::connections::systemBus, crashdumpObject, 3409 crashdumpPath + std::string("/") + logID, crashdumpInterface, 3410 std::move(getStoredLogCallback)); 3411 } 3412 3413 inline void requestRoutesCrashdumpEntryCollection(App& app) 3414 { 3415 // Note: Deviated from redfish privilege registry for GET & HEAD 3416 // method for security reasons. 3417 /** 3418 * Functions triggers appropriate requests on DBus 3419 */ 3420 BMCWEB_ROUTE(app, 3421 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/") 3422 // This is incorrect, should be. 3423 //.privileges(redfish::privileges::postLogEntryCollection) 3424 .privileges({{"ConfigureComponents"}}) 3425 .methods(boost::beast::http::verb::get)( 3426 [&app](const crow::Request& req, 3427 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3428 const std::string& systemName) { 3429 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3430 { 3431 return; 3432 } 3433 if constexpr (bmcwebEnableMultiHost) 3434 { 3435 // Option currently returns no systems. TBD 3436 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3437 systemName); 3438 return; 3439 } 3440 if (systemName != "system") 3441 { 3442 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3443 systemName); 3444 return; 3445 } 3446 3447 constexpr std::array<std::string_view, 1> interfaces = { 3448 crashdumpInterface}; 3449 dbus::utility::getSubTreePaths( 3450 "/", 0, interfaces, 3451 [asyncResp](const boost::system::error_code& ec, 3452 const std::vector<std::string>& resp) { 3453 if (ec) 3454 { 3455 if (ec.value() != 3456 boost::system::errc::no_such_file_or_directory) 3457 { 3458 BMCWEB_LOG_DEBUG("failed to get entries ec: {}", 3459 ec.message()); 3460 messages::internalError(asyncResp->res); 3461 return; 3462 } 3463 } 3464 asyncResp->res.jsonValue["@odata.type"] = 3465 "#LogEntryCollection.LogEntryCollection"; 3466 asyncResp->res.jsonValue["@odata.id"] = 3467 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 3468 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 3469 asyncResp->res.jsonValue["Description"] = 3470 "Collection of Crashdump Entries"; 3471 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3472 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3473 3474 for (const std::string& path : resp) 3475 { 3476 const sdbusplus::message::object_path objPath(path); 3477 // Get the log ID 3478 std::string logID = objPath.filename(); 3479 if (logID.empty()) 3480 { 3481 continue; 3482 } 3483 // Add the log entry to the array 3484 logCrashdumpEntry(asyncResp, logID, 3485 asyncResp->res.jsonValue["Members"]); 3486 } 3487 }); 3488 }); 3489 } 3490 3491 inline void requestRoutesCrashdumpEntry(App& app) 3492 { 3493 // Note: Deviated from redfish privilege registry for GET & HEAD 3494 // method for security reasons. 3495 3496 BMCWEB_ROUTE( 3497 app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/") 3498 // this is incorrect, should be 3499 // .privileges(redfish::privileges::getLogEntry) 3500 .privileges({{"ConfigureComponents"}}) 3501 .methods(boost::beast::http::verb::get)( 3502 [&app](const crow::Request& req, 3503 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3504 const std::string& systemName, const std::string& param) { 3505 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3506 { 3507 return; 3508 } 3509 if constexpr (bmcwebEnableMultiHost) 3510 { 3511 // Option currently returns no systems. TBD 3512 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3513 systemName); 3514 return; 3515 } 3516 if (systemName != "system") 3517 { 3518 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3519 systemName); 3520 return; 3521 } 3522 const std::string& logID = param; 3523 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 3524 }); 3525 } 3526 3527 inline void requestRoutesCrashdumpFile(App& app) 3528 { 3529 // Note: Deviated from redfish privilege registry for GET & HEAD 3530 // method for security reasons. 3531 BMCWEB_ROUTE( 3532 app, 3533 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/") 3534 .privileges(redfish::privileges::getLogEntry) 3535 .methods(boost::beast::http::verb::get)( 3536 [](const crow::Request& req, 3537 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3538 const std::string& systemName, const std::string& logID, 3539 const std::string& fileName) { 3540 // Do not call getRedfishRoute here since the crashdump file is not a 3541 // Redfish resource. 3542 3543 if constexpr (bmcwebEnableMultiHost) 3544 { 3545 // Option currently returns no systems. TBD 3546 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3547 systemName); 3548 return; 3549 } 3550 if (systemName != "system") 3551 { 3552 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3553 systemName); 3554 return; 3555 } 3556 3557 auto getStoredLogCallback = 3558 [asyncResp, logID, fileName, url(boost::urls::url(req.url()))]( 3559 const boost::system::error_code& ec, 3560 const std::vector< 3561 std::pair<std::string, dbus::utility::DbusVariantType>>& 3562 resp) { 3563 if (ec) 3564 { 3565 BMCWEB_LOG_DEBUG("failed to get log ec: {}", ec.message()); 3566 messages::internalError(asyncResp->res); 3567 return; 3568 } 3569 3570 std::string dbusFilename{}; 3571 std::string dbusTimestamp{}; 3572 std::string dbusFilepath{}; 3573 3574 parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 3575 dbusFilepath); 3576 3577 if (dbusFilename.empty() || dbusTimestamp.empty() || 3578 dbusFilepath.empty()) 3579 { 3580 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3581 return; 3582 } 3583 3584 // Verify the file name parameter is correct 3585 if (fileName != dbusFilename) 3586 { 3587 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3588 return; 3589 } 3590 3591 if (!asyncResp->res.openFile(dbusFilepath)) 3592 { 3593 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3594 return; 3595 } 3596 3597 // Configure this to be a file download when accessed 3598 // from a browser 3599 asyncResp->res.addHeader( 3600 boost::beast::http::field::content_disposition, "attachment"); 3601 }; 3602 sdbusplus::asio::getAllProperties( 3603 *crow::connections::systemBus, crashdumpObject, 3604 crashdumpPath + std::string("/") + logID, crashdumpInterface, 3605 std::move(getStoredLogCallback)); 3606 }); 3607 } 3608 3609 enum class OEMDiagnosticType 3610 { 3611 onDemand, 3612 telemetry, 3613 invalid, 3614 }; 3615 3616 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr) 3617 { 3618 if (oemDiagStr == "OnDemand") 3619 { 3620 return OEMDiagnosticType::onDemand; 3621 } 3622 if (oemDiagStr == "Telemetry") 3623 { 3624 return OEMDiagnosticType::telemetry; 3625 } 3626 3627 return OEMDiagnosticType::invalid; 3628 } 3629 3630 inline void requestRoutesCrashdumpCollect(App& app) 3631 { 3632 // Note: Deviated from redfish privilege registry for GET & HEAD 3633 // method for security reasons. 3634 BMCWEB_ROUTE( 3635 app, 3636 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/") 3637 // The below is incorrect; Should be ConfigureManager 3638 //.privileges(redfish::privileges::postLogService) 3639 .privileges({{"ConfigureComponents"}}) 3640 .methods(boost::beast::http::verb::post)( 3641 [&app](const crow::Request& req, 3642 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3643 const std::string& systemName) { 3644 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3645 { 3646 return; 3647 } 3648 3649 if constexpr (bmcwebEnableMultiHost) 3650 { 3651 // Option currently returns no systems. TBD 3652 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3653 systemName); 3654 return; 3655 } 3656 if (systemName != "system") 3657 { 3658 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3659 systemName); 3660 return; 3661 } 3662 3663 std::string diagnosticDataType; 3664 std::string oemDiagnosticDataType; 3665 if (!redfish::json_util::readJsonAction( 3666 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType, 3667 "OEMDiagnosticDataType", oemDiagnosticDataType)) 3668 { 3669 return; 3670 } 3671 3672 if (diagnosticDataType != "OEM") 3673 { 3674 BMCWEB_LOG_ERROR( 3675 "Only OEM DiagnosticDataType supported for Crashdump"); 3676 messages::actionParameterValueFormatError( 3677 asyncResp->res, diagnosticDataType, "DiagnosticDataType", 3678 "CollectDiagnosticData"); 3679 return; 3680 } 3681 3682 OEMDiagnosticType oemDiagType = 3683 getOEMDiagnosticType(oemDiagnosticDataType); 3684 3685 std::string iface; 3686 std::string method; 3687 std::string taskMatchStr; 3688 if (oemDiagType == OEMDiagnosticType::onDemand) 3689 { 3690 iface = crashdumpOnDemandInterface; 3691 method = "GenerateOnDemandLog"; 3692 taskMatchStr = "type='signal'," 3693 "interface='org.freedesktop.DBus.Properties'," 3694 "member='PropertiesChanged'," 3695 "arg0namespace='com.intel.crashdump'"; 3696 } 3697 else if (oemDiagType == OEMDiagnosticType::telemetry) 3698 { 3699 iface = crashdumpTelemetryInterface; 3700 method = "GenerateTelemetryLog"; 3701 taskMatchStr = "type='signal'," 3702 "interface='org.freedesktop.DBus.Properties'," 3703 "member='PropertiesChanged'," 3704 "arg0namespace='com.intel.crashdump'"; 3705 } 3706 else 3707 { 3708 BMCWEB_LOG_ERROR("Unsupported OEMDiagnosticDataType: {}", 3709 oemDiagnosticDataType); 3710 messages::actionParameterValueFormatError( 3711 asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType", 3712 "CollectDiagnosticData"); 3713 return; 3714 } 3715 3716 auto collectCrashdumpCallback = 3717 [asyncResp, payload(task::Payload(req)), 3718 taskMatchStr](const boost::system::error_code& ec, 3719 const std::string&) mutable { 3720 if (ec) 3721 { 3722 if (ec.value() == boost::system::errc::operation_not_supported) 3723 { 3724 messages::resourceInStandby(asyncResp->res); 3725 } 3726 else if (ec.value() == 3727 boost::system::errc::device_or_resource_busy) 3728 { 3729 messages::serviceTemporarilyUnavailable(asyncResp->res, 3730 "60"); 3731 } 3732 else 3733 { 3734 messages::internalError(asyncResp->res); 3735 } 3736 return; 3737 } 3738 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 3739 [](const boost::system::error_code& ec2, sdbusplus::message_t&, 3740 const std::shared_ptr<task::TaskData>& taskData) { 3741 if (!ec2) 3742 { 3743 taskData->messages.emplace_back(messages::taskCompletedOK( 3744 std::to_string(taskData->index))); 3745 taskData->state = "Completed"; 3746 } 3747 return task::completed; 3748 }, 3749 taskMatchStr); 3750 3751 task->startTimer(std::chrono::minutes(5)); 3752 task->populateResp(asyncResp->res); 3753 task->payload.emplace(std::move(payload)); 3754 }; 3755 3756 crow::connections::systemBus->async_method_call( 3757 std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath, 3758 iface, method); 3759 }); 3760 } 3761 3762 /** 3763 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 3764 */ 3765 inline void requestRoutesDBusLogServiceActionsClear(App& app) 3766 { 3767 /** 3768 * Function handles POST method request. 3769 * The Clear Log actions does not require any parameter.The action deletes 3770 * all entries found in the Entries collection for this Log Service. 3771 */ 3772 3773 BMCWEB_ROUTE( 3774 app, 3775 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/") 3776 .privileges(redfish::privileges::postLogService) 3777 .methods(boost::beast::http::verb::post)( 3778 [&app](const crow::Request& req, 3779 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3780 const std::string& systemName) { 3781 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3782 { 3783 return; 3784 } 3785 if constexpr (bmcwebEnableMultiHost) 3786 { 3787 // Option currently returns no systems. TBD 3788 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3789 systemName); 3790 return; 3791 } 3792 if (systemName != "system") 3793 { 3794 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3795 systemName); 3796 return; 3797 } 3798 BMCWEB_LOG_DEBUG("Do delete all entries."); 3799 3800 // Process response from Logging service. 3801 auto respHandler = [asyncResp](const boost::system::error_code& ec) { 3802 BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done"); 3803 if (ec) 3804 { 3805 // TODO Handle for specific error code 3806 BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec); 3807 asyncResp->res.result( 3808 boost::beast::http::status::internal_server_error); 3809 return; 3810 } 3811 3812 asyncResp->res.result(boost::beast::http::status::no_content); 3813 }; 3814 3815 // Make call to Logging service to request Clear Log 3816 crow::connections::systemBus->async_method_call( 3817 respHandler, "xyz.openbmc_project.Logging", 3818 "/xyz/openbmc_project/logging", 3819 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3820 }); 3821 } 3822 3823 /**************************************************** 3824 * Redfish PostCode interfaces 3825 * using DBUS interface: getPostCodesTS 3826 ******************************************************/ 3827 inline void requestRoutesPostCodesLogService(App& app) 3828 { 3829 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/") 3830 .privileges(redfish::privileges::getLogService) 3831 .methods(boost::beast::http::verb::get)( 3832 [&app](const crow::Request& req, 3833 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3834 const std::string& systemName) { 3835 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3836 { 3837 return; 3838 } 3839 if constexpr (bmcwebEnableMultiHost) 3840 { 3841 // Option currently returns no systems. TBD 3842 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3843 systemName); 3844 return; 3845 } 3846 if (systemName != "system") 3847 { 3848 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3849 systemName); 3850 return; 3851 } 3852 asyncResp->res.jsonValue["@odata.id"] = 3853 "/redfish/v1/Systems/system/LogServices/PostCodes"; 3854 asyncResp->res.jsonValue["@odata.type"] = 3855 "#LogService.v1_2_0.LogService"; 3856 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 3857 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 3858 asyncResp->res.jsonValue["Id"] = "PostCodes"; 3859 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 3860 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 3861 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3862 3863 std::pair<std::string, std::string> redfishDateTimeOffset = 3864 redfish::time_utils::getDateTimeOffsetNow(); 3865 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3866 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3867 redfishDateTimeOffset.second; 3868 3869 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 3870 {"target", 3871 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}}; 3872 }); 3873 } 3874 3875 inline void requestRoutesPostCodesClear(App& app) 3876 { 3877 BMCWEB_ROUTE( 3878 app, 3879 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/") 3880 // The following privilege is incorrect; It should be ConfigureManager 3881 //.privileges(redfish::privileges::postLogService) 3882 .privileges({{"ConfigureComponents"}}) 3883 .methods(boost::beast::http::verb::post)( 3884 [&app](const crow::Request& req, 3885 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3886 const std::string& systemName) { 3887 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3888 { 3889 return; 3890 } 3891 if constexpr (bmcwebEnableMultiHost) 3892 { 3893 // Option currently returns no systems. TBD 3894 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3895 systemName); 3896 return; 3897 } 3898 if (systemName != "system") 3899 { 3900 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3901 systemName); 3902 return; 3903 } 3904 BMCWEB_LOG_DEBUG("Do delete all postcodes entries."); 3905 3906 // Make call to post-code service to request clear all 3907 crow::connections::systemBus->async_method_call( 3908 [asyncResp](const boost::system::error_code& ec) { 3909 if (ec) 3910 { 3911 // TODO Handle for specific error code 3912 BMCWEB_LOG_ERROR("doClearPostCodes resp_handler got error {}", 3913 ec); 3914 asyncResp->res.result( 3915 boost::beast::http::status::internal_server_error); 3916 messages::internalError(asyncResp->res); 3917 return; 3918 } 3919 messages::success(asyncResp->res); 3920 }, 3921 "xyz.openbmc_project.State.Boot.PostCode0", 3922 "/xyz/openbmc_project/State/Boot/PostCode0", 3923 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3924 }); 3925 } 3926 3927 /** 3928 * @brief Parse post code ID and get the current value and index value 3929 * eg: postCodeID=B1-2, currentValue=1, index=2 3930 * 3931 * @param[in] postCodeID Post Code ID 3932 * @param[out] currentValue Current value 3933 * @param[out] index Index value 3934 * 3935 * @return bool true if the parsing is successful, false the parsing fails 3936 */ 3937 inline static bool parsePostCode(const std::string& postCodeID, 3938 uint64_t& currentValue, uint16_t& index) 3939 { 3940 std::vector<std::string> split; 3941 bmcweb::split(split, postCodeID, '-'); 3942 if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B') 3943 { 3944 return false; 3945 } 3946 3947 auto start = std::next(split[0].begin()); 3948 auto end = split[0].end(); 3949 auto [ptrIndex, ecIndex] = std::from_chars(&*start, &*end, index); 3950 3951 if (ptrIndex != &*end || ecIndex != std::errc()) 3952 { 3953 return false; 3954 } 3955 3956 start = split[1].begin(); 3957 end = split[1].end(); 3958 3959 auto [ptrValue, ecValue] = std::from_chars(&*start, &*end, currentValue); 3960 3961 return ptrValue == &*end && ecValue == std::errc(); 3962 } 3963 3964 static bool fillPostCodeEntry( 3965 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3966 const boost::container::flat_map< 3967 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 3968 const uint16_t bootIndex, const uint64_t codeIndex = 0, 3969 const uint64_t skip = 0, const uint64_t top = 0) 3970 { 3971 // Get the Message from the MessageRegistry 3972 const registries::Message* message = 3973 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 3974 3975 uint64_t currentCodeIndex = 0; 3976 uint64_t firstCodeTimeUs = 0; 3977 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3978 code : postcode) 3979 { 3980 currentCodeIndex++; 3981 std::string postcodeEntryID = 3982 "B" + std::to_string(bootIndex) + "-" + 3983 std::to_string(currentCodeIndex); // 1 based index in EntryID string 3984 3985 uint64_t usecSinceEpoch = code.first; 3986 uint64_t usTimeOffset = 0; 3987 3988 if (1 == currentCodeIndex) 3989 { // already incremented 3990 firstCodeTimeUs = code.first; 3991 } 3992 else 3993 { 3994 usTimeOffset = code.first - firstCodeTimeUs; 3995 } 3996 3997 // skip if no specific codeIndex is specified and currentCodeIndex does 3998 // not fall between top and skip 3999 if ((codeIndex == 0) && 4000 (currentCodeIndex <= skip || currentCodeIndex > top)) 4001 { 4002 continue; 4003 } 4004 4005 // skip if a specific codeIndex is specified and does not match the 4006 // currentIndex 4007 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 4008 { 4009 // This is done for simplicity. 1st entry is needed to calculate 4010 // time offset. To improve efficiency, one can get to the entry 4011 // directly (possibly with flatmap's nth method) 4012 continue; 4013 } 4014 4015 // currentCodeIndex is within top and skip or equal to specified code 4016 // index 4017 4018 // Get the Created time from the timestamp 4019 std::string entryTimeStr; 4020 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch); 4021 4022 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 4023 std::ostringstream hexCode; 4024 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 4025 << std::get<0>(code.second); 4026 std::ostringstream timeOffsetStr; 4027 // Set Fixed -Point Notation 4028 timeOffsetStr << std::fixed; 4029 // Set precision to 4 digits 4030 timeOffsetStr << std::setprecision(4); 4031 // Add double to stream 4032 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 4033 4034 std::string bootIndexStr = std::to_string(bootIndex); 4035 std::string timeOffsetString = timeOffsetStr.str(); 4036 std::string hexCodeStr = hexCode.str(); 4037 4038 std::array<std::string_view, 3> messageArgs = { 4039 bootIndexStr, timeOffsetString, hexCodeStr}; 4040 4041 std::string msg = 4042 redfish::registries::fillMessageArgs(messageArgs, message->message); 4043 if (msg.empty()) 4044 { 4045 messages::internalError(asyncResp->res); 4046 return false; 4047 } 4048 4049 // Get Severity template from message registry 4050 std::string severity; 4051 if (message != nullptr) 4052 { 4053 severity = message->messageSeverity; 4054 } 4055 4056 // Format entry 4057 nlohmann::json::object_t bmcLogEntry; 4058 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 4059 bmcLogEntry["@odata.id"] = boost::urls::format( 4060 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/{}", 4061 postcodeEntryID); 4062 bmcLogEntry["Name"] = "POST Code Log Entry"; 4063 bmcLogEntry["Id"] = postcodeEntryID; 4064 bmcLogEntry["Message"] = std::move(msg); 4065 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode"; 4066 bmcLogEntry["MessageArgs"] = messageArgs; 4067 bmcLogEntry["EntryType"] = "Event"; 4068 bmcLogEntry["Severity"] = std::move(severity); 4069 bmcLogEntry["Created"] = entryTimeStr; 4070 if (!std::get<std::vector<uint8_t>>(code.second).empty()) 4071 { 4072 bmcLogEntry["AdditionalDataURI"] = 4073 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + 4074 postcodeEntryID + "/attachment"; 4075 } 4076 4077 // codeIndex is only specified when querying single entry, return only 4078 // that entry in this case 4079 if (codeIndex != 0) 4080 { 4081 asyncResp->res.jsonValue.update(bmcLogEntry); 4082 return true; 4083 } 4084 4085 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 4086 logEntryArray.emplace_back(std::move(bmcLogEntry)); 4087 } 4088 4089 // Return value is always false when querying multiple entries 4090 return false; 4091 } 4092 4093 static void 4094 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4095 const std::string& entryId) 4096 { 4097 uint16_t bootIndex = 0; 4098 uint64_t codeIndex = 0; 4099 if (!parsePostCode(entryId, codeIndex, bootIndex)) 4100 { 4101 // Requested ID was not found 4102 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4103 return; 4104 } 4105 4106 if (bootIndex == 0 || codeIndex == 0) 4107 { 4108 // 0 is an invalid index 4109 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4110 return; 4111 } 4112 4113 crow::connections::systemBus->async_method_call( 4114 [asyncResp, entryId, bootIndex, 4115 codeIndex](const boost::system::error_code& ec, 4116 const boost::container::flat_map< 4117 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 4118 postcode) { 4119 if (ec) 4120 { 4121 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 4122 messages::internalError(asyncResp->res); 4123 return; 4124 } 4125 4126 if (postcode.empty()) 4127 { 4128 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4129 return; 4130 } 4131 4132 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 4133 { 4134 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 4135 return; 4136 } 4137 }, 4138 "xyz.openbmc_project.State.Boot.PostCode0", 4139 "/xyz/openbmc_project/State/Boot/PostCode0", 4140 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 4141 bootIndex); 4142 } 4143 4144 static void 4145 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4146 const uint16_t bootIndex, const uint16_t bootCount, 4147 const uint64_t entryCount, size_t skip, size_t top) 4148 { 4149 crow::connections::systemBus->async_method_call( 4150 [asyncResp, bootIndex, bootCount, entryCount, skip, 4151 top](const boost::system::error_code& ec, 4152 const boost::container::flat_map< 4153 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 4154 postcode) { 4155 if (ec) 4156 { 4157 BMCWEB_LOG_DEBUG("DBUS POST CODE PostCode response error"); 4158 messages::internalError(asyncResp->res); 4159 return; 4160 } 4161 4162 uint64_t endCount = entryCount; 4163 if (!postcode.empty()) 4164 { 4165 endCount = entryCount + postcode.size(); 4166 if (skip < endCount && (top + skip) > entryCount) 4167 { 4168 uint64_t thisBootSkip = std::max(static_cast<uint64_t>(skip), 4169 entryCount) - 4170 entryCount; 4171 uint64_t thisBootTop = 4172 std::min(static_cast<uint64_t>(top + skip), endCount) - 4173 entryCount; 4174 4175 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 4176 thisBootSkip, thisBootTop); 4177 } 4178 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 4179 } 4180 4181 // continue to previous bootIndex 4182 if (bootIndex < bootCount) 4183 { 4184 getPostCodeForBoot(asyncResp, static_cast<uint16_t>(bootIndex + 1), 4185 bootCount, endCount, skip, top); 4186 } 4187 else if (skip + top < endCount) 4188 { 4189 asyncResp->res.jsonValue["Members@odata.nextLink"] = 4190 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" + 4191 std::to_string(skip + top); 4192 } 4193 }, 4194 "xyz.openbmc_project.State.Boot.PostCode0", 4195 "/xyz/openbmc_project/State/Boot/PostCode0", 4196 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 4197 bootIndex); 4198 } 4199 4200 static void 4201 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4202 size_t skip, size_t top) 4203 { 4204 uint64_t entryCount = 0; 4205 sdbusplus::asio::getProperty<uint16_t>( 4206 *crow::connections::systemBus, 4207 "xyz.openbmc_project.State.Boot.PostCode0", 4208 "/xyz/openbmc_project/State/Boot/PostCode0", 4209 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 4210 [asyncResp, entryCount, skip, top](const boost::system::error_code& ec, 4211 const uint16_t bootCount) { 4212 if (ec) 4213 { 4214 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 4215 messages::internalError(asyncResp->res); 4216 return; 4217 } 4218 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top); 4219 }); 4220 } 4221 4222 inline void requestRoutesPostCodesEntryCollection(App& app) 4223 { 4224 BMCWEB_ROUTE(app, 4225 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/") 4226 .privileges(redfish::privileges::getLogEntryCollection) 4227 .methods(boost::beast::http::verb::get)( 4228 [&app](const crow::Request& req, 4229 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4230 const std::string& systemName) { 4231 query_param::QueryCapabilities capabilities = { 4232 .canDelegateTop = true, 4233 .canDelegateSkip = true, 4234 }; 4235 query_param::Query delegatedQuery; 4236 if (!redfish::setUpRedfishRouteWithDelegation( 4237 app, req, asyncResp, delegatedQuery, capabilities)) 4238 { 4239 return; 4240 } 4241 if constexpr (bmcwebEnableMultiHost) 4242 { 4243 // Option currently returns no systems. TBD 4244 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4245 systemName); 4246 return; 4247 } 4248 4249 if (systemName != "system") 4250 { 4251 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4252 systemName); 4253 return; 4254 } 4255 asyncResp->res.jsonValue["@odata.type"] = 4256 "#LogEntryCollection.LogEntryCollection"; 4257 asyncResp->res.jsonValue["@odata.id"] = 4258 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 4259 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 4260 asyncResp->res.jsonValue["Description"] = 4261 "Collection of POST Code Log Entries"; 4262 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 4263 asyncResp->res.jsonValue["Members@odata.count"] = 0; 4264 size_t skip = delegatedQuery.skip.value_or(0); 4265 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 4266 getCurrentBootNumber(asyncResp, skip, top); 4267 }); 4268 } 4269 4270 inline void requestRoutesPostCodesEntryAdditionalData(App& app) 4271 { 4272 BMCWEB_ROUTE( 4273 app, 4274 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/") 4275 .privileges(redfish::privileges::getLogEntry) 4276 .methods(boost::beast::http::verb::get)( 4277 [&app](const crow::Request& req, 4278 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4279 const std::string& systemName, 4280 const std::string& postCodeID) { 4281 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4282 { 4283 return; 4284 } 4285 if (!http_helpers::isContentTypeAllowed( 4286 req.getHeaderValue("Accept"), 4287 http_helpers::ContentType::OctetStream, true)) 4288 { 4289 asyncResp->res.result(boost::beast::http::status::bad_request); 4290 return; 4291 } 4292 if constexpr (bmcwebEnableMultiHost) 4293 { 4294 // Option currently returns no systems. TBD 4295 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4296 systemName); 4297 return; 4298 } 4299 if (systemName != "system") 4300 { 4301 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4302 systemName); 4303 return; 4304 } 4305 4306 uint64_t currentValue = 0; 4307 uint16_t index = 0; 4308 if (!parsePostCode(postCodeID, currentValue, index)) 4309 { 4310 messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); 4311 return; 4312 } 4313 4314 crow::connections::systemBus->async_method_call( 4315 [asyncResp, postCodeID, currentValue]( 4316 const boost::system::error_code& ec, 4317 const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>& 4318 postcodes) { 4319 if (ec.value() == EBADR) 4320 { 4321 messages::resourceNotFound(asyncResp->res, "LogEntry", 4322 postCodeID); 4323 return; 4324 } 4325 if (ec) 4326 { 4327 BMCWEB_LOG_DEBUG("DBUS response error {}", ec); 4328 messages::internalError(asyncResp->res); 4329 return; 4330 } 4331 4332 size_t value = static_cast<size_t>(currentValue) - 1; 4333 if (value == std::string::npos || postcodes.size() < currentValue) 4334 { 4335 BMCWEB_LOG_WARNING("Wrong currentValue value"); 4336 messages::resourceNotFound(asyncResp->res, "LogEntry", 4337 postCodeID); 4338 return; 4339 } 4340 4341 const auto& [tID, c] = postcodes[value]; 4342 if (c.empty()) 4343 { 4344 BMCWEB_LOG_WARNING("No found post code data"); 4345 messages::resourceNotFound(asyncResp->res, "LogEntry", 4346 postCodeID); 4347 return; 4348 } 4349 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 4350 const char* d = reinterpret_cast<const char*>(c.data()); 4351 std::string_view strData(d, c.size()); 4352 4353 asyncResp->res.addHeader(boost::beast::http::field::content_type, 4354 "application/octet-stream"); 4355 asyncResp->res.addHeader( 4356 boost::beast::http::field::content_transfer_encoding, "Base64"); 4357 asyncResp->res.write(crow::utility::base64encode(strData)); 4358 }, 4359 "xyz.openbmc_project.State.Boot.PostCode0", 4360 "/xyz/openbmc_project/State/Boot/PostCode0", 4361 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index); 4362 }); 4363 } 4364 4365 inline void requestRoutesPostCodesEntry(App& app) 4366 { 4367 BMCWEB_ROUTE( 4368 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/") 4369 .privileges(redfish::privileges::getLogEntry) 4370 .methods(boost::beast::http::verb::get)( 4371 [&app](const crow::Request& req, 4372 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4373 const std::string& systemName, const std::string& targetID) { 4374 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4375 { 4376 return; 4377 } 4378 if constexpr (bmcwebEnableMultiHost) 4379 { 4380 // Option currently returns no systems. TBD 4381 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4382 systemName); 4383 return; 4384 } 4385 if (systemName != "system") 4386 { 4387 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4388 systemName); 4389 return; 4390 } 4391 4392 getPostCodeForEntry(asyncResp, targetID); 4393 }); 4394 } 4395 4396 } // namespace redfish 4397