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