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