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