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