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 if (logEntryFields.empty()) 1294 { 1295 return LogParseError::parseFailed; 1296 } 1297 std::string& messageID = logEntryFields[0]; 1298 1299 // Get the Message from the MessageRegistry 1300 const registries::Message* message = registries::getMessage(messageID); 1301 1302 if (message == nullptr) 1303 { 1304 BMCWEB_LOG_WARNING << "Log entry not found in registry: " << logEntry; 1305 return LogParseError::messageIdNotInRegistry; 1306 } 1307 1308 std::string msg = message->message; 1309 1310 // Get the MessageArgs from the log if there are any 1311 std::span<std::string> messageArgs; 1312 if (logEntryFields.size() > 1) 1313 { 1314 std::string& messageArgsStart = logEntryFields[1]; 1315 // If the first string is empty, assume there are no MessageArgs 1316 std::size_t messageArgsSize = 0; 1317 if (!messageArgsStart.empty()) 1318 { 1319 messageArgsSize = logEntryFields.size() - 1; 1320 } 1321 1322 messageArgs = {&messageArgsStart, messageArgsSize}; 1323 1324 // Fill the MessageArgs into the Message 1325 int i = 0; 1326 for (const std::string& messageArg : messageArgs) 1327 { 1328 std::string argStr = "%" + std::to_string(++i); 1329 size_t argPos = msg.find(argStr); 1330 if (argPos != std::string::npos) 1331 { 1332 msg.replace(argPos, argStr.length(), messageArg); 1333 } 1334 } 1335 } 1336 1337 // Get the Created time from the timestamp. The log timestamp is in RFC3339 1338 // format which matches the Redfish format except for the fractional seconds 1339 // between the '.' and the '+', so just remove them. 1340 std::size_t dot = timestamp.find_first_of('.'); 1341 std::size_t plus = timestamp.find_first_of('+'); 1342 if (dot != std::string::npos && plus != std::string::npos) 1343 { 1344 timestamp.erase(dot, plus - dot); 1345 } 1346 1347 // Fill in the log entry with the gathered data 1348 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 1349 logEntryJson["@odata.id"] = boost::urls::format( 1350 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/{}", 1351 logEntryID); 1352 logEntryJson["Name"] = "System Event Log Entry"; 1353 logEntryJson["Id"] = logEntryID; 1354 logEntryJson["Message"] = std::move(msg); 1355 logEntryJson["MessageId"] = std::move(messageID); 1356 logEntryJson["MessageArgs"] = messageArgs; 1357 logEntryJson["EntryType"] = "Event"; 1358 logEntryJson["Severity"] = message->messageSeverity; 1359 logEntryJson["Created"] = std::move(timestamp); 1360 return LogParseError::success; 1361 } 1362 1363 inline void requestRoutesJournalEventLogEntryCollection(App& app) 1364 { 1365 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/") 1366 .privileges(redfish::privileges::getLogEntryCollection) 1367 .methods(boost::beast::http::verb::get)( 1368 [&app](const crow::Request& req, 1369 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1370 const std::string& systemName) { 1371 query_param::QueryCapabilities capabilities = { 1372 .canDelegateTop = true, 1373 .canDelegateSkip = true, 1374 }; 1375 query_param::Query delegatedQuery; 1376 if (!redfish::setUpRedfishRouteWithDelegation( 1377 app, req, asyncResp, delegatedQuery, capabilities)) 1378 { 1379 return; 1380 } 1381 if (systemName != "system") 1382 { 1383 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1384 systemName); 1385 return; 1386 } 1387 1388 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 1389 size_t skip = delegatedQuery.skip.value_or(0); 1390 1391 // Collections don't include the static data added by SubRoute 1392 // because it has a duplicate entry for members 1393 asyncResp->res.jsonValue["@odata.type"] = 1394 "#LogEntryCollection.LogEntryCollection"; 1395 asyncResp->res.jsonValue["@odata.id"] = 1396 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 1397 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1398 asyncResp->res.jsonValue["Description"] = 1399 "Collection of System Event Log Entries"; 1400 1401 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 1402 logEntryArray = nlohmann::json::array(); 1403 // Go through the log files and create a unique ID for each 1404 // entry 1405 std::vector<std::filesystem::path> redfishLogFiles; 1406 getRedfishLogFiles(redfishLogFiles); 1407 uint64_t entryCount = 0; 1408 std::string logEntry; 1409 1410 // Oldest logs are in the last file, so start there and loop 1411 // backwards 1412 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 1413 it++) 1414 { 1415 std::ifstream logStream(*it); 1416 if (!logStream.is_open()) 1417 { 1418 continue; 1419 } 1420 1421 // Reset the unique ID on the first entry 1422 bool firstEntry = true; 1423 while (std::getline(logStream, logEntry)) 1424 { 1425 std::string idStr; 1426 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1427 { 1428 continue; 1429 } 1430 firstEntry = false; 1431 1432 nlohmann::json::object_t bmcLogEntry; 1433 LogParseError status = fillEventLogEntryJson(idStr, logEntry, 1434 bmcLogEntry); 1435 if (status == LogParseError::messageIdNotInRegistry) 1436 { 1437 continue; 1438 } 1439 if (status != LogParseError::success) 1440 { 1441 messages::internalError(asyncResp->res); 1442 return; 1443 } 1444 1445 entryCount++; 1446 // Handle paging using skip (number of entries to skip from the 1447 // start) and top (number of entries to display) 1448 if (entryCount <= skip || entryCount > skip + top) 1449 { 1450 continue; 1451 } 1452 1453 logEntryArray.emplace_back(std::move(bmcLogEntry)); 1454 } 1455 } 1456 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1457 if (skip + top < entryCount) 1458 { 1459 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1460 "/redfish/v1/Systems/system/LogServices/EventLog/Entries?$skip=" + 1461 std::to_string(skip + top); 1462 } 1463 }); 1464 } 1465 1466 inline void requestRoutesJournalEventLogEntry(App& app) 1467 { 1468 BMCWEB_ROUTE( 1469 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 1470 .privileges(redfish::privileges::getLogEntry) 1471 .methods(boost::beast::http::verb::get)( 1472 [&app](const crow::Request& req, 1473 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1474 const std::string& systemName, const std::string& param) { 1475 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1476 { 1477 return; 1478 } 1479 1480 if (systemName != "system") 1481 { 1482 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1483 systemName); 1484 return; 1485 } 1486 1487 const std::string& targetID = param; 1488 1489 // Go through the log files and check the unique ID for each 1490 // entry to find the target entry 1491 std::vector<std::filesystem::path> redfishLogFiles; 1492 getRedfishLogFiles(redfishLogFiles); 1493 std::string logEntry; 1494 1495 // Oldest logs are in the last file, so start there and loop 1496 // backwards 1497 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 1498 it++) 1499 { 1500 std::ifstream logStream(*it); 1501 if (!logStream.is_open()) 1502 { 1503 continue; 1504 } 1505 1506 // Reset the unique ID on the first entry 1507 bool firstEntry = true; 1508 while (std::getline(logStream, logEntry)) 1509 { 1510 std::string idStr; 1511 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1512 { 1513 continue; 1514 } 1515 firstEntry = false; 1516 1517 if (idStr == targetID) 1518 { 1519 nlohmann::json::object_t bmcLogEntry; 1520 LogParseError status = 1521 fillEventLogEntryJson(idStr, logEntry, bmcLogEntry); 1522 if (status != LogParseError::success) 1523 { 1524 messages::internalError(asyncResp->res); 1525 return; 1526 } 1527 asyncResp->res.jsonValue.update(bmcLogEntry); 1528 return; 1529 } 1530 } 1531 } 1532 // Requested ID was not found 1533 messages::resourceNotFound(asyncResp->res, "LogEntry", targetID); 1534 }); 1535 } 1536 1537 inline void requestRoutesDBusEventLogEntryCollection(App& app) 1538 { 1539 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/") 1540 .privileges(redfish::privileges::getLogEntryCollection) 1541 .methods(boost::beast::http::verb::get)( 1542 [&app](const crow::Request& req, 1543 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1544 const std::string& systemName) { 1545 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1546 { 1547 return; 1548 } 1549 if (systemName != "system") 1550 { 1551 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1552 systemName); 1553 return; 1554 } 1555 1556 // Collections don't include the static data added by SubRoute 1557 // because it has a duplicate entry for members 1558 asyncResp->res.jsonValue["@odata.type"] = 1559 "#LogEntryCollection.LogEntryCollection"; 1560 asyncResp->res.jsonValue["@odata.id"] = 1561 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 1562 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1563 asyncResp->res.jsonValue["Description"] = 1564 "Collection of System Event Log Entries"; 1565 1566 // DBus implementation of EventLog/Entries 1567 // Make call to Logging Service to find all log entry objects 1568 sdbusplus::message::object_path path("/xyz/openbmc_project/logging"); 1569 dbus::utility::getManagedObjects( 1570 "xyz.openbmc_project.Logging", path, 1571 [asyncResp](const boost::system::error_code& ec, 1572 const dbus::utility::ManagedObjectType& resp) { 1573 if (ec) 1574 { 1575 // TODO Handle for specific error code 1576 BMCWEB_LOG_ERROR 1577 << "getLogEntriesIfaceData resp_handler got error " << ec; 1578 messages::internalError(asyncResp->res); 1579 return; 1580 } 1581 nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"]; 1582 entriesArray = nlohmann::json::array(); 1583 for (const auto& objectPath : resp) 1584 { 1585 const uint32_t* id = nullptr; 1586 const uint64_t* timestamp = nullptr; 1587 const uint64_t* updateTimestamp = nullptr; 1588 const std::string* severity = nullptr; 1589 const std::string* message = nullptr; 1590 const std::string* filePath = nullptr; 1591 const std::string* resolution = nullptr; 1592 bool resolved = false; 1593 const std::string* notify = nullptr; 1594 1595 for (const auto& interfaceMap : objectPath.second) 1596 { 1597 if (interfaceMap.first == 1598 "xyz.openbmc_project.Logging.Entry") 1599 { 1600 for (const auto& propertyMap : interfaceMap.second) 1601 { 1602 if (propertyMap.first == "Id") 1603 { 1604 id = std::get_if<uint32_t>(&propertyMap.second); 1605 } 1606 else if (propertyMap.first == "Timestamp") 1607 { 1608 timestamp = 1609 std::get_if<uint64_t>(&propertyMap.second); 1610 } 1611 else if (propertyMap.first == "UpdateTimestamp") 1612 { 1613 updateTimestamp = 1614 std::get_if<uint64_t>(&propertyMap.second); 1615 } 1616 else if (propertyMap.first == "Severity") 1617 { 1618 severity = std::get_if<std::string>( 1619 &propertyMap.second); 1620 } 1621 else if (propertyMap.first == "Resolution") 1622 { 1623 resolution = std::get_if<std::string>( 1624 &propertyMap.second); 1625 } 1626 else if (propertyMap.first == "Message") 1627 { 1628 message = std::get_if<std::string>( 1629 &propertyMap.second); 1630 } 1631 else if (propertyMap.first == "Resolved") 1632 { 1633 const bool* resolveptr = 1634 std::get_if<bool>(&propertyMap.second); 1635 if (resolveptr == nullptr) 1636 { 1637 messages::internalError(asyncResp->res); 1638 return; 1639 } 1640 resolved = *resolveptr; 1641 } 1642 else if (propertyMap.first == 1643 "ServiceProviderNotify") 1644 { 1645 notify = std::get_if<std::string>( 1646 &propertyMap.second); 1647 if (notify == nullptr) 1648 { 1649 messages::internalError(asyncResp->res); 1650 return; 1651 } 1652 } 1653 } 1654 if (id == nullptr || message == nullptr || 1655 severity == nullptr) 1656 { 1657 messages::internalError(asyncResp->res); 1658 return; 1659 } 1660 } 1661 else if (interfaceMap.first == 1662 "xyz.openbmc_project.Common.FilePath") 1663 { 1664 for (const auto& propertyMap : interfaceMap.second) 1665 { 1666 if (propertyMap.first == "Path") 1667 { 1668 filePath = std::get_if<std::string>( 1669 &propertyMap.second); 1670 } 1671 } 1672 } 1673 } 1674 // Object path without the 1675 // xyz.openbmc_project.Logging.Entry interface, ignore 1676 // and continue. 1677 if (id == nullptr || message == nullptr || 1678 severity == nullptr || timestamp == nullptr || 1679 updateTimestamp == nullptr) 1680 { 1681 continue; 1682 } 1683 entriesArray.push_back({}); 1684 nlohmann::json& thisEntry = entriesArray.back(); 1685 thisEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 1686 thisEntry["@odata.id"] = boost::urls::format( 1687 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/{}", 1688 std::to_string(*id)); 1689 thisEntry["Name"] = "System Event Log Entry"; 1690 thisEntry["Id"] = std::to_string(*id); 1691 thisEntry["Message"] = *message; 1692 thisEntry["Resolved"] = resolved; 1693 if ((resolution != nullptr) && (!(*resolution).empty())) 1694 { 1695 thisEntry["Resolution"] = *resolution; 1696 } 1697 std::optional<bool> notifyAction = 1698 getProviderNotifyAction(*notify); 1699 if (notifyAction) 1700 { 1701 thisEntry["ServiceProviderNotified"] = *notifyAction; 1702 } 1703 thisEntry["EntryType"] = "Event"; 1704 thisEntry["Severity"] = 1705 translateSeverityDbusToRedfish(*severity); 1706 thisEntry["Created"] = 1707 redfish::time_utils::getDateTimeUintMs(*timestamp); 1708 thisEntry["Modified"] = 1709 redfish::time_utils::getDateTimeUintMs(*updateTimestamp); 1710 if (filePath != nullptr) 1711 { 1712 thisEntry["AdditionalDataURI"] = 1713 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1714 std::to_string(*id) + "/attachment"; 1715 } 1716 } 1717 std::sort( 1718 entriesArray.begin(), entriesArray.end(), 1719 [](const nlohmann::json& left, const nlohmann::json& right) { 1720 return (left["Id"] <= right["Id"]); 1721 }); 1722 asyncResp->res.jsonValue["Members@odata.count"] = 1723 entriesArray.size(); 1724 }); 1725 }); 1726 } 1727 1728 inline void requestRoutesDBusEventLogEntry(App& app) 1729 { 1730 BMCWEB_ROUTE( 1731 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 1732 .privileges(redfish::privileges::getLogEntry) 1733 .methods(boost::beast::http::verb::get)( 1734 [&app](const crow::Request& req, 1735 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1736 const std::string& systemName, const std::string& param) { 1737 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1738 { 1739 return; 1740 } 1741 if (systemName != "system") 1742 { 1743 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1744 systemName); 1745 return; 1746 } 1747 1748 std::string entryID = param; 1749 dbus::utility::escapePathForDbus(entryID); 1750 1751 // DBus implementation of EventLog/Entries 1752 // Make call to Logging Service to find all log entry objects 1753 sdbusplus::asio::getAllProperties( 1754 *crow::connections::systemBus, "xyz.openbmc_project.Logging", 1755 "/xyz/openbmc_project/logging/entry/" + entryID, "", 1756 [asyncResp, entryID](const boost::system::error_code& ec, 1757 const dbus::utility::DBusPropertiesMap& resp) { 1758 if (ec.value() == EBADR) 1759 { 1760 messages::resourceNotFound(asyncResp->res, "EventLogEntry", 1761 entryID); 1762 return; 1763 } 1764 if (ec) 1765 { 1766 BMCWEB_LOG_ERROR 1767 << "EventLogEntry (DBus) resp_handler got error " << ec; 1768 messages::internalError(asyncResp->res); 1769 return; 1770 } 1771 const uint32_t* id = nullptr; 1772 const uint64_t* timestamp = nullptr; 1773 const uint64_t* updateTimestamp = nullptr; 1774 const std::string* severity = nullptr; 1775 const std::string* message = nullptr; 1776 const std::string* filePath = nullptr; 1777 const std::string* resolution = nullptr; 1778 bool resolved = false; 1779 const std::string* notify = nullptr; 1780 1781 const bool success = sdbusplus::unpackPropertiesNoThrow( 1782 dbus_utils::UnpackErrorPrinter(), resp, "Id", id, "Timestamp", 1783 timestamp, "UpdateTimestamp", updateTimestamp, "Severity", 1784 severity, "Message", message, "Resolved", resolved, 1785 "Resolution", resolution, "Path", filePath, 1786 "ServiceProviderNotify", notify); 1787 1788 if (!success) 1789 { 1790 messages::internalError(asyncResp->res); 1791 return; 1792 } 1793 1794 if (id == nullptr || message == nullptr || severity == nullptr || 1795 timestamp == nullptr || updateTimestamp == nullptr || 1796 notify == nullptr) 1797 { 1798 messages::internalError(asyncResp->res); 1799 return; 1800 } 1801 1802 asyncResp->res.jsonValue["@odata.type"] = 1803 "#LogEntry.v1_9_0.LogEntry"; 1804 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1805 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/{}", 1806 std::to_string(*id)); 1807 asyncResp->res.jsonValue["Name"] = "System Event Log Entry"; 1808 asyncResp->res.jsonValue["Id"] = std::to_string(*id); 1809 asyncResp->res.jsonValue["Message"] = *message; 1810 asyncResp->res.jsonValue["Resolved"] = resolved; 1811 std::optional<bool> notifyAction = getProviderNotifyAction(*notify); 1812 if (notifyAction) 1813 { 1814 asyncResp->res.jsonValue["ServiceProviderNotified"] = 1815 *notifyAction; 1816 } 1817 if ((resolution != nullptr) && (!(*resolution).empty())) 1818 { 1819 asyncResp->res.jsonValue["Resolution"] = *resolution; 1820 } 1821 asyncResp->res.jsonValue["EntryType"] = "Event"; 1822 asyncResp->res.jsonValue["Severity"] = 1823 translateSeverityDbusToRedfish(*severity); 1824 asyncResp->res.jsonValue["Created"] = 1825 redfish::time_utils::getDateTimeUintMs(*timestamp); 1826 asyncResp->res.jsonValue["Modified"] = 1827 redfish::time_utils::getDateTimeUintMs(*updateTimestamp); 1828 if (filePath != nullptr) 1829 { 1830 asyncResp->res.jsonValue["AdditionalDataURI"] = 1831 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1832 std::to_string(*id) + "/attachment"; 1833 } 1834 }); 1835 }); 1836 1837 BMCWEB_ROUTE( 1838 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 1839 .privileges(redfish::privileges::patchLogEntry) 1840 .methods(boost::beast::http::verb::patch)( 1841 [&app](const crow::Request& req, 1842 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1843 const std::string& systemName, const std::string& entryId) { 1844 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1845 { 1846 return; 1847 } 1848 if (systemName != "system") 1849 { 1850 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1851 systemName); 1852 return; 1853 } 1854 std::optional<bool> resolved; 1855 1856 if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", 1857 resolved)) 1858 { 1859 return; 1860 } 1861 BMCWEB_LOG_DEBUG << "Set Resolved"; 1862 1863 crow::connections::systemBus->async_method_call( 1864 [asyncResp, entryId](const boost::system::error_code& ec) { 1865 if (ec) 1866 { 1867 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1868 messages::internalError(asyncResp->res); 1869 return; 1870 } 1871 }, 1872 "xyz.openbmc_project.Logging", 1873 "/xyz/openbmc_project/logging/entry/" + entryId, 1874 "org.freedesktop.DBus.Properties", "Set", 1875 "xyz.openbmc_project.Logging.Entry", "Resolved", 1876 dbus::utility::DbusVariantType(*resolved)); 1877 }); 1878 1879 BMCWEB_ROUTE( 1880 app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/") 1881 .privileges(redfish::privileges::deleteLogEntry) 1882 1883 .methods(boost::beast::http::verb::delete_)( 1884 [&app](const crow::Request& req, 1885 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1886 const std::string& systemName, const std::string& param) { 1887 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1888 { 1889 return; 1890 } 1891 if (systemName != "system") 1892 { 1893 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1894 systemName); 1895 return; 1896 } 1897 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1898 1899 std::string entryID = param; 1900 1901 dbus::utility::escapePathForDbus(entryID); 1902 1903 // Process response from Logging service. 1904 auto respHandler = 1905 [asyncResp, entryID](const boost::system::error_code& ec) { 1906 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1907 if (ec) 1908 { 1909 if (ec.value() == EBADR) 1910 { 1911 messages::resourceNotFound(asyncResp->res, "LogEntry", 1912 entryID); 1913 return; 1914 } 1915 // TODO Handle for specific error code 1916 BMCWEB_LOG_ERROR 1917 << "EventLogEntry (DBus) doDelete respHandler got error " 1918 << ec; 1919 asyncResp->res.result( 1920 boost::beast::http::status::internal_server_error); 1921 return; 1922 } 1923 1924 asyncResp->res.result(boost::beast::http::status::ok); 1925 }; 1926 1927 // Make call to Logging service to request Delete Log 1928 crow::connections::systemBus->async_method_call( 1929 respHandler, "xyz.openbmc_project.Logging", 1930 "/xyz/openbmc_project/logging/entry/" + entryID, 1931 "xyz.openbmc_project.Object.Delete", "Delete"); 1932 }); 1933 } 1934 1935 inline void requestRoutesDBusEventLogEntryDownload(App& app) 1936 { 1937 BMCWEB_ROUTE( 1938 app, 1939 "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment") 1940 .privileges(redfish::privileges::getLogEntry) 1941 .methods(boost::beast::http::verb::get)( 1942 [&app](const crow::Request& req, 1943 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1944 const std::string& systemName, const std::string& param) { 1945 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1946 { 1947 return; 1948 } 1949 if (!http_helpers::isContentTypeAllowed( 1950 req.getHeaderValue("Accept"), 1951 http_helpers::ContentType::OctetStream, true)) 1952 { 1953 asyncResp->res.result(boost::beast::http::status::bad_request); 1954 return; 1955 } 1956 if (systemName != "system") 1957 { 1958 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 1959 systemName); 1960 return; 1961 } 1962 1963 std::string entryID = param; 1964 dbus::utility::escapePathForDbus(entryID); 1965 1966 crow::connections::systemBus->async_method_call( 1967 [asyncResp, entryID](const boost::system::error_code& ec, 1968 const sdbusplus::message::unix_fd& unixfd) { 1969 if (ec.value() == EBADR) 1970 { 1971 messages::resourceNotFound(asyncResp->res, "EventLogAttachment", 1972 entryID); 1973 return; 1974 } 1975 if (ec) 1976 { 1977 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1978 messages::internalError(asyncResp->res); 1979 return; 1980 } 1981 1982 int fd = -1; 1983 fd = dup(unixfd); 1984 if (fd == -1) 1985 { 1986 messages::internalError(asyncResp->res); 1987 return; 1988 } 1989 1990 long long int size = lseek(fd, 0, SEEK_END); 1991 if (size == -1) 1992 { 1993 messages::internalError(asyncResp->res); 1994 return; 1995 } 1996 1997 // Arbitrary max size of 64kb 1998 constexpr int maxFileSize = 65536; 1999 if (size > maxFileSize) 2000 { 2001 BMCWEB_LOG_ERROR << "File size exceeds maximum allowed size of " 2002 << maxFileSize; 2003 messages::internalError(asyncResp->res); 2004 return; 2005 } 2006 std::vector<char> data(static_cast<size_t>(size)); 2007 long long int rc = lseek(fd, 0, SEEK_SET); 2008 if (rc == -1) 2009 { 2010 messages::internalError(asyncResp->res); 2011 return; 2012 } 2013 rc = read(fd, data.data(), data.size()); 2014 if ((rc == -1) || (rc != size)) 2015 { 2016 messages::internalError(asyncResp->res); 2017 return; 2018 } 2019 close(fd); 2020 2021 std::string_view strData(data.data(), data.size()); 2022 std::string output = crow::utility::base64encode(strData); 2023 2024 asyncResp->res.addHeader(boost::beast::http::field::content_type, 2025 "application/octet-stream"); 2026 asyncResp->res.addHeader( 2027 boost::beast::http::field::content_transfer_encoding, "Base64"); 2028 asyncResp->res.body() = std::move(output); 2029 }, 2030 "xyz.openbmc_project.Logging", 2031 "/xyz/openbmc_project/logging/entry/" + entryID, 2032 "xyz.openbmc_project.Logging.Entry", "GetEntry"); 2033 }); 2034 } 2035 2036 constexpr const char* hostLoggerFolderPath = "/var/log/console"; 2037 2038 inline bool 2039 getHostLoggerFiles(const std::string& hostLoggerFilePath, 2040 std::vector<std::filesystem::path>& hostLoggerFiles) 2041 { 2042 std::error_code ec; 2043 std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec); 2044 if (ec) 2045 { 2046 BMCWEB_LOG_ERROR << ec.message(); 2047 return false; 2048 } 2049 for (const std::filesystem::directory_entry& it : logPath) 2050 { 2051 std::string filename = it.path().filename(); 2052 // Prefix of each log files is "log". Find the file and save the 2053 // path 2054 if (filename.starts_with("log")) 2055 { 2056 hostLoggerFiles.emplace_back(it.path()); 2057 } 2058 } 2059 // As the log files rotate, they are appended with a ".#" that is higher for 2060 // the older logs. Since we start from oldest logs, sort the name in 2061 // descending order. 2062 std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(), 2063 AlphanumLess<std::string>()); 2064 2065 return true; 2066 } 2067 2068 inline bool getHostLoggerEntries( 2069 const std::vector<std::filesystem::path>& hostLoggerFiles, uint64_t skip, 2070 uint64_t top, std::vector<std::string>& logEntries, size_t& logCount) 2071 { 2072 GzFileReader logFile; 2073 2074 // Go though all log files and expose host logs. 2075 for (const std::filesystem::path& it : hostLoggerFiles) 2076 { 2077 if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount)) 2078 { 2079 BMCWEB_LOG_ERROR << "fail to expose host logs"; 2080 return false; 2081 } 2082 } 2083 // Get lastMessage from constructor by getter 2084 std::string lastMessage = logFile.getLastMessage(); 2085 if (!lastMessage.empty()) 2086 { 2087 logCount++; 2088 if (logCount > skip && logCount <= (skip + top)) 2089 { 2090 logEntries.push_back(lastMessage); 2091 } 2092 } 2093 return true; 2094 } 2095 2096 inline void fillHostLoggerEntryJson(const std::string& logEntryID, 2097 const std::string& msg, 2098 nlohmann::json::object_t& logEntryJson) 2099 { 2100 // Fill in the log entry with the gathered data. 2101 logEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 2102 logEntryJson["@odata.id"] = boost::urls::format( 2103 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/{}", 2104 logEntryID); 2105 logEntryJson["Name"] = "Host Logger Entry"; 2106 logEntryJson["Id"] = logEntryID; 2107 logEntryJson["Message"] = msg; 2108 logEntryJson["EntryType"] = "Oem"; 2109 logEntryJson["Severity"] = "OK"; 2110 logEntryJson["OemRecordFormat"] = "Host Logger Entry"; 2111 } 2112 2113 inline void requestRoutesSystemHostLogger(App& app) 2114 { 2115 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/") 2116 .privileges(redfish::privileges::getLogService) 2117 .methods(boost::beast::http::verb::get)( 2118 [&app](const crow::Request& req, 2119 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2120 const std::string& systemName) { 2121 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2122 { 2123 return; 2124 } 2125 if (systemName != "system") 2126 { 2127 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2128 systemName); 2129 return; 2130 } 2131 asyncResp->res.jsonValue["@odata.id"] = 2132 "/redfish/v1/Systems/system/LogServices/HostLogger"; 2133 asyncResp->res.jsonValue["@odata.type"] = 2134 "#LogService.v1_1_0.LogService"; 2135 asyncResp->res.jsonValue["Name"] = "Host Logger Service"; 2136 asyncResp->res.jsonValue["Description"] = "Host Logger Service"; 2137 asyncResp->res.jsonValue["Id"] = "HostLogger"; 2138 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 2139 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries"; 2140 }); 2141 } 2142 2143 inline void requestRoutesSystemHostLoggerCollection(App& app) 2144 { 2145 BMCWEB_ROUTE(app, 2146 "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/") 2147 .privileges(redfish::privileges::getLogEntry) 2148 .methods(boost::beast::http::verb::get)( 2149 [&app](const crow::Request& req, 2150 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2151 const std::string& systemName) { 2152 query_param::QueryCapabilities capabilities = { 2153 .canDelegateTop = true, 2154 .canDelegateSkip = true, 2155 }; 2156 query_param::Query delegatedQuery; 2157 if (!redfish::setUpRedfishRouteWithDelegation( 2158 app, req, asyncResp, delegatedQuery, capabilities)) 2159 { 2160 return; 2161 } 2162 if (systemName != "system") 2163 { 2164 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2165 systemName); 2166 return; 2167 } 2168 asyncResp->res.jsonValue["@odata.id"] = 2169 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries"; 2170 asyncResp->res.jsonValue["@odata.type"] = 2171 "#LogEntryCollection.LogEntryCollection"; 2172 asyncResp->res.jsonValue["Name"] = "HostLogger Entries"; 2173 asyncResp->res.jsonValue["Description"] = 2174 "Collection of HostLogger Entries"; 2175 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2176 logEntryArray = nlohmann::json::array(); 2177 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2178 2179 std::vector<std::filesystem::path> hostLoggerFiles; 2180 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2181 { 2182 BMCWEB_LOG_ERROR << "fail to get host log file path"; 2183 return; 2184 } 2185 // If we weren't provided top and skip limits, use the defaults. 2186 size_t skip = delegatedQuery.skip.value_or(0); 2187 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 2188 size_t logCount = 0; 2189 // This vector only store the entries we want to expose that 2190 // control by skip and top. 2191 std::vector<std::string> logEntries; 2192 if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, 2193 logCount)) 2194 { 2195 messages::internalError(asyncResp->res); 2196 return; 2197 } 2198 // If vector is empty, that means skip value larger than total 2199 // log count 2200 if (logEntries.empty()) 2201 { 2202 asyncResp->res.jsonValue["Members@odata.count"] = logCount; 2203 return; 2204 } 2205 if (!logEntries.empty()) 2206 { 2207 for (size_t i = 0; i < logEntries.size(); i++) 2208 { 2209 nlohmann::json::object_t hostLogEntry; 2210 fillHostLoggerEntryJson(std::to_string(skip + i), logEntries[i], 2211 hostLogEntry); 2212 logEntryArray.emplace_back(std::move(hostLogEntry)); 2213 } 2214 2215 asyncResp->res.jsonValue["Members@odata.count"] = logCount; 2216 if (skip + top < logCount) 2217 { 2218 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2219 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries?$skip=" + 2220 std::to_string(skip + top); 2221 } 2222 } 2223 }); 2224 } 2225 2226 inline void requestRoutesSystemHostLoggerLogEntry(App& app) 2227 { 2228 BMCWEB_ROUTE( 2229 app, "/redfish/v1/Systems/<str>/LogServices/HostLogger/Entries/<str>/") 2230 .privileges(redfish::privileges::getLogEntry) 2231 .methods(boost::beast::http::verb::get)( 2232 [&app](const crow::Request& req, 2233 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2234 const std::string& systemName, const std::string& param) { 2235 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2236 { 2237 return; 2238 } 2239 if (systemName != "system") 2240 { 2241 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 2242 systemName); 2243 return; 2244 } 2245 const std::string& targetID = param; 2246 2247 uint64_t idInt = 0; 2248 2249 auto [ptr, ec] = std::from_chars(&*targetID.begin(), &*targetID.end(), 2250 idInt); 2251 if (ec == std::errc::invalid_argument || 2252 ec == std::errc::result_out_of_range) 2253 { 2254 messages::resourceNotFound(asyncResp->res, "LogEntry", param); 2255 return; 2256 } 2257 2258 std::vector<std::filesystem::path> hostLoggerFiles; 2259 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2260 { 2261 BMCWEB_LOG_ERROR << "fail to get host log file path"; 2262 return; 2263 } 2264 2265 size_t logCount = 0; 2266 size_t top = 1; 2267 std::vector<std::string> logEntries; 2268 // We can get specific entry by skip and top. For example, if we 2269 // want to get nth entry, we can set skip = n-1 and top = 1 to 2270 // get that entry 2271 if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, logEntries, 2272 logCount)) 2273 { 2274 messages::internalError(asyncResp->res); 2275 return; 2276 } 2277 2278 if (!logEntries.empty()) 2279 { 2280 nlohmann::json::object_t hostLogEntry; 2281 fillHostLoggerEntryJson(targetID, logEntries[0], hostLogEntry); 2282 asyncResp->res.jsonValue.update(hostLogEntry); 2283 return; 2284 } 2285 2286 // Requested ID was not found 2287 messages::resourceNotFound(asyncResp->res, "LogEntry", param); 2288 }); 2289 } 2290 2291 inline void handleBMCLogServicesCollectionGet( 2292 crow::App& app, const crow::Request& req, 2293 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2294 { 2295 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2296 { 2297 return; 2298 } 2299 // Collections don't include the static data added by SubRoute 2300 // because it has a duplicate entry for members 2301 asyncResp->res.jsonValue["@odata.type"] = 2302 "#LogServiceCollection.LogServiceCollection"; 2303 asyncResp->res.jsonValue["@odata.id"] = 2304 "/redfish/v1/Managers/bmc/LogServices"; 2305 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 2306 asyncResp->res.jsonValue["Description"] = 2307 "Collection of LogServices for this Manager"; 2308 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; 2309 logServiceArray = nlohmann::json::array(); 2310 2311 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 2312 nlohmann::json::object_t journal; 2313 journal["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal"; 2314 logServiceArray.emplace_back(std::move(journal)); 2315 #endif 2316 2317 asyncResp->res.jsonValue["Members@odata.count"] = logServiceArray.size(); 2318 2319 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 2320 constexpr std::array<std::string_view, 1> interfaces = { 2321 "xyz.openbmc_project.Collection.DeleteAll"}; 2322 dbus::utility::getSubTreePaths( 2323 "/xyz/openbmc_project/dump", 0, interfaces, 2324 [asyncResp]( 2325 const boost::system::error_code& ec, 2326 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) { 2327 if (ec) 2328 { 2329 BMCWEB_LOG_ERROR 2330 << "handleBMCLogServicesCollectionGet respHandler got error " 2331 << ec; 2332 // Assume that getting an error simply means there are no dump 2333 // LogServices. Return without adding any error response. 2334 return; 2335 } 2336 2337 nlohmann::json& logServiceArrayLocal = 2338 asyncResp->res.jsonValue["Members"]; 2339 2340 for (const std::string& path : subTreePaths) 2341 { 2342 if (path == "/xyz/openbmc_project/dump/bmc") 2343 { 2344 nlohmann::json::object_t member; 2345 member["@odata.id"] = 2346 "/redfish/v1/Managers/bmc/LogServices/Dump"; 2347 logServiceArrayLocal.emplace_back(std::move(member)); 2348 } 2349 else if (path == "/xyz/openbmc_project/dump/faultlog") 2350 { 2351 nlohmann::json::object_t member; 2352 member["@odata.id"] = 2353 "/redfish/v1/Managers/bmc/LogServices/FaultLog"; 2354 logServiceArrayLocal.emplace_back(std::move(member)); 2355 } 2356 } 2357 2358 asyncResp->res.jsonValue["Members@odata.count"] = 2359 logServiceArrayLocal.size(); 2360 }); 2361 #endif 2362 } 2363 2364 inline void requestRoutesBMCLogServiceCollection(App& app) 2365 { 2366 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/") 2367 .privileges(redfish::privileges::getLogServiceCollection) 2368 .methods(boost::beast::http::verb::get)( 2369 std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app))); 2370 } 2371 2372 inline void requestRoutesBMCJournalLogService(App& app) 2373 { 2374 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 2375 .privileges(redfish::privileges::getLogService) 2376 .methods(boost::beast::http::verb::get)( 2377 [&app](const crow::Request& req, 2378 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2379 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2380 { 2381 return; 2382 } 2383 asyncResp->res.jsonValue["@odata.type"] = 2384 "#LogService.v1_1_0.LogService"; 2385 asyncResp->res.jsonValue["@odata.id"] = 2386 "/redfish/v1/Managers/bmc/LogServices/Journal"; 2387 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 2388 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 2389 asyncResp->res.jsonValue["Id"] = "Journal"; 2390 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2391 2392 std::pair<std::string, std::string> redfishDateTimeOffset = 2393 redfish::time_utils::getDateTimeOffsetNow(); 2394 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2395 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2396 redfishDateTimeOffset.second; 2397 2398 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 2399 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 2400 }); 2401 } 2402 2403 static int 2404 fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 2405 sd_journal* journal, 2406 nlohmann::json::object_t& bmcJournalLogEntryJson) 2407 { 2408 // Get the Log Entry contents 2409 int ret = 0; 2410 2411 std::string message; 2412 std::string_view syslogID; 2413 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 2414 if (ret < 0) 2415 { 2416 BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: " 2417 << strerror(-ret); 2418 } 2419 if (!syslogID.empty()) 2420 { 2421 message += std::string(syslogID) + ": "; 2422 } 2423 2424 std::string_view msg; 2425 ret = getJournalMetadata(journal, "MESSAGE", msg); 2426 if (ret < 0) 2427 { 2428 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 2429 return 1; 2430 } 2431 message += std::string(msg); 2432 2433 // Get the severity from the PRIORITY field 2434 long int severity = 8; // Default to an invalid priority 2435 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 2436 if (ret < 0) 2437 { 2438 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 2439 } 2440 2441 // Get the Created time from the timestamp 2442 std::string entryTimeStr; 2443 if (!getEntryTimestamp(journal, entryTimeStr)) 2444 { 2445 return 1; 2446 } 2447 2448 // Fill in the log entry with the gathered data 2449 bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 2450 bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( 2451 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/{}", 2452 bmcJournalLogEntryID); 2453 bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; 2454 bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; 2455 bmcJournalLogEntryJson["Message"] = std::move(message); 2456 bmcJournalLogEntryJson["EntryType"] = "Oem"; 2457 bmcJournalLogEntryJson["Severity"] = severity <= 2 ? "Critical" 2458 : severity <= 4 ? "Warning" 2459 : "OK"; 2460 bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; 2461 bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); 2462 return 0; 2463 } 2464 2465 inline void requestRoutesBMCJournalLogEntryCollection(App& app) 2466 { 2467 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 2468 .privileges(redfish::privileges::getLogEntryCollection) 2469 .methods(boost::beast::http::verb::get)( 2470 [&app](const crow::Request& req, 2471 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2472 query_param::QueryCapabilities capabilities = { 2473 .canDelegateTop = true, 2474 .canDelegateSkip = true, 2475 }; 2476 query_param::Query delegatedQuery; 2477 if (!redfish::setUpRedfishRouteWithDelegation( 2478 app, req, asyncResp, delegatedQuery, capabilities)) 2479 { 2480 return; 2481 } 2482 2483 size_t skip = delegatedQuery.skip.value_or(0); 2484 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 2485 2486 // Collections don't include the static data added by SubRoute 2487 // because it has a duplicate entry for members 2488 asyncResp->res.jsonValue["@odata.type"] = 2489 "#LogEntryCollection.LogEntryCollection"; 2490 asyncResp->res.jsonValue["@odata.id"] = 2491 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 2492 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 2493 asyncResp->res.jsonValue["Description"] = 2494 "Collection of BMC Journal Entries"; 2495 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2496 logEntryArray = nlohmann::json::array(); 2497 2498 // Go through the journal and use the timestamp to create a 2499 // unique ID for each entry 2500 sd_journal* journalTmp = nullptr; 2501 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2502 if (ret < 0) 2503 { 2504 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 2505 messages::internalError(asyncResp->res); 2506 return; 2507 } 2508 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2509 journalTmp, sd_journal_close); 2510 journalTmp = nullptr; 2511 uint64_t entryCount = 0; 2512 // Reset the unique ID on the first entry 2513 bool firstEntry = true; 2514 SD_JOURNAL_FOREACH(journal.get()) 2515 { 2516 entryCount++; 2517 // Handle paging using skip (number of entries to skip from 2518 // the start) and top (number of entries to display) 2519 if (entryCount <= skip || entryCount > skip + top) 2520 { 2521 continue; 2522 } 2523 2524 std::string idStr; 2525 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2526 { 2527 continue; 2528 } 2529 firstEntry = false; 2530 2531 nlohmann::json::object_t bmcJournalLogEntry; 2532 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 2533 bmcJournalLogEntry) != 0) 2534 { 2535 messages::internalError(asyncResp->res); 2536 return; 2537 } 2538 logEntryArray.emplace_back(std::move(bmcJournalLogEntry)); 2539 } 2540 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 2541 if (skip + top < entryCount) 2542 { 2543 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2544 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 2545 std::to_string(skip + top); 2546 } 2547 }); 2548 } 2549 2550 inline void requestRoutesBMCJournalLogEntry(App& app) 2551 { 2552 BMCWEB_ROUTE(app, 2553 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/") 2554 .privileges(redfish::privileges::getLogEntry) 2555 .methods(boost::beast::http::verb::get)( 2556 [&app](const crow::Request& req, 2557 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2558 const std::string& entryID) { 2559 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2560 { 2561 return; 2562 } 2563 // Convert the unique ID back to a timestamp to find the entry 2564 uint64_t ts = 0; 2565 uint64_t index = 0; 2566 if (!getTimestampFromID(asyncResp, entryID, ts, index)) 2567 { 2568 return; 2569 } 2570 2571 sd_journal* journalTmp = nullptr; 2572 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2573 if (ret < 0) 2574 { 2575 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 2576 messages::internalError(asyncResp->res); 2577 return; 2578 } 2579 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2580 journalTmp, sd_journal_close); 2581 journalTmp = nullptr; 2582 // Go to the timestamp in the log and move to the entry at the 2583 // index tracking the unique ID 2584 std::string idStr; 2585 bool firstEntry = true; 2586 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 2587 if (ret < 0) 2588 { 2589 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 2590 << strerror(-ret); 2591 messages::internalError(asyncResp->res); 2592 return; 2593 } 2594 for (uint64_t i = 0; i <= index; i++) 2595 { 2596 sd_journal_next(journal.get()); 2597 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2598 { 2599 messages::internalError(asyncResp->res); 2600 return; 2601 } 2602 firstEntry = false; 2603 } 2604 // Confirm that the entry ID matches what was requested 2605 if (idStr != entryID) 2606 { 2607 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 2608 return; 2609 } 2610 2611 nlohmann::json::object_t bmcJournalLogEntry; 2612 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 2613 bmcJournalLogEntry) != 0) 2614 { 2615 messages::internalError(asyncResp->res); 2616 return; 2617 } 2618 asyncResp->res.jsonValue.update(bmcJournalLogEntry); 2619 }); 2620 } 2621 2622 inline void 2623 getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2624 const std::string& dumpType) 2625 { 2626 std::string dumpPath; 2627 std::string overWritePolicy; 2628 bool collectDiagnosticDataSupported = false; 2629 2630 if (dumpType == "BMC") 2631 { 2632 dumpPath = "/redfish/v1/Managers/bmc/LogServices/Dump"; 2633 overWritePolicy = "WrapsWhenFull"; 2634 collectDiagnosticDataSupported = true; 2635 } 2636 else if (dumpType == "FaultLog") 2637 { 2638 dumpPath = "/redfish/v1/Managers/bmc/LogServices/FaultLog"; 2639 overWritePolicy = "Unknown"; 2640 collectDiagnosticDataSupported = false; 2641 } 2642 else if (dumpType == "System") 2643 { 2644 dumpPath = "/redfish/v1/Systems/system/LogServices/Dump"; 2645 overWritePolicy = "WrapsWhenFull"; 2646 collectDiagnosticDataSupported = true; 2647 } 2648 else 2649 { 2650 BMCWEB_LOG_ERROR << "getDumpServiceInfo() invalid dump type: " 2651 << dumpType; 2652 messages::internalError(asyncResp->res); 2653 return; 2654 } 2655 2656 asyncResp->res.jsonValue["@odata.id"] = dumpPath; 2657 asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 2658 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2659 asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService"; 2660 asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename(); 2661 asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy); 2662 2663 std::pair<std::string, std::string> redfishDateTimeOffset = 2664 redfish::time_utils::getDateTimeOffsetNow(); 2665 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2666 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2667 redfishDateTimeOffset.second; 2668 2669 asyncResp->res.jsonValue["Entries"]["@odata.id"] = dumpPath + "/Entries"; 2670 2671 if (collectDiagnosticDataSupported) 2672 { 2673 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] 2674 ["target"] = 2675 dumpPath + "/Actions/LogService.CollectDiagnosticData"; 2676 } 2677 2678 constexpr std::array<std::string_view, 1> interfaces = {deleteAllInterface}; 2679 dbus::utility::getSubTreePaths( 2680 "/xyz/openbmc_project/dump", 0, interfaces, 2681 [asyncResp, dumpType, dumpPath]( 2682 const boost::system::error_code& ec, 2683 const dbus::utility::MapperGetSubTreePathsResponse& subTreePaths) { 2684 if (ec) 2685 { 2686 BMCWEB_LOG_ERROR << "getDumpServiceInfo respHandler got error " 2687 << ec; 2688 // Assume that getting an error simply means there are no dump 2689 // LogServices. Return without adding any error response. 2690 return; 2691 } 2692 2693 const std::string dbusDumpPath = 2694 "/xyz/openbmc_project/dump/" + 2695 boost::algorithm::to_lower_copy(dumpType); 2696 2697 for (const std::string& path : subTreePaths) 2698 { 2699 if (path == dbusDumpPath) 2700 { 2701 asyncResp->res 2702 .jsonValue["Actions"]["#LogService.ClearLog"]["target"] = 2703 dumpPath + "/Actions/LogService.ClearLog"; 2704 break; 2705 } 2706 } 2707 }); 2708 } 2709 2710 inline void handleLogServicesDumpServiceGet( 2711 crow::App& app, const std::string& dumpType, const crow::Request& req, 2712 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2713 { 2714 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2715 { 2716 return; 2717 } 2718 getDumpServiceInfo(asyncResp, dumpType); 2719 } 2720 2721 inline void handleLogServicesDumpServiceComputerSystemGet( 2722 crow::App& app, const crow::Request& req, 2723 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2724 const std::string& chassisId) 2725 { 2726 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2727 { 2728 return; 2729 } 2730 if (chassisId != "system") 2731 { 2732 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2733 return; 2734 } 2735 getDumpServiceInfo(asyncResp, "System"); 2736 } 2737 2738 inline void handleLogServicesDumpEntriesCollectionGet( 2739 crow::App& app, const std::string& dumpType, const crow::Request& req, 2740 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2741 { 2742 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2743 { 2744 return; 2745 } 2746 getDumpEntryCollection(asyncResp, dumpType); 2747 } 2748 2749 inline void handleLogServicesDumpEntriesCollectionComputerSystemGet( 2750 crow::App& app, const crow::Request& req, 2751 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2752 const std::string& chassisId) 2753 { 2754 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2755 { 2756 return; 2757 } 2758 if (chassisId != "system") 2759 { 2760 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2761 return; 2762 } 2763 getDumpEntryCollection(asyncResp, "System"); 2764 } 2765 2766 inline void handleLogServicesDumpEntryGet( 2767 crow::App& app, const std::string& dumpType, const crow::Request& req, 2768 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2769 const std::string& dumpId) 2770 { 2771 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2772 { 2773 return; 2774 } 2775 getDumpEntryById(asyncResp, dumpId, dumpType); 2776 } 2777 inline void handleLogServicesDumpEntryComputerSystemGet( 2778 crow::App& app, const crow::Request& req, 2779 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2780 const std::string& chassisId, const std::string& dumpId) 2781 { 2782 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2783 { 2784 return; 2785 } 2786 if (chassisId != "system") 2787 { 2788 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2789 return; 2790 } 2791 getDumpEntryById(asyncResp, dumpId, "System"); 2792 } 2793 2794 inline void handleLogServicesDumpEntryDelete( 2795 crow::App& app, const std::string& dumpType, const crow::Request& req, 2796 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2797 const std::string& dumpId) 2798 { 2799 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2800 { 2801 return; 2802 } 2803 deleteDumpEntry(asyncResp, dumpId, dumpType); 2804 } 2805 2806 inline void handleLogServicesDumpEntryComputerSystemDelete( 2807 crow::App& app, const crow::Request& req, 2808 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2809 const std::string& chassisId, const std::string& dumpId) 2810 { 2811 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2812 { 2813 return; 2814 } 2815 if (chassisId != "system") 2816 { 2817 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2818 return; 2819 } 2820 deleteDumpEntry(asyncResp, dumpId, "System"); 2821 } 2822 2823 inline void handleLogServicesDumpCollectDiagnosticDataPost( 2824 crow::App& app, const std::string& dumpType, const crow::Request& req, 2825 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2826 { 2827 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2828 { 2829 return; 2830 } 2831 createDump(asyncResp, req, dumpType); 2832 } 2833 2834 inline void handleLogServicesDumpCollectDiagnosticDataComputerSystemPost( 2835 crow::App& app, const crow::Request& req, 2836 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2837 const std::string& chassisId) 2838 { 2839 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2840 { 2841 return; 2842 } 2843 if (chassisId != "system") 2844 { 2845 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2846 return; 2847 } 2848 createDump(asyncResp, req, "System"); 2849 } 2850 2851 inline void handleLogServicesDumpClearLogPost( 2852 crow::App& app, const std::string& dumpType, const crow::Request& req, 2853 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2854 { 2855 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2856 { 2857 return; 2858 } 2859 clearDump(asyncResp, dumpType); 2860 } 2861 2862 inline void handleLogServicesDumpClearLogComputerSystemPost( 2863 crow::App& app, const crow::Request& req, 2864 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2865 const std::string& chassisId) 2866 { 2867 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2868 { 2869 return; 2870 } 2871 if (chassisId != "system") 2872 { 2873 messages::resourceNotFound(asyncResp->res, "ComputerSystem", chassisId); 2874 return; 2875 } 2876 clearDump(asyncResp, "System"); 2877 } 2878 2879 inline void requestRoutesBMCDumpService(App& app) 2880 { 2881 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 2882 .privileges(redfish::privileges::getLogService) 2883 .methods(boost::beast::http::verb::get)(std::bind_front( 2884 handleLogServicesDumpServiceGet, std::ref(app), "BMC")); 2885 } 2886 2887 inline void requestRoutesBMCDumpEntryCollection(App& app) 2888 { 2889 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 2890 .privileges(redfish::privileges::getLogEntryCollection) 2891 .methods(boost::beast::http::verb::get)(std::bind_front( 2892 handleLogServicesDumpEntriesCollectionGet, std::ref(app), "BMC")); 2893 } 2894 2895 inline void requestRoutesBMCDumpEntry(App& app) 2896 { 2897 BMCWEB_ROUTE(app, 2898 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2899 .privileges(redfish::privileges::getLogEntry) 2900 .methods(boost::beast::http::verb::get)(std::bind_front( 2901 handleLogServicesDumpEntryGet, std::ref(app), "BMC")); 2902 2903 BMCWEB_ROUTE(app, 2904 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2905 .privileges(redfish::privileges::deleteLogEntry) 2906 .methods(boost::beast::http::verb::delete_)(std::bind_front( 2907 handleLogServicesDumpEntryDelete, std::ref(app), "BMC")); 2908 } 2909 2910 inline void requestRoutesBMCDumpCreate(App& app) 2911 { 2912 BMCWEB_ROUTE( 2913 app, 2914 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 2915 .privileges(redfish::privileges::postLogService) 2916 .methods(boost::beast::http::verb::post)( 2917 std::bind_front(handleLogServicesDumpCollectDiagnosticDataPost, 2918 std::ref(app), "BMC")); 2919 } 2920 2921 inline void requestRoutesBMCDumpClear(App& app) 2922 { 2923 BMCWEB_ROUTE( 2924 app, 2925 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/") 2926 .privileges(redfish::privileges::postLogService) 2927 .methods(boost::beast::http::verb::post)(std::bind_front( 2928 handleLogServicesDumpClearLogPost, std::ref(app), "BMC")); 2929 } 2930 2931 inline void requestRoutesFaultLogDumpService(App& app) 2932 { 2933 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/") 2934 .privileges(redfish::privileges::getLogService) 2935 .methods(boost::beast::http::verb::get)(std::bind_front( 2936 handleLogServicesDumpServiceGet, std::ref(app), "FaultLog")); 2937 } 2938 2939 inline void requestRoutesFaultLogDumpEntryCollection(App& app) 2940 { 2941 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/") 2942 .privileges(redfish::privileges::getLogEntryCollection) 2943 .methods(boost::beast::http::verb::get)( 2944 std::bind_front(handleLogServicesDumpEntriesCollectionGet, 2945 std::ref(app), "FaultLog")); 2946 } 2947 2948 inline void requestRoutesFaultLogDumpEntry(App& app) 2949 { 2950 BMCWEB_ROUTE(app, 2951 "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/") 2952 .privileges(redfish::privileges::getLogEntry) 2953 .methods(boost::beast::http::verb::get)(std::bind_front( 2954 handleLogServicesDumpEntryGet, std::ref(app), "FaultLog")); 2955 2956 BMCWEB_ROUTE(app, 2957 "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/<str>/") 2958 .privileges(redfish::privileges::deleteLogEntry) 2959 .methods(boost::beast::http::verb::delete_)(std::bind_front( 2960 handleLogServicesDumpEntryDelete, std::ref(app), "FaultLog")); 2961 } 2962 2963 inline void requestRoutesFaultLogDumpClear(App& app) 2964 { 2965 BMCWEB_ROUTE( 2966 app, 2967 "/redfish/v1/Managers/bmc/LogServices/FaultLog/Actions/LogService.ClearLog/") 2968 .privileges(redfish::privileges::postLogService) 2969 .methods(boost::beast::http::verb::post)(std::bind_front( 2970 handleLogServicesDumpClearLogPost, std::ref(app), "FaultLog")); 2971 } 2972 2973 inline void requestRoutesSystemDumpService(App& app) 2974 { 2975 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/") 2976 .privileges(redfish::privileges::getLogService) 2977 .methods(boost::beast::http::verb::get)(std::bind_front( 2978 handleLogServicesDumpServiceComputerSystemGet, std::ref(app))); 2979 } 2980 2981 inline void requestRoutesSystemDumpEntryCollection(App& app) 2982 { 2983 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/") 2984 .privileges(redfish::privileges::getLogEntryCollection) 2985 .methods(boost::beast::http::verb::get)(std::bind_front( 2986 handleLogServicesDumpEntriesCollectionComputerSystemGet, 2987 std::ref(app))); 2988 } 2989 2990 inline void requestRoutesSystemDumpEntry(App& app) 2991 { 2992 BMCWEB_ROUTE(app, 2993 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/") 2994 .privileges(redfish::privileges::getLogEntry) 2995 .methods(boost::beast::http::verb::get)(std::bind_front( 2996 handleLogServicesDumpEntryComputerSystemGet, std::ref(app))); 2997 2998 BMCWEB_ROUTE(app, 2999 "/redfish/v1/Systems/<str>/LogServices/Dump/Entries/<str>/") 3000 .privileges(redfish::privileges::deleteLogEntry) 3001 .methods(boost::beast::http::verb::delete_)(std::bind_front( 3002 handleLogServicesDumpEntryComputerSystemDelete, std::ref(app))); 3003 } 3004 3005 inline void requestRoutesSystemDumpCreate(App& app) 3006 { 3007 BMCWEB_ROUTE( 3008 app, 3009 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 3010 .privileges(redfish::privileges::postLogService) 3011 .methods(boost::beast::http::verb::post)(std::bind_front( 3012 handleLogServicesDumpCollectDiagnosticDataComputerSystemPost, 3013 std::ref(app))); 3014 } 3015 3016 inline void requestRoutesSystemDumpClear(App& app) 3017 { 3018 BMCWEB_ROUTE( 3019 app, 3020 "/redfish/v1/Systems/<str>/LogServices/Dump/Actions/LogService.ClearLog/") 3021 .privileges(redfish::privileges::postLogService) 3022 .methods(boost::beast::http::verb::post)(std::bind_front( 3023 handleLogServicesDumpClearLogComputerSystemPost, std::ref(app))); 3024 } 3025 3026 inline void requestRoutesCrashdumpService(App& app) 3027 { 3028 // Note: Deviated from redfish privilege registry for GET & HEAD 3029 // method for security reasons. 3030 /** 3031 * Functions triggers appropriate requests on DBus 3032 */ 3033 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/") 3034 // This is incorrect, should be: 3035 //.privileges(redfish::privileges::getLogService) 3036 .privileges({{"ConfigureManager"}}) 3037 .methods(boost::beast::http::verb::get)( 3038 [&app](const crow::Request& req, 3039 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3040 const std::string& systemName) { 3041 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3042 { 3043 return; 3044 } 3045 if (systemName != "system") 3046 { 3047 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3048 systemName); 3049 return; 3050 } 3051 3052 // Copy over the static data to include the entries added by 3053 // SubRoute 3054 asyncResp->res.jsonValue["@odata.id"] = 3055 "/redfish/v1/Systems/system/LogServices/Crashdump"; 3056 asyncResp->res.jsonValue["@odata.type"] = 3057 "#LogService.v1_2_0.LogService"; 3058 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 3059 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 3060 asyncResp->res.jsonValue["Id"] = "Crashdump"; 3061 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 3062 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 3063 3064 std::pair<std::string, std::string> redfishDateTimeOffset = 3065 redfish::time_utils::getDateTimeOffsetNow(); 3066 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3067 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3068 redfishDateTimeOffset.second; 3069 3070 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 3071 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 3072 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"]["target"] = 3073 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog"; 3074 asyncResp->res.jsonValue["Actions"]["#LogService.CollectDiagnosticData"] 3075 ["target"] = 3076 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData"; 3077 }); 3078 } 3079 3080 void inline requestRoutesCrashdumpClear(App& app) 3081 { 3082 BMCWEB_ROUTE( 3083 app, 3084 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.ClearLog/") 3085 // This is incorrect, should be: 3086 //.privileges(redfish::privileges::postLogService) 3087 .privileges({{"ConfigureComponents"}}) 3088 .methods(boost::beast::http::verb::post)( 3089 [&app](const crow::Request& req, 3090 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3091 const std::string& systemName) { 3092 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3093 { 3094 return; 3095 } 3096 if (systemName != "system") 3097 { 3098 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3099 systemName); 3100 return; 3101 } 3102 crow::connections::systemBus->async_method_call( 3103 [asyncResp](const boost::system::error_code& ec, 3104 const std::string&) { 3105 if (ec) 3106 { 3107 messages::internalError(asyncResp->res); 3108 return; 3109 } 3110 messages::success(asyncResp->res); 3111 }, 3112 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 3113 }); 3114 } 3115 3116 static void 3117 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3118 const std::string& logID, nlohmann::json& logEntryJson) 3119 { 3120 auto getStoredLogCallback = 3121 [asyncResp, logID, 3122 &logEntryJson](const boost::system::error_code& ec, 3123 const dbus::utility::DBusPropertiesMap& params) { 3124 if (ec) 3125 { 3126 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 3127 if (ec.value() == 3128 boost::system::linux_error::bad_request_descriptor) 3129 { 3130 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3131 } 3132 else 3133 { 3134 messages::internalError(asyncResp->res); 3135 } 3136 return; 3137 } 3138 3139 std::string timestamp{}; 3140 std::string filename{}; 3141 std::string logfile{}; 3142 parseCrashdumpParameters(params, filename, timestamp, logfile); 3143 3144 if (filename.empty() || timestamp.empty()) 3145 { 3146 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3147 return; 3148 } 3149 3150 std::string crashdumpURI = 3151 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 3152 logID + "/" + filename; 3153 nlohmann::json::object_t logEntry; 3154 logEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 3155 logEntry["@odata.id"] = boost::urls::format( 3156 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/{}", 3157 logID); 3158 logEntry["Name"] = "CPU Crashdump"; 3159 logEntry["Id"] = logID; 3160 logEntry["EntryType"] = "Oem"; 3161 logEntry["AdditionalDataURI"] = std::move(crashdumpURI); 3162 logEntry["DiagnosticDataType"] = "OEM"; 3163 logEntry["OEMDiagnosticDataType"] = "PECICrashdump"; 3164 logEntry["Created"] = std::move(timestamp); 3165 3166 // If logEntryJson references an array of LogEntry resources 3167 // ('Members' list), then push this as a new entry, otherwise set it 3168 // directly 3169 if (logEntryJson.is_array()) 3170 { 3171 logEntryJson.push_back(logEntry); 3172 asyncResp->res.jsonValue["Members@odata.count"] = 3173 logEntryJson.size(); 3174 } 3175 else 3176 { 3177 logEntryJson.update(logEntry); 3178 } 3179 }; 3180 sdbusplus::asio::getAllProperties( 3181 *crow::connections::systemBus, crashdumpObject, 3182 crashdumpPath + std::string("/") + logID, crashdumpInterface, 3183 std::move(getStoredLogCallback)); 3184 } 3185 3186 inline void requestRoutesCrashdumpEntryCollection(App& app) 3187 { 3188 // Note: Deviated from redfish privilege registry for GET & HEAD 3189 // method for security reasons. 3190 /** 3191 * Functions triggers appropriate requests on DBus 3192 */ 3193 BMCWEB_ROUTE(app, 3194 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/") 3195 // This is incorrect, should be. 3196 //.privileges(redfish::privileges::postLogEntryCollection) 3197 .privileges({{"ConfigureComponents"}}) 3198 .methods(boost::beast::http::verb::get)( 3199 [&app](const crow::Request& req, 3200 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3201 const std::string& systemName) { 3202 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3203 { 3204 return; 3205 } 3206 if (systemName != "system") 3207 { 3208 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3209 systemName); 3210 return; 3211 } 3212 3213 constexpr std::array<std::string_view, 1> interfaces = { 3214 crashdumpInterface}; 3215 dbus::utility::getSubTreePaths( 3216 "/", 0, interfaces, 3217 [asyncResp](const boost::system::error_code& ec, 3218 const std::vector<std::string>& resp) { 3219 if (ec) 3220 { 3221 if (ec.value() != 3222 boost::system::errc::no_such_file_or_directory) 3223 { 3224 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 3225 << ec.message(); 3226 messages::internalError(asyncResp->res); 3227 return; 3228 } 3229 } 3230 asyncResp->res.jsonValue["@odata.type"] = 3231 "#LogEntryCollection.LogEntryCollection"; 3232 asyncResp->res.jsonValue["@odata.id"] = 3233 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 3234 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 3235 asyncResp->res.jsonValue["Description"] = 3236 "Collection of Crashdump Entries"; 3237 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3238 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3239 3240 for (const std::string& path : resp) 3241 { 3242 const sdbusplus::message::object_path objPath(path); 3243 // Get the log ID 3244 std::string logID = objPath.filename(); 3245 if (logID.empty()) 3246 { 3247 continue; 3248 } 3249 // Add the log entry to the array 3250 logCrashdumpEntry(asyncResp, logID, 3251 asyncResp->res.jsonValue["Members"]); 3252 } 3253 }); 3254 }); 3255 } 3256 3257 inline void requestRoutesCrashdumpEntry(App& app) 3258 { 3259 // Note: Deviated from redfish privilege registry for GET & HEAD 3260 // method for security reasons. 3261 3262 BMCWEB_ROUTE( 3263 app, "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/") 3264 // this is incorrect, should be 3265 // .privileges(redfish::privileges::getLogEntry) 3266 .privileges({{"ConfigureComponents"}}) 3267 .methods(boost::beast::http::verb::get)( 3268 [&app](const crow::Request& req, 3269 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3270 const std::string& systemName, const std::string& param) { 3271 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3272 { 3273 return; 3274 } 3275 if (systemName != "system") 3276 { 3277 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3278 systemName); 3279 return; 3280 } 3281 const std::string& logID = param; 3282 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 3283 }); 3284 } 3285 3286 inline void requestRoutesCrashdumpFile(App& app) 3287 { 3288 // Note: Deviated from redfish privilege registry for GET & HEAD 3289 // method for security reasons. 3290 BMCWEB_ROUTE( 3291 app, 3292 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Entries/<str>/<str>/") 3293 .privileges(redfish::privileges::getLogEntry) 3294 .methods(boost::beast::http::verb::get)( 3295 [](const crow::Request& req, 3296 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3297 const std::string& systemName, const std::string& logID, 3298 const std::string& fileName) { 3299 // Do not call getRedfishRoute here since the crashdump file is not a 3300 // Redfish resource. 3301 3302 if (systemName != "system") 3303 { 3304 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3305 systemName); 3306 return; 3307 } 3308 3309 auto getStoredLogCallback = 3310 [asyncResp, logID, fileName, url(boost::urls::url(req.url()))]( 3311 const boost::system::error_code& ec, 3312 const std::vector< 3313 std::pair<std::string, dbus::utility::DbusVariantType>>& 3314 resp) { 3315 if (ec) 3316 { 3317 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 3318 messages::internalError(asyncResp->res); 3319 return; 3320 } 3321 3322 std::string dbusFilename{}; 3323 std::string dbusTimestamp{}; 3324 std::string dbusFilepath{}; 3325 3326 parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 3327 dbusFilepath); 3328 3329 if (dbusFilename.empty() || dbusTimestamp.empty() || 3330 dbusFilepath.empty()) 3331 { 3332 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3333 return; 3334 } 3335 3336 // Verify the file name parameter is correct 3337 if (fileName != dbusFilename) 3338 { 3339 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3340 return; 3341 } 3342 3343 if (!std::filesystem::exists(dbusFilepath)) 3344 { 3345 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 3346 return; 3347 } 3348 std::ifstream ifs(dbusFilepath, std::ios::in | std::ios::binary); 3349 asyncResp->res.body() = 3350 std::string(std::istreambuf_iterator<char>{ifs}, {}); 3351 3352 // Configure this to be a file download when accessed 3353 // from a browser 3354 asyncResp->res.addHeader( 3355 boost::beast::http::field::content_disposition, "attachment"); 3356 }; 3357 sdbusplus::asio::getAllProperties( 3358 *crow::connections::systemBus, crashdumpObject, 3359 crashdumpPath + std::string("/") + logID, crashdumpInterface, 3360 std::move(getStoredLogCallback)); 3361 }); 3362 } 3363 3364 enum class OEMDiagnosticType 3365 { 3366 onDemand, 3367 telemetry, 3368 invalid, 3369 }; 3370 3371 inline OEMDiagnosticType getOEMDiagnosticType(std::string_view oemDiagStr) 3372 { 3373 if (oemDiagStr == "OnDemand") 3374 { 3375 return OEMDiagnosticType::onDemand; 3376 } 3377 if (oemDiagStr == "Telemetry") 3378 { 3379 return OEMDiagnosticType::telemetry; 3380 } 3381 3382 return OEMDiagnosticType::invalid; 3383 } 3384 3385 inline void requestRoutesCrashdumpCollect(App& app) 3386 { 3387 // Note: Deviated from redfish privilege registry for GET & HEAD 3388 // method for security reasons. 3389 BMCWEB_ROUTE( 3390 app, 3391 "/redfish/v1/Systems/<str>/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/") 3392 // The below is incorrect; Should be ConfigureManager 3393 //.privileges(redfish::privileges::postLogService) 3394 .privileges({{"ConfigureComponents"}}) 3395 .methods(boost::beast::http::verb::post)( 3396 [&app](const crow::Request& req, 3397 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3398 const std::string& systemName) { 3399 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3400 { 3401 return; 3402 } 3403 3404 if (systemName != "system") 3405 { 3406 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3407 systemName); 3408 return; 3409 } 3410 3411 std::string diagnosticDataType; 3412 std::string oemDiagnosticDataType; 3413 if (!redfish::json_util::readJsonAction( 3414 req, asyncResp->res, "DiagnosticDataType", diagnosticDataType, 3415 "OEMDiagnosticDataType", oemDiagnosticDataType)) 3416 { 3417 return; 3418 } 3419 3420 if (diagnosticDataType != "OEM") 3421 { 3422 BMCWEB_LOG_ERROR 3423 << "Only OEM DiagnosticDataType supported for Crashdump"; 3424 messages::actionParameterValueFormatError( 3425 asyncResp->res, diagnosticDataType, "DiagnosticDataType", 3426 "CollectDiagnosticData"); 3427 return; 3428 } 3429 3430 OEMDiagnosticType oemDiagType = 3431 getOEMDiagnosticType(oemDiagnosticDataType); 3432 3433 std::string iface; 3434 std::string method; 3435 std::string taskMatchStr; 3436 if (oemDiagType == OEMDiagnosticType::onDemand) 3437 { 3438 iface = crashdumpOnDemandInterface; 3439 method = "GenerateOnDemandLog"; 3440 taskMatchStr = "type='signal'," 3441 "interface='org.freedesktop.DBus.Properties'," 3442 "member='PropertiesChanged'," 3443 "arg0namespace='com.intel.crashdump'"; 3444 } 3445 else if (oemDiagType == OEMDiagnosticType::telemetry) 3446 { 3447 iface = crashdumpTelemetryInterface; 3448 method = "GenerateTelemetryLog"; 3449 taskMatchStr = "type='signal'," 3450 "interface='org.freedesktop.DBus.Properties'," 3451 "member='PropertiesChanged'," 3452 "arg0namespace='com.intel.crashdump'"; 3453 } 3454 else 3455 { 3456 BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: " 3457 << oemDiagnosticDataType; 3458 messages::actionParameterValueFormatError( 3459 asyncResp->res, oemDiagnosticDataType, "OEMDiagnosticDataType", 3460 "CollectDiagnosticData"); 3461 return; 3462 } 3463 3464 auto collectCrashdumpCallback = 3465 [asyncResp, payload(task::Payload(req)), 3466 taskMatchStr](const boost::system::error_code& ec, 3467 const std::string&) mutable { 3468 if (ec) 3469 { 3470 if (ec.value() == boost::system::errc::operation_not_supported) 3471 { 3472 messages::resourceInStandby(asyncResp->res); 3473 } 3474 else if (ec.value() == 3475 boost::system::errc::device_or_resource_busy) 3476 { 3477 messages::serviceTemporarilyUnavailable(asyncResp->res, 3478 "60"); 3479 } 3480 else 3481 { 3482 messages::internalError(asyncResp->res); 3483 } 3484 return; 3485 } 3486 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 3487 [](const boost::system::error_code& err, sdbusplus::message_t&, 3488 const std::shared_ptr<task::TaskData>& taskData) { 3489 if (!err) 3490 { 3491 taskData->messages.emplace_back(messages::taskCompletedOK( 3492 std::to_string(taskData->index))); 3493 taskData->state = "Completed"; 3494 } 3495 return task::completed; 3496 }, 3497 taskMatchStr); 3498 3499 task->startTimer(std::chrono::minutes(5)); 3500 task->populateResp(asyncResp->res); 3501 task->payload.emplace(std::move(payload)); 3502 }; 3503 3504 crow::connections::systemBus->async_method_call( 3505 std::move(collectCrashdumpCallback), crashdumpObject, crashdumpPath, 3506 iface, method); 3507 }); 3508 } 3509 3510 /** 3511 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 3512 */ 3513 inline void requestRoutesDBusLogServiceActionsClear(App& app) 3514 { 3515 /** 3516 * Function handles POST method request. 3517 * The Clear Log actions does not require any parameter.The action deletes 3518 * all entries found in the Entries collection for this Log Service. 3519 */ 3520 3521 BMCWEB_ROUTE( 3522 app, 3523 "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/") 3524 .privileges(redfish::privileges::postLogService) 3525 .methods(boost::beast::http::verb::post)( 3526 [&app](const crow::Request& req, 3527 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3528 const std::string& systemName) { 3529 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3530 { 3531 return; 3532 } 3533 if (systemName != "system") 3534 { 3535 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3536 systemName); 3537 return; 3538 } 3539 BMCWEB_LOG_DEBUG << "Do delete all entries."; 3540 3541 // Process response from Logging service. 3542 auto respHandler = [asyncResp](const boost::system::error_code& ec) { 3543 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 3544 if (ec) 3545 { 3546 // TODO Handle for specific error code 3547 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 3548 asyncResp->res.result( 3549 boost::beast::http::status::internal_server_error); 3550 return; 3551 } 3552 3553 asyncResp->res.result(boost::beast::http::status::no_content); 3554 }; 3555 3556 // Make call to Logging service to request Clear Log 3557 crow::connections::systemBus->async_method_call( 3558 respHandler, "xyz.openbmc_project.Logging", 3559 "/xyz/openbmc_project/logging", 3560 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3561 }); 3562 } 3563 3564 /**************************************************** 3565 * Redfish PostCode interfaces 3566 * using DBUS interface: getPostCodesTS 3567 ******************************************************/ 3568 inline void requestRoutesPostCodesLogService(App& app) 3569 { 3570 BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/") 3571 .privileges(redfish::privileges::getLogService) 3572 .methods(boost::beast::http::verb::get)( 3573 [&app](const crow::Request& req, 3574 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3575 const std::string& systemName) { 3576 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3577 { 3578 return; 3579 } 3580 if (systemName != "system") 3581 { 3582 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3583 systemName); 3584 return; 3585 } 3586 asyncResp->res.jsonValue["@odata.id"] = 3587 "/redfish/v1/Systems/system/LogServices/PostCodes"; 3588 asyncResp->res.jsonValue["@odata.type"] = 3589 "#LogService.v1_1_0.LogService"; 3590 asyncResp->res.jsonValue["Name"] = "POST Code Log Service"; 3591 asyncResp->res.jsonValue["Description"] = "POST Code Log Service"; 3592 asyncResp->res.jsonValue["Id"] = "PostCodes"; 3593 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 3594 asyncResp->res.jsonValue["Entries"]["@odata.id"] = 3595 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3596 3597 std::pair<std::string, std::string> redfishDateTimeOffset = 3598 redfish::time_utils::getDateTimeOffsetNow(); 3599 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3600 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3601 redfishDateTimeOffset.second; 3602 3603 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 3604 {"target", 3605 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}}; 3606 }); 3607 } 3608 3609 inline void requestRoutesPostCodesClear(App& app) 3610 { 3611 BMCWEB_ROUTE( 3612 app, 3613 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Actions/LogService.ClearLog/") 3614 // The following privilege is incorrect; It should be ConfigureManager 3615 //.privileges(redfish::privileges::postLogService) 3616 .privileges({{"ConfigureComponents"}}) 3617 .methods(boost::beast::http::verb::post)( 3618 [&app](const crow::Request& req, 3619 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3620 const std::string& systemName) { 3621 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 3622 { 3623 return; 3624 } 3625 if (systemName != "system") 3626 { 3627 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3628 systemName); 3629 return; 3630 } 3631 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 3632 3633 // Make call to post-code service to request clear all 3634 crow::connections::systemBus->async_method_call( 3635 [asyncResp](const boost::system::error_code& ec) { 3636 if (ec) 3637 { 3638 // TODO Handle for specific error code 3639 BMCWEB_LOG_ERROR << "doClearPostCodes resp_handler got error " 3640 << ec; 3641 asyncResp->res.result( 3642 boost::beast::http::status::internal_server_error); 3643 messages::internalError(asyncResp->res); 3644 return; 3645 } 3646 }, 3647 "xyz.openbmc_project.State.Boot.PostCode0", 3648 "/xyz/openbmc_project/State/Boot/PostCode0", 3649 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3650 }); 3651 } 3652 3653 /** 3654 * @brief Parse post code ID and get the current value and index value 3655 * eg: postCodeID=B1-2, currentValue=1, index=2 3656 * 3657 * @param[in] postCodeID Post Code ID 3658 * @param[out] currentValue Current value 3659 * @param[out] index Index value 3660 * 3661 * @return bool true if the parsing is successful, false the parsing fails 3662 */ 3663 inline static bool parsePostCode(const std::string& postCodeID, 3664 uint64_t& currentValue, uint16_t& index) 3665 { 3666 std::vector<std::string> split; 3667 bmcweb::split(split, postCodeID, '-'); 3668 if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B') 3669 { 3670 return false; 3671 } 3672 3673 auto start = std::next(split[0].begin()); 3674 auto end = split[0].end(); 3675 auto [ptrIndex, ecIndex] = std::from_chars(&*start, &*end, index); 3676 3677 if (ptrIndex != &*end || ecIndex != std::errc()) 3678 { 3679 return false; 3680 } 3681 3682 start = split[1].begin(); 3683 end = split[1].end(); 3684 3685 auto [ptrValue, ecValue] = std::from_chars(&*start, &*end, currentValue); 3686 3687 return ptrValue == &*end && ecValue == std::errc(); 3688 } 3689 3690 static bool fillPostCodeEntry( 3691 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3692 const boost::container::flat_map< 3693 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 3694 const uint16_t bootIndex, const uint64_t codeIndex = 0, 3695 const uint64_t skip = 0, const uint64_t top = 0) 3696 { 3697 // Get the Message from the MessageRegistry 3698 const registries::Message* message = 3699 registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 3700 3701 uint64_t currentCodeIndex = 0; 3702 uint64_t firstCodeTimeUs = 0; 3703 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3704 code : postcode) 3705 { 3706 currentCodeIndex++; 3707 std::string postcodeEntryID = 3708 "B" + std::to_string(bootIndex) + "-" + 3709 std::to_string(currentCodeIndex); // 1 based index in EntryID string 3710 3711 uint64_t usecSinceEpoch = code.first; 3712 uint64_t usTimeOffset = 0; 3713 3714 if (1 == currentCodeIndex) 3715 { // already incremented 3716 firstCodeTimeUs = code.first; 3717 } 3718 else 3719 { 3720 usTimeOffset = code.first - firstCodeTimeUs; 3721 } 3722 3723 // skip if no specific codeIndex is specified and currentCodeIndex does 3724 // not fall between top and skip 3725 if ((codeIndex == 0) && 3726 (currentCodeIndex <= skip || currentCodeIndex > top)) 3727 { 3728 continue; 3729 } 3730 3731 // skip if a specific codeIndex is specified and does not match the 3732 // currentIndex 3733 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 3734 { 3735 // This is done for simplicity. 1st entry is needed to calculate 3736 // time offset. To improve efficiency, one can get to the entry 3737 // directly (possibly with flatmap's nth method) 3738 continue; 3739 } 3740 3741 // currentCodeIndex is within top and skip or equal to specified code 3742 // index 3743 3744 // Get the Created time from the timestamp 3745 std::string entryTimeStr; 3746 entryTimeStr = redfish::time_utils::getDateTimeUintUs(usecSinceEpoch); 3747 3748 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 3749 std::ostringstream hexCode; 3750 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 3751 << std::get<0>(code.second); 3752 std::ostringstream timeOffsetStr; 3753 // Set Fixed -Point Notation 3754 timeOffsetStr << std::fixed; 3755 // Set precision to 4 digits 3756 timeOffsetStr << std::setprecision(4); 3757 // Add double to stream 3758 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 3759 std::vector<std::string> messageArgs = { 3760 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 3761 3762 // Get MessageArgs template from message registry 3763 std::string msg; 3764 if (message != nullptr) 3765 { 3766 msg = message->message; 3767 3768 // fill in this post code value 3769 int i = 0; 3770 for (const std::string& messageArg : messageArgs) 3771 { 3772 std::string argStr = "%" + std::to_string(++i); 3773 size_t argPos = msg.find(argStr); 3774 if (argPos != std::string::npos) 3775 { 3776 msg.replace(argPos, argStr.length(), messageArg); 3777 } 3778 } 3779 } 3780 3781 // Get Severity template from message registry 3782 std::string severity; 3783 if (message != nullptr) 3784 { 3785 severity = message->messageSeverity; 3786 } 3787 3788 // Format entry 3789 nlohmann::json::object_t bmcLogEntry; 3790 bmcLogEntry["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; 3791 bmcLogEntry["@odata.id"] = boost::urls::format( 3792 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/{}", 3793 postcodeEntryID); 3794 bmcLogEntry["Name"] = "POST Code Log Entry"; 3795 bmcLogEntry["Id"] = postcodeEntryID; 3796 bmcLogEntry["Message"] = std::move(msg); 3797 bmcLogEntry["MessageId"] = "OpenBMC.0.2.BIOSPOSTCode"; 3798 bmcLogEntry["MessageArgs"] = std::move(messageArgs); 3799 bmcLogEntry["EntryType"] = "Event"; 3800 bmcLogEntry["Severity"] = std::move(severity); 3801 bmcLogEntry["Created"] = entryTimeStr; 3802 if (!std::get<std::vector<uint8_t>>(code.second).empty()) 3803 { 3804 bmcLogEntry["AdditionalDataURI"] = 3805 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + 3806 postcodeEntryID + "/attachment"; 3807 } 3808 3809 // codeIndex is only specified when querying single entry, return only 3810 // that entry in this case 3811 if (codeIndex != 0) 3812 { 3813 asyncResp->res.jsonValue.update(bmcLogEntry); 3814 return true; 3815 } 3816 3817 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 3818 logEntryArray.emplace_back(std::move(bmcLogEntry)); 3819 } 3820 3821 // Return value is always false when querying multiple entries 3822 return false; 3823 } 3824 3825 static void 3826 getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3827 const std::string& entryId) 3828 { 3829 uint16_t bootIndex = 0; 3830 uint64_t codeIndex = 0; 3831 if (!parsePostCode(entryId, codeIndex, bootIndex)) 3832 { 3833 // Requested ID was not found 3834 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 3835 return; 3836 } 3837 3838 if (bootIndex == 0 || codeIndex == 0) 3839 { 3840 // 0 is an invalid index 3841 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 3842 return; 3843 } 3844 3845 crow::connections::systemBus->async_method_call( 3846 [asyncResp, entryId, bootIndex, 3847 codeIndex](const boost::system::error_code& ec, 3848 const boost::container::flat_map< 3849 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3850 postcode) { 3851 if (ec) 3852 { 3853 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3854 messages::internalError(asyncResp->res); 3855 return; 3856 } 3857 3858 if (postcode.empty()) 3859 { 3860 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 3861 return; 3862 } 3863 3864 if (!fillPostCodeEntry(asyncResp, postcode, bootIndex, codeIndex)) 3865 { 3866 messages::resourceNotFound(asyncResp->res, "LogEntry", entryId); 3867 return; 3868 } 3869 }, 3870 "xyz.openbmc_project.State.Boot.PostCode0", 3871 "/xyz/openbmc_project/State/Boot/PostCode0", 3872 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3873 bootIndex); 3874 } 3875 3876 static void 3877 getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3878 const uint16_t bootIndex, const uint16_t bootCount, 3879 const uint64_t entryCount, size_t skip, size_t top) 3880 { 3881 crow::connections::systemBus->async_method_call( 3882 [asyncResp, bootIndex, bootCount, entryCount, skip, 3883 top](const boost::system::error_code& ec, 3884 const boost::container::flat_map< 3885 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3886 postcode) { 3887 if (ec) 3888 { 3889 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3890 messages::internalError(asyncResp->res); 3891 return; 3892 } 3893 3894 uint64_t endCount = entryCount; 3895 if (!postcode.empty()) 3896 { 3897 endCount = entryCount + postcode.size(); 3898 if (skip < endCount && (top + skip) > entryCount) 3899 { 3900 uint64_t thisBootSkip = std::max(static_cast<uint64_t>(skip), 3901 entryCount) - 3902 entryCount; 3903 uint64_t thisBootTop = 3904 std::min(static_cast<uint64_t>(top + skip), endCount) - 3905 entryCount; 3906 3907 fillPostCodeEntry(asyncResp, postcode, bootIndex, 0, 3908 thisBootSkip, thisBootTop); 3909 } 3910 asyncResp->res.jsonValue["Members@odata.count"] = endCount; 3911 } 3912 3913 // continue to previous bootIndex 3914 if (bootIndex < bootCount) 3915 { 3916 getPostCodeForBoot(asyncResp, static_cast<uint16_t>(bootIndex + 1), 3917 bootCount, endCount, skip, top); 3918 } 3919 else if (skip + top < endCount) 3920 { 3921 asyncResp->res.jsonValue["Members@odata.nextLink"] = 3922 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" + 3923 std::to_string(skip + top); 3924 } 3925 }, 3926 "xyz.openbmc_project.State.Boot.PostCode0", 3927 "/xyz/openbmc_project/State/Boot/PostCode0", 3928 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3929 bootIndex); 3930 } 3931 3932 static void 3933 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3934 size_t skip, size_t top) 3935 { 3936 uint64_t entryCount = 0; 3937 sdbusplus::asio::getProperty<uint16_t>( 3938 *crow::connections::systemBus, 3939 "xyz.openbmc_project.State.Boot.PostCode0", 3940 "/xyz/openbmc_project/State/Boot/PostCode0", 3941 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 3942 [asyncResp, entryCount, skip, top](const boost::system::error_code& ec, 3943 const uint16_t bootCount) { 3944 if (ec) 3945 { 3946 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3947 messages::internalError(asyncResp->res); 3948 return; 3949 } 3950 getPostCodeForBoot(asyncResp, 1, bootCount, entryCount, skip, top); 3951 }); 3952 } 3953 3954 inline void requestRoutesPostCodesEntryCollection(App& app) 3955 { 3956 BMCWEB_ROUTE(app, 3957 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/") 3958 .privileges(redfish::privileges::getLogEntryCollection) 3959 .methods(boost::beast::http::verb::get)( 3960 [&app](const crow::Request& req, 3961 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3962 const std::string& systemName) { 3963 query_param::QueryCapabilities capabilities = { 3964 .canDelegateTop = true, 3965 .canDelegateSkip = true, 3966 }; 3967 query_param::Query delegatedQuery; 3968 if (!redfish::setUpRedfishRouteWithDelegation( 3969 app, req, asyncResp, delegatedQuery, capabilities)) 3970 { 3971 return; 3972 } 3973 3974 if (systemName != "system") 3975 { 3976 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 3977 systemName); 3978 return; 3979 } 3980 asyncResp->res.jsonValue["@odata.type"] = 3981 "#LogEntryCollection.LogEntryCollection"; 3982 asyncResp->res.jsonValue["@odata.id"] = 3983 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3984 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3985 asyncResp->res.jsonValue["Description"] = 3986 "Collection of POST Code Log Entries"; 3987 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3988 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3989 size_t skip = delegatedQuery.skip.value_or(0); 3990 size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 3991 getCurrentBootNumber(asyncResp, skip, top); 3992 }); 3993 } 3994 3995 inline void requestRoutesPostCodesEntryAdditionalData(App& app) 3996 { 3997 BMCWEB_ROUTE( 3998 app, 3999 "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/attachment/") 4000 .privileges(redfish::privileges::getLogEntry) 4001 .methods(boost::beast::http::verb::get)( 4002 [&app](const crow::Request& req, 4003 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4004 const std::string& systemName, 4005 const std::string& postCodeID) { 4006 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4007 { 4008 return; 4009 } 4010 if (!http_helpers::isContentTypeAllowed( 4011 req.getHeaderValue("Accept"), 4012 http_helpers::ContentType::OctetStream, true)) 4013 { 4014 asyncResp->res.result(boost::beast::http::status::bad_request); 4015 return; 4016 } 4017 if (systemName != "system") 4018 { 4019 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4020 systemName); 4021 return; 4022 } 4023 4024 uint64_t currentValue = 0; 4025 uint16_t index = 0; 4026 if (!parsePostCode(postCodeID, currentValue, index)) 4027 { 4028 messages::resourceNotFound(asyncResp->res, "LogEntry", postCodeID); 4029 return; 4030 } 4031 4032 crow::connections::systemBus->async_method_call( 4033 [asyncResp, postCodeID, currentValue]( 4034 const boost::system::error_code& ec, 4035 const std::vector<std::tuple<uint64_t, std::vector<uint8_t>>>& 4036 postcodes) { 4037 if (ec.value() == EBADR) 4038 { 4039 messages::resourceNotFound(asyncResp->res, "LogEntry", 4040 postCodeID); 4041 return; 4042 } 4043 if (ec) 4044 { 4045 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 4046 messages::internalError(asyncResp->res); 4047 return; 4048 } 4049 4050 size_t value = static_cast<size_t>(currentValue) - 1; 4051 if (value == std::string::npos || postcodes.size() < currentValue) 4052 { 4053 BMCWEB_LOG_WARNING << "Wrong currentValue value"; 4054 messages::resourceNotFound(asyncResp->res, "LogEntry", 4055 postCodeID); 4056 return; 4057 } 4058 4059 const auto& [tID, c] = postcodes[value]; 4060 if (c.empty()) 4061 { 4062 BMCWEB_LOG_WARNING << "No found post code data"; 4063 messages::resourceNotFound(asyncResp->res, "LogEntry", 4064 postCodeID); 4065 return; 4066 } 4067 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 4068 const char* d = reinterpret_cast<const char*>(c.data()); 4069 std::string_view strData(d, c.size()); 4070 4071 asyncResp->res.addHeader(boost::beast::http::field::content_type, 4072 "application/octet-stream"); 4073 asyncResp->res.addHeader( 4074 boost::beast::http::field::content_transfer_encoding, "Base64"); 4075 asyncResp->res.body() = crow::utility::base64encode(strData); 4076 }, 4077 "xyz.openbmc_project.State.Boot.PostCode0", 4078 "/xyz/openbmc_project/State/Boot/PostCode0", 4079 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", index); 4080 }); 4081 } 4082 4083 inline void requestRoutesPostCodesEntry(App& app) 4084 { 4085 BMCWEB_ROUTE( 4086 app, "/redfish/v1/Systems/<str>/LogServices/PostCodes/Entries/<str>/") 4087 .privileges(redfish::privileges::getLogEntry) 4088 .methods(boost::beast::http::verb::get)( 4089 [&app](const crow::Request& req, 4090 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4091 const std::string& systemName, const std::string& targetID) { 4092 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 4093 { 4094 return; 4095 } 4096 if (systemName != "system") 4097 { 4098 messages::resourceNotFound(asyncResp->res, "ComputerSystem", 4099 systemName); 4100 return; 4101 } 4102 4103 getPostCodeForEntry(asyncResp, targetID); 4104 }); 4105 } 4106 4107 } // namespace redfish 4108