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