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