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