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