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 std::time_t timestamp{}; 1432 std::time_t updateTimestamp{}; 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 const uint64_t* millisTimeStamp = 1452 std::get_if<uint64_t>( 1453 &propertyMap.second); 1454 if (millisTimeStamp != nullptr) 1455 { 1456 timestamp = 1457 crow::utility::getTimestamp( 1458 *millisTimeStamp); 1459 } 1460 } 1461 else if (propertyMap.first == 1462 "UpdateTimestamp") 1463 { 1464 const uint64_t* millisTimeStamp = 1465 std::get_if<uint64_t>( 1466 &propertyMap.second); 1467 if (millisTimeStamp != nullptr) 1468 { 1469 updateTimestamp = 1470 crow::utility::getTimestamp( 1471 *millisTimeStamp); 1472 } 1473 } 1474 else if (propertyMap.first == "Severity") 1475 { 1476 severity = std::get_if<std::string>( 1477 &propertyMap.second); 1478 } 1479 else if (propertyMap.first == "Message") 1480 { 1481 message = std::get_if<std::string>( 1482 &propertyMap.second); 1483 } 1484 else if (propertyMap.first == "Resolved") 1485 { 1486 const bool* resolveptr = 1487 std::get_if<bool>( 1488 &propertyMap.second); 1489 if (resolveptr == nullptr) 1490 { 1491 messages::internalError( 1492 asyncResp->res); 1493 return; 1494 } 1495 resolved = *resolveptr; 1496 } 1497 } 1498 if (id == nullptr || message == nullptr || 1499 severity == nullptr) 1500 { 1501 messages::internalError(asyncResp->res); 1502 return; 1503 } 1504 } 1505 else if (interfaceMap.first == 1506 "xyz.openbmc_project.Common.FilePath") 1507 { 1508 for (auto& propertyMap : interfaceMap.second) 1509 { 1510 if (propertyMap.first == "Path") 1511 { 1512 filePath = std::get_if<std::string>( 1513 &propertyMap.second); 1514 } 1515 } 1516 } 1517 } 1518 // Object path without the 1519 // xyz.openbmc_project.Logging.Entry interface, ignore 1520 // and continue. 1521 if (id == nullptr || message == nullptr || 1522 severity == nullptr) 1523 { 1524 continue; 1525 } 1526 entriesArray.push_back({}); 1527 nlohmann::json& thisEntry = entriesArray.back(); 1528 thisEntry["@odata.type"] = "#LogEntry.v1_8_0.LogEntry"; 1529 thisEntry["@odata.id"] = 1530 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1531 std::to_string(*id); 1532 thisEntry["Name"] = "System Event Log Entry"; 1533 thisEntry["Id"] = std::to_string(*id); 1534 thisEntry["Message"] = *message; 1535 thisEntry["Resolved"] = resolved; 1536 thisEntry["EntryType"] = "Event"; 1537 thisEntry["Severity"] = 1538 translateSeverityDbusToRedfish(*severity); 1539 thisEntry["Created"] = 1540 crow::utility::getDateTimeStdtime(timestamp); 1541 thisEntry["Modified"] = 1542 crow::utility::getDateTimeStdtime(updateTimestamp); 1543 if (filePath != nullptr) 1544 { 1545 thisEntry["AdditionalDataURI"] = 1546 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1547 std::to_string(*id) + "/attachment"; 1548 } 1549 } 1550 std::sort(entriesArray.begin(), entriesArray.end(), 1551 [](const nlohmann::json& left, 1552 const nlohmann::json& right) { 1553 return (left["Id"] <= right["Id"]); 1554 }); 1555 asyncResp->res.jsonValue["Members@odata.count"] = 1556 entriesArray.size(); 1557 }, 1558 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 1559 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1560 }); 1561 } 1562 1563 inline void requestRoutesDBusEventLogEntry(App& app) 1564 { 1565 BMCWEB_ROUTE( 1566 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1567 .privileges(redfish::privileges::getLogEntry) 1568 .methods(boost::beast::http::verb::get)( 1569 [](const crow::Request&, 1570 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1571 const std::string& param) 1572 1573 { 1574 std::string entryID = param; 1575 dbus::utility::escapePathForDbus(entryID); 1576 1577 // DBus implementation of EventLog/Entries 1578 // Make call to Logging Service to find all log entry objects 1579 crow::connections::systemBus->async_method_call( 1580 [asyncResp, entryID](const boost::system::error_code ec, 1581 const GetManagedPropertyType& resp) { 1582 if (ec.value() == EBADR) 1583 { 1584 messages::resourceNotFound( 1585 asyncResp->res, "EventLogEntry", entryID); 1586 return; 1587 } 1588 if (ec) 1589 { 1590 BMCWEB_LOG_ERROR 1591 << "EventLogEntry (DBus) resp_handler got error " 1592 << ec; 1593 messages::internalError(asyncResp->res); 1594 return; 1595 } 1596 const uint32_t* id = nullptr; 1597 std::time_t timestamp{}; 1598 std::time_t updateTimestamp{}; 1599 const std::string* severity = nullptr; 1600 const std::string* message = nullptr; 1601 const std::string* filePath = nullptr; 1602 bool resolved = false; 1603 1604 for (auto& propertyMap : resp) 1605 { 1606 if (propertyMap.first == "Id") 1607 { 1608 id = std::get_if<uint32_t>(&propertyMap.second); 1609 } 1610 else if (propertyMap.first == "Timestamp") 1611 { 1612 const uint64_t* millisTimeStamp = 1613 std::get_if<uint64_t>(&propertyMap.second); 1614 if (millisTimeStamp != nullptr) 1615 { 1616 timestamp = crow::utility::getTimestamp( 1617 *millisTimeStamp); 1618 } 1619 } 1620 else if (propertyMap.first == "UpdateTimestamp") 1621 { 1622 const uint64_t* millisTimeStamp = 1623 std::get_if<uint64_t>(&propertyMap.second); 1624 if (millisTimeStamp != nullptr) 1625 { 1626 updateTimestamp = 1627 crow::utility::getTimestamp( 1628 *millisTimeStamp); 1629 } 1630 } 1631 else if (propertyMap.first == "Severity") 1632 { 1633 severity = std::get_if<std::string>( 1634 &propertyMap.second); 1635 } 1636 else if (propertyMap.first == "Message") 1637 { 1638 message = std::get_if<std::string>( 1639 &propertyMap.second); 1640 } 1641 else if (propertyMap.first == "Resolved") 1642 { 1643 const bool* resolveptr = 1644 std::get_if<bool>(&propertyMap.second); 1645 if (resolveptr == nullptr) 1646 { 1647 messages::internalError(asyncResp->res); 1648 return; 1649 } 1650 resolved = *resolveptr; 1651 } 1652 else if (propertyMap.first == "Path") 1653 { 1654 filePath = std::get_if<std::string>( 1655 &propertyMap.second); 1656 } 1657 } 1658 if (id == nullptr || message == nullptr || 1659 severity == nullptr) 1660 { 1661 messages::internalError(asyncResp->res); 1662 return; 1663 } 1664 asyncResp->res.jsonValue["@odata.type"] = 1665 "#LogEntry.v1_8_0.LogEntry"; 1666 asyncResp->res.jsonValue["@odata.id"] = 1667 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1668 std::to_string(*id); 1669 asyncResp->res.jsonValue["Name"] = 1670 "System Event Log Entry"; 1671 asyncResp->res.jsonValue["Id"] = std::to_string(*id); 1672 asyncResp->res.jsonValue["Message"] = *message; 1673 asyncResp->res.jsonValue["Resolved"] = resolved; 1674 asyncResp->res.jsonValue["EntryType"] = "Event"; 1675 asyncResp->res.jsonValue["Severity"] = 1676 translateSeverityDbusToRedfish(*severity); 1677 asyncResp->res.jsonValue["Created"] = 1678 crow::utility::getDateTimeStdtime(timestamp); 1679 asyncResp->res.jsonValue["Modified"] = 1680 crow::utility::getDateTimeStdtime(updateTimestamp); 1681 if (filePath != nullptr) 1682 { 1683 asyncResp->res.jsonValue["AdditionalDataURI"] = 1684 "/redfish/v1/Systems/system/LogServices/EventLog/attachment/" + 1685 std::to_string(*id); 1686 } 1687 }, 1688 "xyz.openbmc_project.Logging", 1689 "/xyz/openbmc_project/logging/entry/" + entryID, 1690 "org.freedesktop.DBus.Properties", "GetAll", ""); 1691 }); 1692 1693 BMCWEB_ROUTE( 1694 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1695 .privileges(redfish::privileges::patchLogEntry) 1696 .methods(boost::beast::http::verb::patch)( 1697 [](const crow::Request& req, 1698 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1699 const std::string& entryId) { 1700 std::optional<bool> resolved; 1701 1702 if (!json_util::readJson(req, asyncResp->res, "Resolved", 1703 resolved)) 1704 { 1705 return; 1706 } 1707 BMCWEB_LOG_DEBUG << "Set Resolved"; 1708 1709 crow::connections::systemBus->async_method_call( 1710 [asyncResp, entryId](const boost::system::error_code ec) { 1711 if (ec) 1712 { 1713 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1714 messages::internalError(asyncResp->res); 1715 return; 1716 } 1717 }, 1718 "xyz.openbmc_project.Logging", 1719 "/xyz/openbmc_project/logging/entry/" + entryId, 1720 "org.freedesktop.DBus.Properties", "Set", 1721 "xyz.openbmc_project.Logging.Entry", "Resolved", 1722 dbus::utility::DbusVariantType(*resolved)); 1723 }); 1724 1725 BMCWEB_ROUTE( 1726 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1727 .privileges(redfish::privileges::deleteLogEntry) 1728 1729 .methods(boost::beast::http::verb::delete_)( 1730 [](const crow::Request&, 1731 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1732 const std::string& param) 1733 1734 { 1735 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1736 1737 std::string entryID = param; 1738 1739 dbus::utility::escapePathForDbus(entryID); 1740 1741 // Process response from Logging service. 1742 auto respHandler = [asyncResp, entryID]( 1743 const boost::system::error_code ec) { 1744 BMCWEB_LOG_DEBUG 1745 << "EventLogEntry (DBus) doDelete callback: Done"; 1746 if (ec) 1747 { 1748 if (ec.value() == EBADR) 1749 { 1750 messages::resourceNotFound(asyncResp->res, 1751 "LogEntry", entryID); 1752 return; 1753 } 1754 // TODO Handle for specific error code 1755 BMCWEB_LOG_ERROR 1756 << "EventLogEntry (DBus) doDelete respHandler got error " 1757 << ec; 1758 asyncResp->res.result( 1759 boost::beast::http::status::internal_server_error); 1760 return; 1761 } 1762 1763 asyncResp->res.result(boost::beast::http::status::ok); 1764 }; 1765 1766 // Make call to Logging service to request Delete Log 1767 crow::connections::systemBus->async_method_call( 1768 respHandler, "xyz.openbmc_project.Logging", 1769 "/xyz/openbmc_project/logging/entry/" + entryID, 1770 "xyz.openbmc_project.Object.Delete", "Delete"); 1771 }); 1772 } 1773 1774 inline void requestRoutesDBusEventLogEntryDownload(App& app) 1775 { 1776 BMCWEB_ROUTE( 1777 app, 1778 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/attachment") 1779 .privileges(redfish::privileges::getLogEntry) 1780 .methods(boost::beast::http::verb::get)( 1781 [](const crow::Request& req, 1782 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1783 const std::string& param) 1784 1785 { 1786 if (!http_helpers::isOctetAccepted( 1787 req.getHeaderValue("Accept"))) 1788 { 1789 asyncResp->res.result( 1790 boost::beast::http::status::bad_request); 1791 return; 1792 } 1793 1794 std::string entryID = param; 1795 dbus::utility::escapePathForDbus(entryID); 1796 1797 crow::connections::systemBus->async_method_call( 1798 [asyncResp, 1799 entryID](const boost::system::error_code ec, 1800 const sdbusplus::message::unix_fd& unixfd) { 1801 if (ec.value() == EBADR) 1802 { 1803 messages::resourceNotFound( 1804 asyncResp->res, "EventLogAttachment", entryID); 1805 return; 1806 } 1807 if (ec) 1808 { 1809 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1810 messages::internalError(asyncResp->res); 1811 return; 1812 } 1813 1814 int fd = -1; 1815 fd = dup(unixfd); 1816 if (fd == -1) 1817 { 1818 messages::internalError(asyncResp->res); 1819 return; 1820 } 1821 1822 long long int size = lseek(fd, 0, SEEK_END); 1823 if (size == -1) 1824 { 1825 messages::internalError(asyncResp->res); 1826 return; 1827 } 1828 1829 // Arbitrary max size of 64kb 1830 constexpr int maxFileSize = 65536; 1831 if (size > maxFileSize) 1832 { 1833 BMCWEB_LOG_ERROR 1834 << "File size exceeds maximum allowed size of " 1835 << maxFileSize; 1836 messages::internalError(asyncResp->res); 1837 return; 1838 } 1839 std::vector<char> data(static_cast<size_t>(size)); 1840 long long int rc = lseek(fd, 0, SEEK_SET); 1841 if (rc == -1) 1842 { 1843 messages::internalError(asyncResp->res); 1844 return; 1845 } 1846 rc = read(fd, data.data(), data.size()); 1847 if ((rc == -1) || (rc != size)) 1848 { 1849 messages::internalError(asyncResp->res); 1850 return; 1851 } 1852 close(fd); 1853 1854 std::string_view strData(data.data(), data.size()); 1855 std::string output = 1856 crow::utility::base64encode(strData); 1857 1858 asyncResp->res.addHeader("Content-Type", 1859 "application/octet-stream"); 1860 asyncResp->res.addHeader("Content-Transfer-Encoding", 1861 "Base64"); 1862 asyncResp->res.body() = std::move(output); 1863 }, 1864 "xyz.openbmc_project.Logging", 1865 "/xyz/openbmc_project/logging/entry/" + entryID, 1866 "xyz.openbmc_project.Logging.Entry", "GetEntry"); 1867 }); 1868 } 1869 1870 constexpr const char* hostLoggerFolderPath = "/var/log/console"; 1871 1872 inline bool 1873 getHostLoggerFiles(const std::string& hostLoggerFilePath, 1874 std::vector<std::filesystem::path>& hostLoggerFiles) 1875 { 1876 std::error_code ec; 1877 std::filesystem::directory_iterator logPath(hostLoggerFilePath, ec); 1878 if (ec) 1879 { 1880 BMCWEB_LOG_ERROR << ec.message(); 1881 return false; 1882 } 1883 for (const std::filesystem::directory_entry& it : logPath) 1884 { 1885 std::string filename = it.path().filename(); 1886 // Prefix of each log files is "log". Find the file and save the 1887 // path 1888 if (boost::starts_with(filename, "log")) 1889 { 1890 hostLoggerFiles.emplace_back(it.path()); 1891 } 1892 } 1893 // As the log files rotate, they are appended with a ".#" that is higher for 1894 // the older logs. Since we start from oldest logs, sort the name in 1895 // descending order. 1896 std::sort(hostLoggerFiles.rbegin(), hostLoggerFiles.rend(), 1897 AlphanumLess<std::string>()); 1898 1899 return true; 1900 } 1901 1902 inline bool 1903 getHostLoggerEntries(std::vector<std::filesystem::path>& hostLoggerFiles, 1904 uint64_t& skip, uint64_t& top, 1905 std::vector<std::string>& logEntries, size_t& logCount) 1906 { 1907 GzFileReader logFile; 1908 1909 // Go though all log files and expose host logs. 1910 for (const std::filesystem::path& it : hostLoggerFiles) 1911 { 1912 if (!logFile.gzGetLines(it.string(), skip, top, logEntries, logCount)) 1913 { 1914 BMCWEB_LOG_ERROR << "fail to expose host logs"; 1915 return false; 1916 } 1917 } 1918 // Get lastMessage from constructor by getter 1919 std::string lastMessage = logFile.getLastMessage(); 1920 if (!lastMessage.empty()) 1921 { 1922 logCount++; 1923 if (logCount > skip && logCount <= (skip + top)) 1924 { 1925 logEntries.push_back(lastMessage); 1926 } 1927 } 1928 return true; 1929 } 1930 1931 inline void fillHostLoggerEntryJson(const std::string& logEntryID, 1932 const std::string& msg, 1933 nlohmann::json& logEntryJson) 1934 { 1935 // Fill in the log entry with the gathered data. 1936 logEntryJson = { 1937 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1938 {"@odata.id", 1939 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/" + 1940 logEntryID}, 1941 {"Name", "Host Logger Entry"}, 1942 {"Id", logEntryID}, 1943 {"Message", msg}, 1944 {"EntryType", "Oem"}, 1945 {"Severity", "OK"}, 1946 {"OemRecordFormat", "Host Logger Entry"}}; 1947 } 1948 1949 inline void requestRoutesSystemHostLogger(App& app) 1950 { 1951 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/HostLogger/") 1952 .privileges(redfish::privileges::getLogService) 1953 .methods( 1954 boost::beast::http::verb:: 1955 get)([](const crow::Request&, 1956 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1957 asyncResp->res.jsonValue["@odata.id"] = 1958 "/redfish/v1/Systems/system/LogServices/HostLogger"; 1959 asyncResp->res.jsonValue["@odata.type"] = 1960 "#LogService.v1_1_0.LogService"; 1961 asyncResp->res.jsonValue["Name"] = "Host Logger Service"; 1962 asyncResp->res.jsonValue["Description"] = "Host Logger Service"; 1963 asyncResp->res.jsonValue["Id"] = "HostLogger"; 1964 asyncResp->res.jsonValue["Entries"] = { 1965 {"@odata.id", 1966 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries"}}; 1967 }); 1968 } 1969 1970 inline void requestRoutesSystemHostLoggerCollection(App& app) 1971 { 1972 BMCWEB_ROUTE(app, 1973 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/") 1974 .privileges(redfish::privileges::getLogEntry) 1975 .methods( 1976 boost::beast::http::verb:: 1977 get)([](const crow::Request& req, 1978 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1979 uint64_t skip = 0; 1980 uint64_t top = maxEntriesPerPage; // Show max 1000 entries by 1981 // default, allow range 1 to 1982 // 1000 entries per page. 1983 if (!getSkipParam(asyncResp, req, skip)) 1984 { 1985 return; 1986 } 1987 if (!getTopParam(asyncResp, req, top)) 1988 { 1989 return; 1990 } 1991 asyncResp->res.jsonValue["@odata.id"] = 1992 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries"; 1993 asyncResp->res.jsonValue["@odata.type"] = 1994 "#LogEntryCollection.LogEntryCollection"; 1995 asyncResp->res.jsonValue["Name"] = "HostLogger Entries"; 1996 asyncResp->res.jsonValue["Description"] = 1997 "Collection of HostLogger Entries"; 1998 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 1999 logEntryArray = nlohmann::json::array(); 2000 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2001 2002 std::vector<std::filesystem::path> hostLoggerFiles; 2003 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2004 { 2005 BMCWEB_LOG_ERROR << "fail to get host log file path"; 2006 return; 2007 } 2008 2009 size_t logCount = 0; 2010 // This vector only store the entries we want to expose that 2011 // control by skip and top. 2012 std::vector<std::string> logEntries; 2013 if (!getHostLoggerEntries(hostLoggerFiles, skip, top, logEntries, 2014 logCount)) 2015 { 2016 messages::internalError(asyncResp->res); 2017 return; 2018 } 2019 // If vector is empty, that means skip value larger than total 2020 // log count 2021 if (logEntries.empty()) 2022 { 2023 asyncResp->res.jsonValue["Members@odata.count"] = logCount; 2024 return; 2025 } 2026 if (!logEntries.empty()) 2027 { 2028 for (size_t i = 0; i < logEntries.size(); i++) 2029 { 2030 logEntryArray.push_back({}); 2031 nlohmann::json& hostLogEntry = logEntryArray.back(); 2032 fillHostLoggerEntryJson(std::to_string(skip + i), 2033 logEntries[i], hostLogEntry); 2034 } 2035 2036 asyncResp->res.jsonValue["Members@odata.count"] = logCount; 2037 if (skip + top < logCount) 2038 { 2039 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2040 "/redfish/v1/Systems/system/LogServices/HostLogger/Entries?$skip=" + 2041 std::to_string(skip + top); 2042 } 2043 } 2044 }); 2045 } 2046 2047 inline void requestRoutesSystemHostLoggerLogEntry(App& app) 2048 { 2049 BMCWEB_ROUTE( 2050 app, "/redfish/v1/Systems/system/LogServices/HostLogger/Entries/<str>/") 2051 .privileges(redfish::privileges::getLogEntry) 2052 .methods(boost::beast::http::verb::get)( 2053 [](const crow::Request&, 2054 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2055 const std::string& param) { 2056 const std::string& targetID = param; 2057 2058 uint64_t idInt = 0; 2059 2060 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 2061 const char* end = targetID.data() + targetID.size(); 2062 2063 auto [ptr, ec] = std::from_chars(targetID.data(), end, idInt); 2064 if (ec == std::errc::invalid_argument) 2065 { 2066 messages::resourceMissingAtURI(asyncResp->res, targetID); 2067 return; 2068 } 2069 if (ec == std::errc::result_out_of_range) 2070 { 2071 messages::resourceMissingAtURI(asyncResp->res, targetID); 2072 return; 2073 } 2074 2075 std::vector<std::filesystem::path> hostLoggerFiles; 2076 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2077 { 2078 BMCWEB_LOG_ERROR << "fail to get host log file path"; 2079 return; 2080 } 2081 2082 size_t logCount = 0; 2083 uint64_t top = 1; 2084 std::vector<std::string> logEntries; 2085 // We can get specific entry by skip and top. For example, if we 2086 // want to get nth entry, we can set skip = n-1 and top = 1 to 2087 // get that entry 2088 if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, 2089 logEntries, logCount)) 2090 { 2091 messages::internalError(asyncResp->res); 2092 return; 2093 } 2094 2095 if (!logEntries.empty()) 2096 { 2097 fillHostLoggerEntryJson(targetID, logEntries[0], 2098 asyncResp->res.jsonValue); 2099 return; 2100 } 2101 2102 // Requested ID was not found 2103 messages::resourceMissingAtURI(asyncResp->res, targetID); 2104 }); 2105 } 2106 2107 inline void requestRoutesBMCLogServiceCollection(App& app) 2108 { 2109 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/") 2110 .privileges(redfish::privileges::getLogServiceCollection) 2111 .methods(boost::beast::http::verb::get)( 2112 [](const crow::Request&, 2113 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2114 // Collections don't include the static data added by SubRoute 2115 // because it has a duplicate entry for members 2116 asyncResp->res.jsonValue["@odata.type"] = 2117 "#LogServiceCollection.LogServiceCollection"; 2118 asyncResp->res.jsonValue["@odata.id"] = 2119 "/redfish/v1/Managers/bmc/LogServices"; 2120 asyncResp->res.jsonValue["Name"] = 2121 "Open BMC Log Services Collection"; 2122 asyncResp->res.jsonValue["Description"] = 2123 "Collection of LogServices for this Manager"; 2124 nlohmann::json& logServiceArray = 2125 asyncResp->res.jsonValue["Members"]; 2126 logServiceArray = nlohmann::json::array(); 2127 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 2128 logServiceArray.push_back( 2129 {{"@odata.id", 2130 "/redfish/v1/Managers/bmc/LogServices/Dump"}}); 2131 #endif 2132 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 2133 logServiceArray.push_back( 2134 {{"@odata.id", 2135 "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 2136 #endif 2137 asyncResp->res.jsonValue["Members@odata.count"] = 2138 logServiceArray.size(); 2139 }); 2140 } 2141 2142 inline void requestRoutesBMCJournalLogService(App& app) 2143 { 2144 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 2145 .privileges(redfish::privileges::getLogService) 2146 .methods(boost::beast::http::verb::get)( 2147 [](const crow::Request&, 2148 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2149 2150 { 2151 asyncResp->res.jsonValue["@odata.type"] = 2152 "#LogService.v1_1_0.LogService"; 2153 asyncResp->res.jsonValue["@odata.id"] = 2154 "/redfish/v1/Managers/bmc/LogServices/Journal"; 2155 asyncResp->res.jsonValue["Name"] = 2156 "Open BMC Journal Log Service"; 2157 asyncResp->res.jsonValue["Description"] = 2158 "BMC Journal Log Service"; 2159 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 2160 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2161 2162 std::pair<std::string, std::string> redfishDateTimeOffset = 2163 crow::utility::getDateTimeOffsetNow(); 2164 asyncResp->res.jsonValue["DateTime"] = 2165 redfishDateTimeOffset.first; 2166 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2167 redfishDateTimeOffset.second; 2168 2169 asyncResp->res.jsonValue["Entries"] = { 2170 {"@odata.id", 2171 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 2172 }); 2173 } 2174 2175 static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 2176 sd_journal* journal, 2177 nlohmann::json& bmcJournalLogEntryJson) 2178 { 2179 // Get the Log Entry contents 2180 int ret = 0; 2181 2182 std::string message; 2183 std::string_view syslogID; 2184 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 2185 if (ret < 0) 2186 { 2187 BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: " 2188 << strerror(-ret); 2189 } 2190 if (!syslogID.empty()) 2191 { 2192 message += std::string(syslogID) + ": "; 2193 } 2194 2195 std::string_view msg; 2196 ret = getJournalMetadata(journal, "MESSAGE", msg); 2197 if (ret < 0) 2198 { 2199 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 2200 return 1; 2201 } 2202 message += std::string(msg); 2203 2204 // Get the severity from the PRIORITY field 2205 long int severity = 8; // Default to an invalid priority 2206 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 2207 if (ret < 0) 2208 { 2209 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 2210 } 2211 2212 // Get the Created time from the timestamp 2213 std::string entryTimeStr; 2214 if (!getEntryTimestamp(journal, entryTimeStr)) 2215 { 2216 return 1; 2217 } 2218 2219 // Fill in the log entry with the gathered data 2220 bmcJournalLogEntryJson = { 2221 {"@odata.type", "#LogEntry.v1_8_0.LogEntry"}, 2222 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 2223 bmcJournalLogEntryID}, 2224 {"Name", "BMC Journal Entry"}, 2225 {"Id", bmcJournalLogEntryID}, 2226 {"Message", std::move(message)}, 2227 {"EntryType", "Oem"}, 2228 {"Severity", severity <= 2 ? "Critical" 2229 : severity <= 4 ? "Warning" 2230 : "OK"}, 2231 {"OemRecordFormat", "BMC Journal Entry"}, 2232 {"Created", std::move(entryTimeStr)}}; 2233 return 0; 2234 } 2235 2236 inline void requestRoutesBMCJournalLogEntryCollection(App& app) 2237 { 2238 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 2239 .privileges(redfish::privileges::getLogEntryCollection) 2240 .methods( 2241 boost::beast::http::verb:: 2242 get)([](const crow::Request& req, 2243 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2244 static constexpr const long maxEntriesPerPage = 1000; 2245 uint64_t skip = 0; 2246 uint64_t top = maxEntriesPerPage; // Show max entries by default 2247 if (!getSkipParam(asyncResp, req, skip)) 2248 { 2249 return; 2250 } 2251 if (!getTopParam(asyncResp, req, top)) 2252 { 2253 return; 2254 } 2255 // Collections don't include the static data added by SubRoute 2256 // because it has a duplicate entry for members 2257 asyncResp->res.jsonValue["@odata.type"] = 2258 "#LogEntryCollection.LogEntryCollection"; 2259 asyncResp->res.jsonValue["@odata.id"] = 2260 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 2261 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 2262 asyncResp->res.jsonValue["Description"] = 2263 "Collection of BMC Journal Entries"; 2264 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2265 logEntryArray = nlohmann::json::array(); 2266 2267 // Go through the journal and use the timestamp to create a 2268 // unique ID for each entry 2269 sd_journal* journalTmp = nullptr; 2270 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2271 if (ret < 0) 2272 { 2273 BMCWEB_LOG_ERROR << "failed to open journal: " 2274 << strerror(-ret); 2275 messages::internalError(asyncResp->res); 2276 return; 2277 } 2278 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2279 journalTmp, sd_journal_close); 2280 journalTmp = nullptr; 2281 uint64_t entryCount = 0; 2282 // Reset the unique ID on the first entry 2283 bool firstEntry = true; 2284 SD_JOURNAL_FOREACH(journal.get()) 2285 { 2286 entryCount++; 2287 // Handle paging using skip (number of entries to skip from 2288 // the start) and top (number of entries to display) 2289 if (entryCount <= skip || entryCount > skip + top) 2290 { 2291 continue; 2292 } 2293 2294 std::string idStr; 2295 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2296 { 2297 continue; 2298 } 2299 2300 if (firstEntry) 2301 { 2302 firstEntry = false; 2303 } 2304 2305 logEntryArray.push_back({}); 2306 nlohmann::json& bmcJournalLogEntry = logEntryArray.back(); 2307 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 2308 bmcJournalLogEntry) != 0) 2309 { 2310 messages::internalError(asyncResp->res); 2311 return; 2312 } 2313 } 2314 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 2315 if (skip + top < entryCount) 2316 { 2317 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2318 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 2319 std::to_string(skip + top); 2320 } 2321 }); 2322 } 2323 2324 inline void requestRoutesBMCJournalLogEntry(App& app) 2325 { 2326 BMCWEB_ROUTE(app, 2327 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/") 2328 .privileges(redfish::privileges::getLogEntry) 2329 .methods(boost::beast::http::verb::get)( 2330 [](const crow::Request&, 2331 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2332 const std::string& entryID) { 2333 // Convert the unique ID back to a timestamp to find the entry 2334 uint64_t ts = 0; 2335 uint64_t index = 0; 2336 if (!getTimestampFromID(asyncResp, entryID, ts, index)) 2337 { 2338 return; 2339 } 2340 2341 sd_journal* journalTmp = nullptr; 2342 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2343 if (ret < 0) 2344 { 2345 BMCWEB_LOG_ERROR << "failed to open journal: " 2346 << strerror(-ret); 2347 messages::internalError(asyncResp->res); 2348 return; 2349 } 2350 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> 2351 journal(journalTmp, sd_journal_close); 2352 journalTmp = nullptr; 2353 // Go to the timestamp in the log and move to the entry at the 2354 // index tracking the unique ID 2355 std::string idStr; 2356 bool firstEntry = true; 2357 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 2358 if (ret < 0) 2359 { 2360 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 2361 << strerror(-ret); 2362 messages::internalError(asyncResp->res); 2363 return; 2364 } 2365 for (uint64_t i = 0; i <= index; i++) 2366 { 2367 sd_journal_next(journal.get()); 2368 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2369 { 2370 messages::internalError(asyncResp->res); 2371 return; 2372 } 2373 if (firstEntry) 2374 { 2375 firstEntry = false; 2376 } 2377 } 2378 // Confirm that the entry ID matches what was requested 2379 if (idStr != entryID) 2380 { 2381 messages::resourceMissingAtURI(asyncResp->res, entryID); 2382 return; 2383 } 2384 2385 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 2386 asyncResp->res.jsonValue) != 0) 2387 { 2388 messages::internalError(asyncResp->res); 2389 return; 2390 } 2391 }); 2392 } 2393 2394 inline void requestRoutesBMCDumpService(App& app) 2395 { 2396 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 2397 .privileges(redfish::privileges::getLogService) 2398 .methods( 2399 boost::beast::http::verb:: 2400 get)([](const crow::Request&, 2401 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2402 asyncResp->res.jsonValue["@odata.id"] = 2403 "/redfish/v1/Managers/bmc/LogServices/Dump"; 2404 asyncResp->res.jsonValue["@odata.type"] = 2405 "#LogService.v1_2_0.LogService"; 2406 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2407 asyncResp->res.jsonValue["Description"] = "BMC Dump LogService"; 2408 asyncResp->res.jsonValue["Id"] = "Dump"; 2409 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2410 2411 std::pair<std::string, std::string> redfishDateTimeOffset = 2412 crow::utility::getDateTimeOffsetNow(); 2413 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2414 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2415 redfishDateTimeOffset.second; 2416 2417 asyncResp->res.jsonValue["Entries"] = { 2418 {"@odata.id", 2419 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}}; 2420 asyncResp->res.jsonValue["Actions"] = { 2421 {"#LogService.ClearLog", 2422 {{"target", 2423 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog"}}}, 2424 {"#LogService.CollectDiagnosticData", 2425 {{"target", 2426 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData"}}}}; 2427 }); 2428 } 2429 2430 inline void requestRoutesBMCDumpEntryCollection(App& app) 2431 { 2432 2433 /** 2434 * Functions triggers appropriate requests on DBus 2435 */ 2436 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 2437 .privileges(redfish::privileges::getLogEntryCollection) 2438 .methods(boost::beast::http::verb::get)( 2439 [](const crow::Request&, 2440 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2441 asyncResp->res.jsonValue["@odata.type"] = 2442 "#LogEntryCollection.LogEntryCollection"; 2443 asyncResp->res.jsonValue["@odata.id"] = 2444 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; 2445 asyncResp->res.jsonValue["Name"] = "BMC Dump Entries"; 2446 asyncResp->res.jsonValue["Description"] = 2447 "Collection of BMC Dump Entries"; 2448 2449 getDumpEntryCollection(asyncResp, "BMC"); 2450 }); 2451 } 2452 2453 inline void requestRoutesBMCDumpEntry(App& app) 2454 { 2455 BMCWEB_ROUTE(app, 2456 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2457 .privileges(redfish::privileges::getLogEntry) 2458 .methods(boost::beast::http::verb::get)( 2459 [](const crow::Request&, 2460 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2461 const std::string& param) { 2462 getDumpEntryById(asyncResp, param, "BMC"); 2463 }); 2464 BMCWEB_ROUTE(app, 2465 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2466 .privileges(redfish::privileges::deleteLogEntry) 2467 .methods(boost::beast::http::verb::delete_)( 2468 [](const crow::Request&, 2469 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2470 const std::string& param) { 2471 deleteDumpEntry(asyncResp, param, "bmc"); 2472 }); 2473 } 2474 2475 inline void requestRoutesBMCDumpCreate(App& app) 2476 { 2477 2478 BMCWEB_ROUTE( 2479 app, 2480 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 2481 .privileges(redfish::privileges::postLogService) 2482 .methods(boost::beast::http::verb::post)( 2483 [](const crow::Request& req, 2484 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2485 createDump(asyncResp, req, "BMC"); 2486 }); 2487 } 2488 2489 inline void requestRoutesBMCDumpClear(App& app) 2490 { 2491 BMCWEB_ROUTE( 2492 app, 2493 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/") 2494 .privileges(redfish::privileges::postLogService) 2495 .methods(boost::beast::http::verb::post)( 2496 [](const crow::Request&, 2497 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2498 clearDump(asyncResp, "BMC"); 2499 }); 2500 } 2501 2502 inline void requestRoutesSystemDumpService(App& app) 2503 { 2504 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/") 2505 .privileges(redfish::privileges::getLogService) 2506 .methods(boost::beast::http::verb::get)( 2507 [](const crow::Request&, 2508 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2509 2510 { 2511 asyncResp->res.jsonValue["@odata.id"] = 2512 "/redfish/v1/Systems/system/LogServices/Dump"; 2513 asyncResp->res.jsonValue["@odata.type"] = 2514 "#LogService.v1_2_0.LogService"; 2515 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2516 asyncResp->res.jsonValue["Description"] = 2517 "System Dump LogService"; 2518 asyncResp->res.jsonValue["Id"] = "Dump"; 2519 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2520 2521 std::pair<std::string, std::string> redfishDateTimeOffset = 2522 crow::utility::getDateTimeOffsetNow(); 2523 asyncResp->res.jsonValue["DateTime"] = 2524 redfishDateTimeOffset.first; 2525 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2526 redfishDateTimeOffset.second; 2527 2528 asyncResp->res.jsonValue["Entries"] = { 2529 {"@odata.id", 2530 "/redfish/v1/Systems/system/LogServices/Dump/Entries"}}; 2531 asyncResp->res.jsonValue["Actions"] = { 2532 {"#LogService.ClearLog", 2533 {{"target", 2534 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog"}}}, 2535 {"#LogService.CollectDiagnosticData", 2536 {{"target", 2537 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData"}}}}; 2538 }); 2539 } 2540 2541 inline void requestRoutesSystemDumpEntryCollection(App& app) 2542 { 2543 2544 /** 2545 * Functions triggers appropriate requests on DBus 2546 */ 2547 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/") 2548 .privileges(redfish::privileges::getLogEntryCollection) 2549 .methods(boost::beast::http::verb::get)( 2550 [](const crow::Request&, 2551 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2552 asyncResp->res.jsonValue["@odata.type"] = 2553 "#LogEntryCollection.LogEntryCollection"; 2554 asyncResp->res.jsonValue["@odata.id"] = 2555 "/redfish/v1/Systems/system/LogServices/Dump/Entries"; 2556 asyncResp->res.jsonValue["Name"] = "System Dump Entries"; 2557 asyncResp->res.jsonValue["Description"] = 2558 "Collection of System Dump Entries"; 2559 2560 getDumpEntryCollection(asyncResp, "System"); 2561 }); 2562 } 2563 2564 inline void requestRoutesSystemDumpEntry(App& app) 2565 { 2566 BMCWEB_ROUTE(app, 2567 "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/") 2568 .privileges(redfish::privileges::getLogEntry) 2569 2570 .methods(boost::beast::http::verb::get)( 2571 [](const crow::Request&, 2572 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2573 const std::string& param) { 2574 getDumpEntryById(asyncResp, param, "System"); 2575 }); 2576 2577 BMCWEB_ROUTE(app, 2578 "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/") 2579 .privileges(redfish::privileges::deleteLogEntry) 2580 .methods(boost::beast::http::verb::delete_)( 2581 [](const crow::Request&, 2582 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2583 const std::string& param) { 2584 deleteDumpEntry(asyncResp, param, "system"); 2585 }); 2586 } 2587 2588 inline void requestRoutesSystemDumpCreate(App& app) 2589 { 2590 BMCWEB_ROUTE( 2591 app, 2592 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 2593 .privileges(redfish::privileges::postLogService) 2594 .methods(boost::beast::http::verb::post)( 2595 [](const crow::Request& req, 2596 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2597 2598 { createDump(asyncResp, req, "System"); }); 2599 } 2600 2601 inline void requestRoutesSystemDumpClear(App& app) 2602 { 2603 BMCWEB_ROUTE( 2604 app, 2605 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog/") 2606 .privileges(redfish::privileges::postLogService) 2607 .methods(boost::beast::http::verb::post)( 2608 [](const crow::Request&, 2609 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2610 2611 { clearDump(asyncResp, "System"); }); 2612 } 2613 2614 inline void requestRoutesCrashdumpService(App& app) 2615 { 2616 // Note: Deviated from redfish privilege registry for GET & HEAD 2617 // method for security reasons. 2618 /** 2619 * Functions triggers appropriate requests on DBus 2620 */ 2621 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 2622 // This is incorrect, should be: 2623 //.privileges(redfish::privileges::getLogService) 2624 .privileges({{"ConfigureManager"}}) 2625 .methods( 2626 boost::beast::http::verb:: 2627 get)([](const crow::Request&, 2628 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2629 // Copy over the static data to include the entries added by 2630 // SubRoute 2631 asyncResp->res.jsonValue["@odata.id"] = 2632 "/redfish/v1/Systems/system/LogServices/Crashdump"; 2633 asyncResp->res.jsonValue["@odata.type"] = 2634 "#LogService.v1_2_0.LogService"; 2635 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 2636 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 2637 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 2638 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2639 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 2640 2641 std::pair<std::string, std::string> redfishDateTimeOffset = 2642 crow::utility::getDateTimeOffsetNow(); 2643 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2644 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2645 redfishDateTimeOffset.second; 2646 2647 asyncResp->res.jsonValue["Entries"] = { 2648 {"@odata.id", 2649 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 2650 asyncResp->res.jsonValue["Actions"] = { 2651 {"#LogService.ClearLog", 2652 {{"target", 2653 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog"}}}, 2654 {"#LogService.CollectDiagnosticData", 2655 {{"target", 2656 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData"}}}}; 2657 }); 2658 } 2659 2660 void inline requestRoutesCrashdumpClear(App& app) 2661 { 2662 BMCWEB_ROUTE( 2663 app, 2664 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog/") 2665 // This is incorrect, should be: 2666 //.privileges(redfish::privileges::postLogService) 2667 .privileges({{"ConfigureComponents"}}) 2668 .methods(boost::beast::http::verb::post)( 2669 [](const crow::Request&, 2670 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2671 crow::connections::systemBus->async_method_call( 2672 [asyncResp](const boost::system::error_code ec, 2673 const std::string&) { 2674 if (ec) 2675 { 2676 messages::internalError(asyncResp->res); 2677 return; 2678 } 2679 messages::success(asyncResp->res); 2680 }, 2681 crashdumpObject, crashdumpPath, deleteAllInterface, 2682 "DeleteAll"); 2683 }); 2684 } 2685 2686 static void 2687 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2688 const std::string& logID, nlohmann::json& logEntryJson) 2689 { 2690 auto getStoredLogCallback = 2691 [asyncResp, logID, &logEntryJson]( 2692 const boost::system::error_code ec, 2693 const std::vector< 2694 std::pair<std::string, dbus::utility::DbusVariantType>>& 2695 params) { 2696 if (ec) 2697 { 2698 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 2699 if (ec.value() == 2700 boost::system::linux_error::bad_request_descriptor) 2701 { 2702 messages::resourceNotFound(asyncResp->res, "LogEntry", 2703 logID); 2704 } 2705 else 2706 { 2707 messages::internalError(asyncResp->res); 2708 } 2709 return; 2710 } 2711 2712 std::string timestamp{}; 2713 std::string filename{}; 2714 std::string logfile{}; 2715 parseCrashdumpParameters(params, filename, timestamp, logfile); 2716 2717 if (filename.empty() || timestamp.empty()) 2718 { 2719 messages::resourceMissingAtURI(asyncResp->res, logID); 2720 return; 2721 } 2722 2723 std::string crashdumpURI = 2724 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 2725 logID + "/" + filename; 2726 logEntryJson = {{"@odata.type", "#LogEntry.v1_7_0.LogEntry"}, 2727 {"@odata.id", "/redfish/v1/Systems/system/" 2728 "LogServices/Crashdump/Entries/" + 2729 logID}, 2730 {"Name", "CPU Crashdump"}, 2731 {"Id", logID}, 2732 {"EntryType", "Oem"}, 2733 {"AdditionalDataURI", std::move(crashdumpURI)}, 2734 {"DiagnosticDataType", "OEM"}, 2735 {"OEMDiagnosticDataType", "PECICrashdump"}, 2736 {"Created", std::move(timestamp)}}; 2737 }; 2738 crow::connections::systemBus->async_method_call( 2739 std::move(getStoredLogCallback), crashdumpObject, 2740 crashdumpPath + std::string("/") + logID, 2741 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2742 } 2743 2744 inline void requestRoutesCrashdumpEntryCollection(App& app) 2745 { 2746 // Note: Deviated from redfish privilege registry for GET & HEAD 2747 // method for security reasons. 2748 /** 2749 * Functions triggers appropriate requests on DBus 2750 */ 2751 BMCWEB_ROUTE(app, 2752 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 2753 // This is incorrect, should be. 2754 //.privileges(redfish::privileges::postLogEntryCollection) 2755 .privileges({{"ConfigureComponents"}}) 2756 .methods( 2757 boost::beast::http::verb:: 2758 get)([](const crow::Request&, 2759 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2760 // Collections don't include the static data added by SubRoute 2761 // because it has a duplicate entry for members 2762 auto getLogEntriesCallback = [asyncResp]( 2763 const boost::system::error_code ec, 2764 const std::vector<std::string>& 2765 resp) { 2766 if (ec) 2767 { 2768 if (ec.value() != 2769 boost::system::errc::no_such_file_or_directory) 2770 { 2771 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 2772 << ec.message(); 2773 messages::internalError(asyncResp->res); 2774 return; 2775 } 2776 } 2777 asyncResp->res.jsonValue["@odata.type"] = 2778 "#LogEntryCollection.LogEntryCollection"; 2779 asyncResp->res.jsonValue["@odata.id"] = 2780 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 2781 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 2782 asyncResp->res.jsonValue["Description"] = 2783 "Collection of Crashdump Entries"; 2784 nlohmann::json& logEntryArray = 2785 asyncResp->res.jsonValue["Members"]; 2786 logEntryArray = nlohmann::json::array(); 2787 std::vector<std::string> logIDs; 2788 // Get the list of log entries and build up an empty array big 2789 // enough to hold them 2790 for (const std::string& objpath : resp) 2791 { 2792 // Get the log ID 2793 std::size_t lastPos = objpath.rfind('/'); 2794 if (lastPos == std::string::npos) 2795 { 2796 continue; 2797 } 2798 logIDs.emplace_back(objpath.substr(lastPos + 1)); 2799 2800 // Add a space for the log entry to the array 2801 logEntryArray.push_back({}); 2802 } 2803 // Now go through and set up async calls to fill in the entries 2804 size_t index = 0; 2805 for (const std::string& logID : logIDs) 2806 { 2807 // Add the log entry to the array 2808 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 2809 } 2810 asyncResp->res.jsonValue["Members@odata.count"] = 2811 logEntryArray.size(); 2812 }; 2813 crow::connections::systemBus->async_method_call( 2814 std::move(getLogEntriesCallback), 2815 "xyz.openbmc_project.ObjectMapper", 2816 "/xyz/openbmc_project/object_mapper", 2817 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 2818 std::array<const char*, 1>{crashdumpInterface}); 2819 }); 2820 } 2821 2822 inline void requestRoutesCrashdumpEntry(App& app) 2823 { 2824 // Note: Deviated from redfish privilege registry for GET & HEAD 2825 // method for security reasons. 2826 2827 BMCWEB_ROUTE( 2828 app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/") 2829 // this is incorrect, should be 2830 // .privileges(redfish::privileges::getLogEntry) 2831 .privileges({{"ConfigureComponents"}}) 2832 .methods(boost::beast::http::verb::get)( 2833 [](const crow::Request&, 2834 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2835 const std::string& param) { 2836 const std::string& logID = param; 2837 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 2838 }); 2839 } 2840 2841 inline void requestRoutesCrashdumpFile(App& app) 2842 { 2843 // Note: Deviated from redfish privilege registry for GET & HEAD 2844 // method for security reasons. 2845 BMCWEB_ROUTE( 2846 app, 2847 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/<str>/") 2848 .privileges(redfish::privileges::getLogEntry) 2849 .methods(boost::beast::http::verb::get)( 2850 [](const crow::Request&, 2851 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2852 const std::string& logID, const std::string& fileName) { 2853 auto getStoredLogCallback = 2854 [asyncResp, logID, fileName]( 2855 const boost::system::error_code ec, 2856 const std::vector<std::pair< 2857 std::string, dbus::utility::DbusVariantType>>& 2858 resp) { 2859 if (ec) 2860 { 2861 BMCWEB_LOG_DEBUG << "failed to get log ec: " 2862 << ec.message(); 2863 messages::internalError(asyncResp->res); 2864 return; 2865 } 2866 2867 std::string dbusFilename{}; 2868 std::string dbusTimestamp{}; 2869 std::string dbusFilepath{}; 2870 2871 parseCrashdumpParameters(resp, dbusFilename, 2872 dbusTimestamp, dbusFilepath); 2873 2874 if (dbusFilename.empty() || dbusTimestamp.empty() || 2875 dbusFilepath.empty()) 2876 { 2877 messages::resourceMissingAtURI(asyncResp->res, 2878 fileName); 2879 return; 2880 } 2881 2882 // Verify the file name parameter is correct 2883 if (fileName != dbusFilename) 2884 { 2885 messages::resourceMissingAtURI(asyncResp->res, 2886 fileName); 2887 return; 2888 } 2889 2890 if (!std::filesystem::exists(dbusFilepath)) 2891 { 2892 messages::resourceMissingAtURI(asyncResp->res, 2893 fileName); 2894 return; 2895 } 2896 std::ifstream ifs(dbusFilepath, 2897 std::ios::in | std::ios::binary); 2898 asyncResp->res.body() = std::string( 2899 std::istreambuf_iterator<char>{ifs}, {}); 2900 2901 // Configure this to be a file download when accessed 2902 // from a browser 2903 asyncResp->res.addHeader("Content-Disposition", 2904 "attachment"); 2905 }; 2906 crow::connections::systemBus->async_method_call( 2907 std::move(getStoredLogCallback), crashdumpObject, 2908 crashdumpPath + std::string("/") + logID, 2909 "org.freedesktop.DBus.Properties", "GetAll", 2910 crashdumpInterface); 2911 }); 2912 } 2913 2914 inline void requestRoutesCrashdumpCollect(App& app) 2915 { 2916 // Note: Deviated from redfish privilege registry for GET & HEAD 2917 // method for security reasons. 2918 BMCWEB_ROUTE( 2919 app, 2920 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/") 2921 // The below is incorrect; Should be ConfigureManager 2922 //.privileges(redfish::privileges::postLogService) 2923 .privileges({{"ConfigureComponents"}}) 2924 .methods( 2925 boost::beast::http::verb:: 2926 post)([](const crow::Request& req, 2927 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2928 std::string diagnosticDataType; 2929 std::string oemDiagnosticDataType; 2930 if (!redfish::json_util::readJson( 2931 req, asyncResp->res, "DiagnosticDataType", 2932 diagnosticDataType, "OEMDiagnosticDataType", 2933 oemDiagnosticDataType)) 2934 { 2935 return; 2936 } 2937 2938 if (diagnosticDataType != "OEM") 2939 { 2940 BMCWEB_LOG_ERROR 2941 << "Only OEM DiagnosticDataType supported for Crashdump"; 2942 messages::actionParameterValueFormatError( 2943 asyncResp->res, diagnosticDataType, "DiagnosticDataType", 2944 "CollectDiagnosticData"); 2945 return; 2946 } 2947 2948 auto collectCrashdumpCallback = [asyncResp, 2949 payload(task::Payload(req))]( 2950 const boost::system::error_code 2951 ec, 2952 const std::string&) mutable { 2953 if (ec) 2954 { 2955 if (ec.value() == 2956 boost::system::errc::operation_not_supported) 2957 { 2958 messages::resourceInStandby(asyncResp->res); 2959 } 2960 else if (ec.value() == 2961 boost::system::errc::device_or_resource_busy) 2962 { 2963 messages::serviceTemporarilyUnavailable(asyncResp->res, 2964 "60"); 2965 } 2966 else 2967 { 2968 messages::internalError(asyncResp->res); 2969 } 2970 return; 2971 } 2972 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2973 [](boost::system::error_code err, 2974 sdbusplus::message::message&, 2975 const std::shared_ptr<task::TaskData>& taskData) { 2976 if (!err) 2977 { 2978 taskData->messages.emplace_back( 2979 messages::taskCompletedOK( 2980 std::to_string(taskData->index))); 2981 taskData->state = "Completed"; 2982 } 2983 return task::completed; 2984 }, 2985 "type='signal',interface='org.freedesktop.DBus." 2986 "Properties'," 2987 "member='PropertiesChanged',arg0namespace='com.intel.crashdump'"); 2988 task->startTimer(std::chrono::minutes(5)); 2989 task->populateResp(asyncResp->res); 2990 task->payload.emplace(std::move(payload)); 2991 }; 2992 2993 if (oemDiagnosticDataType == "OnDemand") 2994 { 2995 crow::connections::systemBus->async_method_call( 2996 std::move(collectCrashdumpCallback), crashdumpObject, 2997 crashdumpPath, crashdumpOnDemandInterface, 2998 "GenerateOnDemandLog"); 2999 } 3000 else if (oemDiagnosticDataType == "Telemetry") 3001 { 3002 crow::connections::systemBus->async_method_call( 3003 std::move(collectCrashdumpCallback), crashdumpObject, 3004 crashdumpPath, crashdumpTelemetryInterface, 3005 "GenerateTelemetryLog"); 3006 } 3007 else 3008 { 3009 BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: " 3010 << oemDiagnosticDataType; 3011 messages::actionParameterValueFormatError( 3012 asyncResp->res, oemDiagnosticDataType, 3013 "OEMDiagnosticDataType", "CollectDiagnosticData"); 3014 return; 3015 } 3016 }); 3017 } 3018 3019 /** 3020 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 3021 */ 3022 inline void requestRoutesDBusLogServiceActionsClear(App& app) 3023 { 3024 /** 3025 * Function handles POST method request. 3026 * The Clear Log actions does not require any parameter.The action deletes 3027 * all entries found in the Entries collection for this Log Service. 3028 */ 3029 3030 BMCWEB_ROUTE( 3031 app, 3032 "/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog/") 3033 .privileges(redfish::privileges::postLogService) 3034 .methods(boost::beast::http::verb::post)( 3035 [](const crow::Request&, 3036 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3037 BMCWEB_LOG_DEBUG << "Do delete all entries."; 3038 3039 // Process response from Logging service. 3040 auto respHandler = [asyncResp]( 3041 const boost::system::error_code ec) { 3042 BMCWEB_LOG_DEBUG 3043 << "doClearLog resp_handler callback: Done"; 3044 if (ec) 3045 { 3046 // TODO Handle for specific error code 3047 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " 3048 << ec; 3049 asyncResp->res.result( 3050 boost::beast::http::status::internal_server_error); 3051 return; 3052 } 3053 3054 asyncResp->res.result( 3055 boost::beast::http::status::no_content); 3056 }; 3057 3058 // Make call to Logging service to request Clear Log 3059 crow::connections::systemBus->async_method_call( 3060 respHandler, "xyz.openbmc_project.Logging", 3061 "/xyz/openbmc_project/logging", 3062 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3063 }); 3064 } 3065 3066 /**************************************************** 3067 * Redfish PostCode interfaces 3068 * using DBUS interface: getPostCodesTS 3069 ******************************************************/ 3070 inline void requestRoutesPostCodesLogService(App& app) 3071 { 3072 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 3073 .privileges(redfish::privileges::getLogService) 3074 .methods( 3075 boost::beast::http::verb:: 3076 get)([](const crow::Request&, 3077 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3078 asyncResp->res.jsonValue = { 3079 {"@odata.id", 3080 "/redfish/v1/Systems/system/LogServices/PostCodes"}, 3081 {"@odata.type", "#LogService.v1_1_0.LogService"}, 3082 {"Name", "POST Code Log Service"}, 3083 {"Description", "POST Code Log Service"}, 3084 {"Id", "BIOS POST Code Log"}, 3085 {"OverWritePolicy", "WrapsWhenFull"}, 3086 {"Entries", 3087 {{"@odata.id", 3088 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 3089 3090 std::pair<std::string, std::string> redfishDateTimeOffset = 3091 crow::utility::getDateTimeOffsetNow(); 3092 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3093 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3094 redfishDateTimeOffset.second; 3095 3096 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 3097 {"target", 3098 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}}; 3099 }); 3100 } 3101 3102 inline void requestRoutesPostCodesClear(App& app) 3103 { 3104 BMCWEB_ROUTE( 3105 app, 3106 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog/") 3107 // The following privilege is incorrect; It should be ConfigureManager 3108 //.privileges(redfish::privileges::postLogService) 3109 .privileges({{"ConfigureComponents"}}) 3110 .methods(boost::beast::http::verb::post)( 3111 [](const crow::Request&, 3112 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3113 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 3114 3115 // Make call to post-code service to request clear all 3116 crow::connections::systemBus->async_method_call( 3117 [asyncResp](const boost::system::error_code ec) { 3118 if (ec) 3119 { 3120 // TODO Handle for specific error code 3121 BMCWEB_LOG_ERROR 3122 << "doClearPostCodes resp_handler got error " 3123 << ec; 3124 asyncResp->res.result(boost::beast::http::status:: 3125 internal_server_error); 3126 messages::internalError(asyncResp->res); 3127 return; 3128 } 3129 }, 3130 "xyz.openbmc_project.State.Boot.PostCode0", 3131 "/xyz/openbmc_project/State/Boot/PostCode0", 3132 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3133 }); 3134 } 3135 3136 static void fillPostCodeEntry( 3137 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3138 const boost::container::flat_map< 3139 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 3140 const uint16_t bootIndex, const uint64_t codeIndex = 0, 3141 const uint64_t skip = 0, const uint64_t top = 0) 3142 { 3143 // Get the Message from the MessageRegistry 3144 const message_registries::Message* message = 3145 message_registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 3146 3147 uint64_t currentCodeIndex = 0; 3148 nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"]; 3149 3150 uint64_t firstCodeTimeUs = 0; 3151 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3152 code : postcode) 3153 { 3154 currentCodeIndex++; 3155 std::string postcodeEntryID = 3156 "B" + std::to_string(bootIndex) + "-" + 3157 std::to_string(currentCodeIndex); // 1 based index in EntryID string 3158 3159 uint64_t usecSinceEpoch = code.first; 3160 uint64_t usTimeOffset = 0; 3161 3162 if (1 == currentCodeIndex) 3163 { // already incremented 3164 firstCodeTimeUs = code.first; 3165 } 3166 else 3167 { 3168 usTimeOffset = code.first - firstCodeTimeUs; 3169 } 3170 3171 // skip if no specific codeIndex is specified and currentCodeIndex does 3172 // not fall between top and skip 3173 if ((codeIndex == 0) && 3174 (currentCodeIndex <= skip || currentCodeIndex > top)) 3175 { 3176 continue; 3177 } 3178 3179 // skip if a specific codeIndex is specified and does not match the 3180 // currentIndex 3181 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 3182 { 3183 // This is done for simplicity. 1st entry is needed to calculate 3184 // time offset. To improve efficiency, one can get to the entry 3185 // directly (possibly with flatmap's nth method) 3186 continue; 3187 } 3188 3189 // currentCodeIndex is within top and skip or equal to specified code 3190 // index 3191 3192 // Get the Created time from the timestamp 3193 std::string entryTimeStr; 3194 entryTimeStr = 3195 crow::utility::getDateTimeUint(usecSinceEpoch / 1000 / 1000); 3196 3197 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 3198 std::ostringstream hexCode; 3199 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 3200 << std::get<0>(code.second); 3201 std::ostringstream timeOffsetStr; 3202 // Set Fixed -Point Notation 3203 timeOffsetStr << std::fixed; 3204 // Set precision to 4 digits 3205 timeOffsetStr << std::setprecision(4); 3206 // Add double to stream 3207 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 3208 std::vector<std::string> messageArgs = { 3209 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 3210 3211 // Get MessageArgs template from message registry 3212 std::string msg; 3213 if (message != nullptr) 3214 { 3215 msg = message->message; 3216 3217 // fill in this post code value 3218 int i = 0; 3219 for (const std::string& messageArg : messageArgs) 3220 { 3221 std::string argStr = "%" + std::to_string(++i); 3222 size_t argPos = msg.find(argStr); 3223 if (argPos != std::string::npos) 3224 { 3225 msg.replace(argPos, argStr.length(), messageArg); 3226 } 3227 } 3228 } 3229 3230 // Get Severity template from message registry 3231 std::string severity; 3232 if (message != nullptr) 3233 { 3234 severity = message->severity; 3235 } 3236 3237 // add to AsyncResp 3238 logEntryArray.push_back({}); 3239 nlohmann::json& bmcLogEntry = logEntryArray.back(); 3240 bmcLogEntry = { 3241 {"@odata.type", "#LogEntry.v1_8_0.LogEntry"}, 3242 {"@odata.id", 3243 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + 3244 postcodeEntryID}, 3245 {"Name", "POST Code Log Entry"}, 3246 {"Id", postcodeEntryID}, 3247 {"Message", std::move(msg)}, 3248 {"MessageId", "OpenBMC.0.2.BIOSPOSTCode"}, 3249 {"MessageArgs", std::move(messageArgs)}, 3250 {"EntryType", "Event"}, 3251 {"Severity", std::move(severity)}, 3252 {"Created", entryTimeStr}}; 3253 if (!std::get<std::vector<uint8_t>>(code.second).empty()) 3254 { 3255 bmcLogEntry["AdditionalDataURI"] = 3256 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + 3257 postcodeEntryID + "/attachment"; 3258 } 3259 } 3260 } 3261 3262 static void getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3263 const uint16_t bootIndex, 3264 const uint64_t codeIndex) 3265 { 3266 crow::connections::systemBus->async_method_call( 3267 [aResp, bootIndex, 3268 codeIndex](const boost::system::error_code ec, 3269 const boost::container::flat_map< 3270 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3271 postcode) { 3272 if (ec) 3273 { 3274 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3275 messages::internalError(aResp->res); 3276 return; 3277 } 3278 3279 // skip the empty postcode boots 3280 if (postcode.empty()) 3281 { 3282 return; 3283 } 3284 3285 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 3286 3287 aResp->res.jsonValue["Members@odata.count"] = 3288 aResp->res.jsonValue["Members"].size(); 3289 }, 3290 "xyz.openbmc_project.State.Boot.PostCode0", 3291 "/xyz/openbmc_project/State/Boot/PostCode0", 3292 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3293 bootIndex); 3294 } 3295 3296 static void getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3297 const uint16_t bootIndex, 3298 const uint16_t bootCount, 3299 const uint64_t entryCount, const uint64_t skip, 3300 const uint64_t top) 3301 { 3302 crow::connections::systemBus->async_method_call( 3303 [aResp, bootIndex, bootCount, entryCount, skip, 3304 top](const boost::system::error_code ec, 3305 const boost::container::flat_map< 3306 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3307 postcode) { 3308 if (ec) 3309 { 3310 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3311 messages::internalError(aResp->res); 3312 return; 3313 } 3314 3315 uint64_t endCount = entryCount; 3316 if (!postcode.empty()) 3317 { 3318 endCount = entryCount + postcode.size(); 3319 3320 if ((skip < endCount) && ((top + skip) > entryCount)) 3321 { 3322 uint64_t thisBootSkip = 3323 std::max(skip, entryCount) - entryCount; 3324 uint64_t thisBootTop = 3325 std::min(top + skip, endCount) - entryCount; 3326 3327 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 3328 thisBootSkip, thisBootTop); 3329 } 3330 aResp->res.jsonValue["Members@odata.count"] = endCount; 3331 } 3332 3333 // continue to previous bootIndex 3334 if (bootIndex < bootCount) 3335 { 3336 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 3337 bootCount, endCount, skip, top); 3338 } 3339 else 3340 { 3341 aResp->res.jsonValue["Members@odata.nextLink"] = 3342 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" + 3343 std::to_string(skip + top); 3344 } 3345 }, 3346 "xyz.openbmc_project.State.Boot.PostCode0", 3347 "/xyz/openbmc_project/State/Boot/PostCode0", 3348 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3349 bootIndex); 3350 } 3351 3352 static void 3353 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3354 const uint64_t skip, const uint64_t top) 3355 { 3356 uint64_t entryCount = 0; 3357 sdbusplus::asio::getProperty<uint16_t>( 3358 *crow::connections::systemBus, 3359 "xyz.openbmc_project.State.Boot.PostCode0", 3360 "/xyz/openbmc_project/State/Boot/PostCode0", 3361 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 3362 [aResp, entryCount, skip, top](const boost::system::error_code ec, 3363 const uint16_t bootCount) { 3364 if (ec) 3365 { 3366 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3367 messages::internalError(aResp->res); 3368 return; 3369 } 3370 getPostCodeForBoot(aResp, 1, bootCount, entryCount, skip, top); 3371 }); 3372 } 3373 3374 inline void requestRoutesPostCodesEntryCollection(App& app) 3375 { 3376 BMCWEB_ROUTE(app, 3377 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 3378 .privileges(redfish::privileges::getLogEntryCollection) 3379 .methods(boost::beast::http::verb::get)( 3380 [](const crow::Request& req, 3381 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3382 asyncResp->res.jsonValue["@odata.type"] = 3383 "#LogEntryCollection.LogEntryCollection"; 3384 asyncResp->res.jsonValue["@odata.id"] = 3385 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3386 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3387 asyncResp->res.jsonValue["Description"] = 3388 "Collection of POST Code Log Entries"; 3389 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3390 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3391 3392 uint64_t skip = 0; 3393 uint64_t top = maxEntriesPerPage; // Show max entries by default 3394 if (!getSkipParam(asyncResp, req, skip)) 3395 { 3396 return; 3397 } 3398 if (!getTopParam(asyncResp, req, top)) 3399 { 3400 return; 3401 } 3402 getCurrentBootNumber(asyncResp, skip, top); 3403 }); 3404 } 3405 3406 /** 3407 * @brief Parse post code ID and get the current value and index value 3408 * eg: postCodeID=B1-2, currentValue=1, index=2 3409 * 3410 * @param[in] postCodeID Post Code ID 3411 * @param[out] currentValue Current value 3412 * @param[out] index Index value 3413 * 3414 * @return bool true if the parsing is successful, false the parsing fails 3415 */ 3416 inline static bool parsePostCode(const std::string& postCodeID, 3417 uint64_t& currentValue, uint16_t& index) 3418 { 3419 std::vector<std::string> split; 3420 boost::algorithm::split(split, postCodeID, boost::is_any_of("-")); 3421 if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B') 3422 { 3423 return false; 3424 } 3425 3426 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 3427 const char* start = split[0].data() + 1; 3428 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 3429 const char* end = split[0].data() + split[0].size(); 3430 auto [ptrIndex, ecIndex] = std::from_chars(start, end, index); 3431 3432 if (ptrIndex != end || ecIndex != std::errc()) 3433 { 3434 return false; 3435 } 3436 3437 start = split[1].data(); 3438 3439 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) 3440 end = split[1].data() + split[1].size(); 3441 auto [ptrValue, ecValue] = std::from_chars(start, end, currentValue); 3442 if (ptrValue != end || ecValue != std::errc()) 3443 { 3444 return false; 3445 } 3446 3447 return true; 3448 } 3449 3450 inline void requestRoutesPostCodesEntryAdditionalData(App& app) 3451 { 3452 BMCWEB_ROUTE( 3453 app, 3454 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/attachment/") 3455 .privileges(redfish::privileges::getLogEntry) 3456 .methods(boost::beast::http::verb::get)( 3457 [](const crow::Request& req, 3458 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3459 const std::string& postCodeID) { 3460 if (!http_helpers::isOctetAccepted( 3461 req.getHeaderValue("Accept"))) 3462 { 3463 asyncResp->res.result( 3464 boost::beast::http::status::bad_request); 3465 return; 3466 } 3467 3468 uint64_t currentValue = 0; 3469 uint16_t index = 0; 3470 if (!parsePostCode(postCodeID, currentValue, index)) 3471 { 3472 messages::resourceNotFound(asyncResp->res, "LogEntry", 3473 postCodeID); 3474 return; 3475 } 3476 3477 crow::connections::systemBus->async_method_call( 3478 [asyncResp, postCodeID, currentValue]( 3479 const boost::system::error_code ec, 3480 const std::vector<std::tuple< 3481 uint64_t, std::vector<uint8_t>>>& postcodes) { 3482 if (ec.value() == EBADR) 3483 { 3484 messages::resourceNotFound(asyncResp->res, 3485 "LogEntry", postCodeID); 3486 return; 3487 } 3488 if (ec) 3489 { 3490 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3491 messages::internalError(asyncResp->res); 3492 return; 3493 } 3494 3495 size_t value = static_cast<size_t>(currentValue) - 1; 3496 if (value == std::string::npos || 3497 postcodes.size() < currentValue) 3498 { 3499 BMCWEB_LOG_ERROR << "Wrong currentValue value"; 3500 messages::resourceNotFound(asyncResp->res, 3501 "LogEntry", postCodeID); 3502 return; 3503 } 3504 3505 auto& [tID, c] = postcodes[value]; 3506 if (c.empty()) 3507 { 3508 BMCWEB_LOG_INFO << "No found post code data"; 3509 messages::resourceNotFound(asyncResp->res, 3510 "LogEntry", postCodeID); 3511 return; 3512 } 3513 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 3514 const char* d = reinterpret_cast<const char*>(c.data()); 3515 std::string_view strData(d, c.size()); 3516 3517 asyncResp->res.addHeader("Content-Type", 3518 "application/octet-stream"); 3519 asyncResp->res.addHeader("Content-Transfer-Encoding", 3520 "Base64"); 3521 asyncResp->res.body() = 3522 crow::utility::base64encode(strData); 3523 }, 3524 "xyz.openbmc_project.State.Boot.PostCode0", 3525 "/xyz/openbmc_project/State/Boot/PostCode0", 3526 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", 3527 index); 3528 }); 3529 } 3530 3531 inline void requestRoutesPostCodesEntry(App& app) 3532 { 3533 BMCWEB_ROUTE( 3534 app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/") 3535 .privileges(redfish::privileges::getLogEntry) 3536 .methods(boost::beast::http::verb::get)( 3537 [](const crow::Request&, 3538 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3539 const std::string& targetID) { 3540 uint16_t bootIndex = 0; 3541 uint64_t codeIndex = 0; 3542 if (!parsePostCode(targetID, codeIndex, bootIndex)) 3543 { 3544 // Requested ID was not found 3545 messages::resourceMissingAtURI(asyncResp->res, targetID); 3546 return; 3547 } 3548 if (bootIndex == 0 || codeIndex == 0) 3549 { 3550 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 3551 << targetID; 3552 } 3553 3554 asyncResp->res.jsonValue["@odata.type"] = 3555 "#LogEntry.v1_4_0.LogEntry"; 3556 asyncResp->res.jsonValue["@odata.id"] = 3557 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3558 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3559 asyncResp->res.jsonValue["Description"] = 3560 "Collection of POST Code Log Entries"; 3561 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3562 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3563 3564 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 3565 }); 3566 } 3567 3568 } // namespace redfish 3569