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