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