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