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