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