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