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