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