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