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