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