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