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