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