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 auto [ptr, ec] = std::from_chars( 2013 targetID.data(), targetID.data() + targetID.size(), idInt); 2014 if (ec == std::errc::invalid_argument) 2015 { 2016 messages::resourceMissingAtURI(asyncResp->res, targetID); 2017 return; 2018 } 2019 if (ec == std::errc::result_out_of_range) 2020 { 2021 messages::resourceMissingAtURI(asyncResp->res, targetID); 2022 return; 2023 } 2024 2025 std::vector<std::filesystem::path> hostLoggerFiles; 2026 if (!getHostLoggerFiles(hostLoggerFolderPath, hostLoggerFiles)) 2027 { 2028 BMCWEB_LOG_ERROR << "fail to get host log file path"; 2029 return; 2030 } 2031 2032 size_t logCount = 0; 2033 uint64_t top = 1; 2034 std::vector<std::string> logEntries; 2035 // We can get specific entry by skip and top. For example, if we 2036 // want to get nth entry, we can set skip = n-1 and top = 1 to 2037 // get that entry 2038 if (!getHostLoggerEntries(hostLoggerFiles, idInt, top, 2039 logEntries, logCount)) 2040 { 2041 messages::internalError(asyncResp->res); 2042 return; 2043 } 2044 2045 if (!logEntries.empty()) 2046 { 2047 fillHostLoggerEntryJson(targetID, logEntries[0], 2048 asyncResp->res.jsonValue); 2049 return; 2050 } 2051 2052 // Requested ID was not found 2053 messages::resourceMissingAtURI(asyncResp->res, targetID); 2054 }); 2055 } 2056 2057 inline void requestRoutesBMCLogServiceCollection(App& app) 2058 { 2059 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/") 2060 .privileges(redfish::privileges::getLogServiceCollection) 2061 .methods(boost::beast::http::verb::get)( 2062 [](const crow::Request&, 2063 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2064 // Collections don't include the static data added by SubRoute 2065 // because it has a duplicate entry for members 2066 asyncResp->res.jsonValue["@odata.type"] = 2067 "#LogServiceCollection.LogServiceCollection"; 2068 asyncResp->res.jsonValue["@odata.id"] = 2069 "/redfish/v1/Managers/bmc/LogServices"; 2070 asyncResp->res.jsonValue["Name"] = 2071 "Open BMC Log Services Collection"; 2072 asyncResp->res.jsonValue["Description"] = 2073 "Collection of LogServices for this Manager"; 2074 nlohmann::json& logServiceArray = 2075 asyncResp->res.jsonValue["Members"]; 2076 logServiceArray = nlohmann::json::array(); 2077 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 2078 logServiceArray.push_back( 2079 {{"@odata.id", 2080 "/redfish/v1/Managers/bmc/LogServices/Dump"}}); 2081 #endif 2082 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 2083 logServiceArray.push_back( 2084 {{"@odata.id", 2085 "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 2086 #endif 2087 asyncResp->res.jsonValue["Members@odata.count"] = 2088 logServiceArray.size(); 2089 }); 2090 } 2091 2092 inline void requestRoutesBMCJournalLogService(App& app) 2093 { 2094 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 2095 .privileges(redfish::privileges::getLogService) 2096 .methods(boost::beast::http::verb::get)( 2097 [](const crow::Request&, 2098 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2099 2100 { 2101 asyncResp->res.jsonValue["@odata.type"] = 2102 "#LogService.v1_1_0.LogService"; 2103 asyncResp->res.jsonValue["@odata.id"] = 2104 "/redfish/v1/Managers/bmc/LogServices/Journal"; 2105 asyncResp->res.jsonValue["Name"] = 2106 "Open BMC Journal Log Service"; 2107 asyncResp->res.jsonValue["Description"] = 2108 "BMC Journal Log Service"; 2109 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 2110 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2111 2112 std::pair<std::string, std::string> redfishDateTimeOffset = 2113 crow::utility::getDateTimeOffsetNow(); 2114 asyncResp->res.jsonValue["DateTime"] = 2115 redfishDateTimeOffset.first; 2116 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2117 redfishDateTimeOffset.second; 2118 2119 asyncResp->res.jsonValue["Entries"] = { 2120 {"@odata.id", 2121 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 2122 }); 2123 } 2124 2125 static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 2126 sd_journal* journal, 2127 nlohmann::json& bmcJournalLogEntryJson) 2128 { 2129 // Get the Log Entry contents 2130 int ret = 0; 2131 2132 std::string message; 2133 std::string_view syslogID; 2134 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 2135 if (ret < 0) 2136 { 2137 BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: " 2138 << strerror(-ret); 2139 } 2140 if (!syslogID.empty()) 2141 { 2142 message += std::string(syslogID) + ": "; 2143 } 2144 2145 std::string_view msg; 2146 ret = getJournalMetadata(journal, "MESSAGE", msg); 2147 if (ret < 0) 2148 { 2149 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 2150 return 1; 2151 } 2152 message += std::string(msg); 2153 2154 // Get the severity from the PRIORITY field 2155 long int severity = 8; // Default to an invalid priority 2156 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 2157 if (ret < 0) 2158 { 2159 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 2160 } 2161 2162 // Get the Created time from the timestamp 2163 std::string entryTimeStr; 2164 if (!getEntryTimestamp(journal, entryTimeStr)) 2165 { 2166 return 1; 2167 } 2168 2169 // Fill in the log entry with the gathered data 2170 bmcJournalLogEntryJson = { 2171 {"@odata.type", "#LogEntry.v1_8_0.LogEntry"}, 2172 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 2173 bmcJournalLogEntryID}, 2174 {"Name", "BMC Journal Entry"}, 2175 {"Id", bmcJournalLogEntryID}, 2176 {"Message", std::move(message)}, 2177 {"EntryType", "Oem"}, 2178 {"Severity", severity <= 2 ? "Critical" 2179 : severity <= 4 ? "Warning" 2180 : "OK"}, 2181 {"OemRecordFormat", "BMC Journal Entry"}, 2182 {"Created", std::move(entryTimeStr)}}; 2183 return 0; 2184 } 2185 2186 inline void requestRoutesBMCJournalLogEntryCollection(App& app) 2187 { 2188 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 2189 .privileges(redfish::privileges::getLogEntryCollection) 2190 .methods( 2191 boost::beast::http::verb:: 2192 get)([](const crow::Request& req, 2193 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2194 static constexpr const long maxEntriesPerPage = 1000; 2195 uint64_t skip = 0; 2196 uint64_t top = maxEntriesPerPage; // Show max entries by default 2197 if (!getSkipParam(asyncResp, req, skip)) 2198 { 2199 return; 2200 } 2201 if (!getTopParam(asyncResp, req, top)) 2202 { 2203 return; 2204 } 2205 // Collections don't include the static data added by SubRoute 2206 // because it has a duplicate entry for members 2207 asyncResp->res.jsonValue["@odata.type"] = 2208 "#LogEntryCollection.LogEntryCollection"; 2209 asyncResp->res.jsonValue["@odata.id"] = 2210 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 2211 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 2212 asyncResp->res.jsonValue["Description"] = 2213 "Collection of BMC Journal Entries"; 2214 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2215 logEntryArray = nlohmann::json::array(); 2216 2217 // Go through the journal and use the timestamp to create a 2218 // unique ID for each entry 2219 sd_journal* journalTmp = nullptr; 2220 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2221 if (ret < 0) 2222 { 2223 BMCWEB_LOG_ERROR << "failed to open journal: " 2224 << strerror(-ret); 2225 messages::internalError(asyncResp->res); 2226 return; 2227 } 2228 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 2229 journalTmp, sd_journal_close); 2230 journalTmp = nullptr; 2231 uint64_t entryCount = 0; 2232 // Reset the unique ID on the first entry 2233 bool firstEntry = true; 2234 SD_JOURNAL_FOREACH(journal.get()) 2235 { 2236 entryCount++; 2237 // Handle paging using skip (number of entries to skip from 2238 // the start) and top (number of entries to display) 2239 if (entryCount <= skip || entryCount > skip + top) 2240 { 2241 continue; 2242 } 2243 2244 std::string idStr; 2245 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2246 { 2247 continue; 2248 } 2249 2250 if (firstEntry) 2251 { 2252 firstEntry = false; 2253 } 2254 2255 logEntryArray.push_back({}); 2256 nlohmann::json& bmcJournalLogEntry = logEntryArray.back(); 2257 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 2258 bmcJournalLogEntry) != 0) 2259 { 2260 messages::internalError(asyncResp->res); 2261 return; 2262 } 2263 } 2264 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 2265 if (skip + top < entryCount) 2266 { 2267 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2268 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 2269 std::to_string(skip + top); 2270 } 2271 }); 2272 } 2273 2274 inline void requestRoutesBMCJournalLogEntry(App& app) 2275 { 2276 BMCWEB_ROUTE(app, 2277 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/") 2278 .privileges(redfish::privileges::getLogEntry) 2279 .methods(boost::beast::http::verb::get)( 2280 [](const crow::Request&, 2281 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2282 const std::string& entryID) { 2283 // Convert the unique ID back to a timestamp to find the entry 2284 uint64_t ts = 0; 2285 uint64_t index = 0; 2286 if (!getTimestampFromID(asyncResp, entryID, ts, index)) 2287 { 2288 return; 2289 } 2290 2291 sd_journal* journalTmp = nullptr; 2292 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2293 if (ret < 0) 2294 { 2295 BMCWEB_LOG_ERROR << "failed to open journal: " 2296 << strerror(-ret); 2297 messages::internalError(asyncResp->res); 2298 return; 2299 } 2300 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> 2301 journal(journalTmp, sd_journal_close); 2302 journalTmp = nullptr; 2303 // Go to the timestamp in the log and move to the entry at the 2304 // index tracking the unique ID 2305 std::string idStr; 2306 bool firstEntry = true; 2307 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 2308 if (ret < 0) 2309 { 2310 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 2311 << strerror(-ret); 2312 messages::internalError(asyncResp->res); 2313 return; 2314 } 2315 for (uint64_t i = 0; i <= index; i++) 2316 { 2317 sd_journal_next(journal.get()); 2318 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2319 { 2320 messages::internalError(asyncResp->res); 2321 return; 2322 } 2323 if (firstEntry) 2324 { 2325 firstEntry = false; 2326 } 2327 } 2328 // Confirm that the entry ID matches what was requested 2329 if (idStr != entryID) 2330 { 2331 messages::resourceMissingAtURI(asyncResp->res, entryID); 2332 return; 2333 } 2334 2335 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 2336 asyncResp->res.jsonValue) != 0) 2337 { 2338 messages::internalError(asyncResp->res); 2339 return; 2340 } 2341 }); 2342 } 2343 2344 inline void requestRoutesBMCDumpService(App& app) 2345 { 2346 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 2347 .privileges(redfish::privileges::getLogService) 2348 .methods( 2349 boost::beast::http::verb:: 2350 get)([](const crow::Request&, 2351 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2352 asyncResp->res.jsonValue["@odata.id"] = 2353 "/redfish/v1/Managers/bmc/LogServices/Dump"; 2354 asyncResp->res.jsonValue["@odata.type"] = 2355 "#LogService.v1_2_0.LogService"; 2356 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2357 asyncResp->res.jsonValue["Description"] = "BMC Dump LogService"; 2358 asyncResp->res.jsonValue["Id"] = "Dump"; 2359 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2360 2361 std::pair<std::string, std::string> redfishDateTimeOffset = 2362 crow::utility::getDateTimeOffsetNow(); 2363 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2364 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2365 redfishDateTimeOffset.second; 2366 2367 asyncResp->res.jsonValue["Entries"] = { 2368 {"@odata.id", 2369 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}}; 2370 asyncResp->res.jsonValue["Actions"] = { 2371 {"#LogService.ClearLog", 2372 {{"target", 2373 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog"}}}, 2374 {"#LogService.CollectDiagnosticData", 2375 {{"target", 2376 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData"}}}}; 2377 }); 2378 } 2379 2380 inline void requestRoutesBMCDumpEntryCollection(App& app) 2381 { 2382 2383 /** 2384 * Functions triggers appropriate requests on DBus 2385 */ 2386 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 2387 .privileges(redfish::privileges::getLogEntryCollection) 2388 .methods(boost::beast::http::verb::get)( 2389 [](const crow::Request&, 2390 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2391 asyncResp->res.jsonValue["@odata.type"] = 2392 "#LogEntryCollection.LogEntryCollection"; 2393 asyncResp->res.jsonValue["@odata.id"] = 2394 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; 2395 asyncResp->res.jsonValue["Name"] = "BMC Dump Entries"; 2396 asyncResp->res.jsonValue["Description"] = 2397 "Collection of BMC Dump Entries"; 2398 2399 getDumpEntryCollection(asyncResp, "BMC"); 2400 }); 2401 } 2402 2403 inline void requestRoutesBMCDumpEntry(App& app) 2404 { 2405 BMCWEB_ROUTE(app, 2406 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2407 .privileges(redfish::privileges::getLogEntry) 2408 .methods(boost::beast::http::verb::get)( 2409 [](const crow::Request&, 2410 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2411 const std::string& param) { 2412 getDumpEntryById(asyncResp, param, "BMC"); 2413 }); 2414 BMCWEB_ROUTE(app, 2415 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2416 .privileges(redfish::privileges::deleteLogEntry) 2417 .methods(boost::beast::http::verb::delete_)( 2418 [](const crow::Request&, 2419 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2420 const std::string& param) { 2421 deleteDumpEntry(asyncResp, param, "bmc"); 2422 }); 2423 } 2424 2425 inline void requestRoutesBMCDumpCreate(App& app) 2426 { 2427 2428 BMCWEB_ROUTE( 2429 app, 2430 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 2431 .privileges(redfish::privileges::postLogService) 2432 .methods(boost::beast::http::verb::post)( 2433 [](const crow::Request& req, 2434 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2435 createDump(asyncResp, req, "BMC"); 2436 }); 2437 } 2438 2439 inline void requestRoutesBMCDumpClear(App& app) 2440 { 2441 BMCWEB_ROUTE( 2442 app, 2443 "/redfish/v1/Managers/bmc/LogServices/Dump/Actions/LogService.ClearLog/") 2444 .privileges(redfish::privileges::postLogService) 2445 .methods(boost::beast::http::verb::post)( 2446 [](const crow::Request&, 2447 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2448 clearDump(asyncResp, "BMC"); 2449 }); 2450 } 2451 2452 inline void requestRoutesSystemDumpService(App& app) 2453 { 2454 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/") 2455 .privileges(redfish::privileges::getLogService) 2456 .methods(boost::beast::http::verb::get)( 2457 [](const crow::Request&, 2458 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2459 2460 { 2461 asyncResp->res.jsonValue["@odata.id"] = 2462 "/redfish/v1/Systems/system/LogServices/Dump"; 2463 asyncResp->res.jsonValue["@odata.type"] = 2464 "#LogService.v1_2_0.LogService"; 2465 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2466 asyncResp->res.jsonValue["Description"] = 2467 "System Dump LogService"; 2468 asyncResp->res.jsonValue["Id"] = "Dump"; 2469 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2470 2471 std::pair<std::string, std::string> redfishDateTimeOffset = 2472 crow::utility::getDateTimeOffsetNow(); 2473 asyncResp->res.jsonValue["DateTime"] = 2474 redfishDateTimeOffset.first; 2475 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2476 redfishDateTimeOffset.second; 2477 2478 asyncResp->res.jsonValue["Entries"] = { 2479 {"@odata.id", 2480 "/redfish/v1/Systems/system/LogServices/Dump/Entries"}}; 2481 asyncResp->res.jsonValue["Actions"] = { 2482 {"#LogService.ClearLog", 2483 {{"target", 2484 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog"}}}, 2485 {"#LogService.CollectDiagnosticData", 2486 {{"target", 2487 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData"}}}}; 2488 }); 2489 } 2490 2491 inline void requestRoutesSystemDumpEntryCollection(App& app) 2492 { 2493 2494 /** 2495 * Functions triggers appropriate requests on DBus 2496 */ 2497 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/") 2498 .privileges(redfish::privileges::getLogEntryCollection) 2499 .methods(boost::beast::http::verb::get)( 2500 [](const crow::Request&, 2501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2502 asyncResp->res.jsonValue["@odata.type"] = 2503 "#LogEntryCollection.LogEntryCollection"; 2504 asyncResp->res.jsonValue["@odata.id"] = 2505 "/redfish/v1/Systems/system/LogServices/Dump/Entries"; 2506 asyncResp->res.jsonValue["Name"] = "System Dump Entries"; 2507 asyncResp->res.jsonValue["Description"] = 2508 "Collection of System Dump Entries"; 2509 2510 getDumpEntryCollection(asyncResp, "System"); 2511 }); 2512 } 2513 2514 inline void requestRoutesSystemDumpEntry(App& app) 2515 { 2516 BMCWEB_ROUTE(app, 2517 "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/") 2518 .privileges(redfish::privileges::getLogEntry) 2519 2520 .methods(boost::beast::http::verb::get)( 2521 [](const crow::Request&, 2522 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2523 const std::string& param) { 2524 getDumpEntryById(asyncResp, param, "System"); 2525 }); 2526 2527 BMCWEB_ROUTE(app, 2528 "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/") 2529 .privileges(redfish::privileges::deleteLogEntry) 2530 .methods(boost::beast::http::verb::delete_)( 2531 [](const crow::Request&, 2532 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2533 const std::string& param) { 2534 deleteDumpEntry(asyncResp, param, "system"); 2535 }); 2536 } 2537 2538 inline void requestRoutesSystemDumpCreate(App& app) 2539 { 2540 BMCWEB_ROUTE( 2541 app, 2542 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.CollectDiagnosticData/") 2543 .privileges(redfish::privileges::postLogService) 2544 .methods(boost::beast::http::verb::post)( 2545 [](const crow::Request& req, 2546 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2547 2548 { createDump(asyncResp, req, "System"); }); 2549 } 2550 2551 inline void requestRoutesSystemDumpClear(App& app) 2552 { 2553 BMCWEB_ROUTE( 2554 app, 2555 "/redfish/v1/Systems/system/LogServices/Dump/Actions/LogService.ClearLog/") 2556 .privileges(redfish::privileges::postLogService) 2557 .methods(boost::beast::http::verb::post)( 2558 [](const crow::Request&, 2559 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2560 2561 { clearDump(asyncResp, "System"); }); 2562 } 2563 2564 inline void requestRoutesCrashdumpService(App& app) 2565 { 2566 // Note: Deviated from redfish privilege registry for GET & HEAD 2567 // method for security reasons. 2568 /** 2569 * Functions triggers appropriate requests on DBus 2570 */ 2571 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 2572 // This is incorrect, should be: 2573 //.privileges(redfish::privileges::getLogService) 2574 .privileges({{"ConfigureManager"}}) 2575 .methods( 2576 boost::beast::http::verb:: 2577 get)([](const crow::Request&, 2578 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2579 // Copy over the static data to include the entries added by 2580 // SubRoute 2581 asyncResp->res.jsonValue["@odata.id"] = 2582 "/redfish/v1/Systems/system/LogServices/Crashdump"; 2583 asyncResp->res.jsonValue["@odata.type"] = 2584 "#LogService.v1_2_0.LogService"; 2585 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 2586 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 2587 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 2588 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2589 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 2590 2591 std::pair<std::string, std::string> redfishDateTimeOffset = 2592 crow::utility::getDateTimeOffsetNow(); 2593 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2594 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2595 redfishDateTimeOffset.second; 2596 2597 asyncResp->res.jsonValue["Entries"] = { 2598 {"@odata.id", 2599 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 2600 asyncResp->res.jsonValue["Actions"] = { 2601 {"#LogService.ClearLog", 2602 {{"target", 2603 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog"}}}, 2604 {"#LogService.CollectDiagnosticData", 2605 {{"target", 2606 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData"}}}}; 2607 }); 2608 } 2609 2610 void inline requestRoutesCrashdumpClear(App& app) 2611 { 2612 BMCWEB_ROUTE( 2613 app, 2614 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.ClearLog/") 2615 // This is incorrect, should be: 2616 //.privileges(redfish::privileges::postLogService) 2617 .privileges({{"ConfigureComponents"}}) 2618 .methods(boost::beast::http::verb::post)( 2619 [](const crow::Request&, 2620 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2621 crow::connections::systemBus->async_method_call( 2622 [asyncResp](const boost::system::error_code ec, 2623 const std::string&) { 2624 if (ec) 2625 { 2626 messages::internalError(asyncResp->res); 2627 return; 2628 } 2629 messages::success(asyncResp->res); 2630 }, 2631 crashdumpObject, crashdumpPath, deleteAllInterface, 2632 "DeleteAll"); 2633 }); 2634 } 2635 2636 static void 2637 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2638 const std::string& logID, nlohmann::json& logEntryJson) 2639 { 2640 auto getStoredLogCallback = 2641 [asyncResp, logID, &logEntryJson]( 2642 const boost::system::error_code ec, 2643 const std::vector< 2644 std::pair<std::string, dbus::utility::DbusVariantType>>& 2645 params) { 2646 if (ec) 2647 { 2648 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 2649 if (ec.value() == 2650 boost::system::linux_error::bad_request_descriptor) 2651 { 2652 messages::resourceNotFound(asyncResp->res, "LogEntry", 2653 logID); 2654 } 2655 else 2656 { 2657 messages::internalError(asyncResp->res); 2658 } 2659 return; 2660 } 2661 2662 std::string timestamp{}; 2663 std::string filename{}; 2664 std::string logfile{}; 2665 parseCrashdumpParameters(params, filename, timestamp, logfile); 2666 2667 if (filename.empty() || timestamp.empty()) 2668 { 2669 messages::resourceMissingAtURI(asyncResp->res, logID); 2670 return; 2671 } 2672 2673 std::string crashdumpURI = 2674 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 2675 logID + "/" + filename; 2676 logEntryJson = {{"@odata.type", "#LogEntry.v1_7_0.LogEntry"}, 2677 {"@odata.id", "/redfish/v1/Systems/system/" 2678 "LogServices/Crashdump/Entries/" + 2679 logID}, 2680 {"Name", "CPU Crashdump"}, 2681 {"Id", logID}, 2682 {"EntryType", "Oem"}, 2683 {"AdditionalDataURI", std::move(crashdumpURI)}, 2684 {"DiagnosticDataType", "OEM"}, 2685 {"OEMDiagnosticDataType", "PECICrashdump"}, 2686 {"Created", std::move(timestamp)}}; 2687 }; 2688 crow::connections::systemBus->async_method_call( 2689 std::move(getStoredLogCallback), crashdumpObject, 2690 crashdumpPath + std::string("/") + logID, 2691 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2692 } 2693 2694 inline void requestRoutesCrashdumpEntryCollection(App& app) 2695 { 2696 // Note: Deviated from redfish privilege registry for GET & HEAD 2697 // method for security reasons. 2698 /** 2699 * Functions triggers appropriate requests on DBus 2700 */ 2701 BMCWEB_ROUTE(app, 2702 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 2703 // This is incorrect, should be. 2704 //.privileges(redfish::privileges::postLogEntryCollection) 2705 .privileges({{"ConfigureComponents"}}) 2706 .methods( 2707 boost::beast::http::verb:: 2708 get)([](const crow::Request&, 2709 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2710 // Collections don't include the static data added by SubRoute 2711 // because it has a duplicate entry for members 2712 auto getLogEntriesCallback = [asyncResp]( 2713 const boost::system::error_code ec, 2714 const std::vector<std::string>& 2715 resp) { 2716 if (ec) 2717 { 2718 if (ec.value() != 2719 boost::system::errc::no_such_file_or_directory) 2720 { 2721 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 2722 << ec.message(); 2723 messages::internalError(asyncResp->res); 2724 return; 2725 } 2726 } 2727 asyncResp->res.jsonValue["@odata.type"] = 2728 "#LogEntryCollection.LogEntryCollection"; 2729 asyncResp->res.jsonValue["@odata.id"] = 2730 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 2731 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 2732 asyncResp->res.jsonValue["Description"] = 2733 "Collection of Crashdump Entries"; 2734 nlohmann::json& logEntryArray = 2735 asyncResp->res.jsonValue["Members"]; 2736 logEntryArray = nlohmann::json::array(); 2737 std::vector<std::string> logIDs; 2738 // Get the list of log entries and build up an empty array big 2739 // enough to hold them 2740 for (const std::string& objpath : resp) 2741 { 2742 // Get the log ID 2743 std::size_t lastPos = objpath.rfind('/'); 2744 if (lastPos == std::string::npos) 2745 { 2746 continue; 2747 } 2748 logIDs.emplace_back(objpath.substr(lastPos + 1)); 2749 2750 // Add a space for the log entry to the array 2751 logEntryArray.push_back({}); 2752 } 2753 // Now go through and set up async calls to fill in the entries 2754 size_t index = 0; 2755 for (const std::string& logID : logIDs) 2756 { 2757 // Add the log entry to the array 2758 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 2759 } 2760 asyncResp->res.jsonValue["Members@odata.count"] = 2761 logEntryArray.size(); 2762 }; 2763 crow::connections::systemBus->async_method_call( 2764 std::move(getLogEntriesCallback), 2765 "xyz.openbmc_project.ObjectMapper", 2766 "/xyz/openbmc_project/object_mapper", 2767 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 2768 std::array<const char*, 1>{crashdumpInterface}); 2769 }); 2770 } 2771 2772 inline void requestRoutesCrashdumpEntry(App& app) 2773 { 2774 // Note: Deviated from redfish privilege registry for GET & HEAD 2775 // method for security reasons. 2776 2777 BMCWEB_ROUTE( 2778 app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/") 2779 // this is incorrect, should be 2780 // .privileges(redfish::privileges::getLogEntry) 2781 .privileges({{"ConfigureComponents"}}) 2782 .methods(boost::beast::http::verb::get)( 2783 [](const crow::Request&, 2784 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2785 const std::string& param) { 2786 const std::string& logID = param; 2787 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 2788 }); 2789 } 2790 2791 inline void requestRoutesCrashdumpFile(App& app) 2792 { 2793 // Note: Deviated from redfish privilege registry for GET & HEAD 2794 // method for security reasons. 2795 BMCWEB_ROUTE( 2796 app, 2797 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/<str>/") 2798 .privileges(redfish::privileges::getLogEntry) 2799 .methods(boost::beast::http::verb::get)( 2800 [](const crow::Request&, 2801 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2802 const std::string& logID, const std::string& fileName) { 2803 auto getStoredLogCallback = 2804 [asyncResp, logID, fileName]( 2805 const boost::system::error_code ec, 2806 const std::vector<std::pair< 2807 std::string, dbus::utility::DbusVariantType>>& 2808 resp) { 2809 if (ec) 2810 { 2811 BMCWEB_LOG_DEBUG << "failed to get log ec: " 2812 << ec.message(); 2813 messages::internalError(asyncResp->res); 2814 return; 2815 } 2816 2817 std::string dbusFilename{}; 2818 std::string dbusTimestamp{}; 2819 std::string dbusFilepath{}; 2820 2821 parseCrashdumpParameters(resp, dbusFilename, 2822 dbusTimestamp, dbusFilepath); 2823 2824 if (dbusFilename.empty() || dbusTimestamp.empty() || 2825 dbusFilepath.empty()) 2826 { 2827 messages::resourceMissingAtURI(asyncResp->res, 2828 fileName); 2829 return; 2830 } 2831 2832 // Verify the file name parameter is correct 2833 if (fileName != dbusFilename) 2834 { 2835 messages::resourceMissingAtURI(asyncResp->res, 2836 fileName); 2837 return; 2838 } 2839 2840 if (!std::filesystem::exists(dbusFilepath)) 2841 { 2842 messages::resourceMissingAtURI(asyncResp->res, 2843 fileName); 2844 return; 2845 } 2846 std::ifstream ifs(dbusFilepath, std::ios::in | 2847 std::ios::binary | 2848 std::ios::ate); 2849 std::ifstream::pos_type fileSize = ifs.tellg(); 2850 if (fileSize < 0) 2851 { 2852 messages::generalError(asyncResp->res); 2853 return; 2854 } 2855 ifs.seekg(0, std::ios::beg); 2856 2857 auto crashData = std::make_unique<char[]>( 2858 static_cast<unsigned int>(fileSize)); 2859 2860 ifs.read(crashData.get(), static_cast<int>(fileSize)); 2861 2862 // The cast to std::string is intentional in order to 2863 // use the assign() that applies move mechanics 2864 asyncResp->res.body().assign( 2865 static_cast<std::string>(crashData.get())); 2866 2867 // Configure this to be a file download when accessed 2868 // from a browser 2869 asyncResp->res.addHeader("Content-Disposition", 2870 "attachment"); 2871 }; 2872 crow::connections::systemBus->async_method_call( 2873 std::move(getStoredLogCallback), crashdumpObject, 2874 crashdumpPath + std::string("/") + logID, 2875 "org.freedesktop.DBus.Properties", "GetAll", 2876 crashdumpInterface); 2877 }); 2878 } 2879 2880 inline void requestRoutesCrashdumpCollect(App& app) 2881 { 2882 // Note: Deviated from redfish privilege registry for GET & HEAD 2883 // method for security reasons. 2884 BMCWEB_ROUTE( 2885 app, 2886 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/LogService.CollectDiagnosticData/") 2887 // The below is incorrect; Should be ConfigureManager 2888 //.privileges(redfish::privileges::postLogService) 2889 .privileges({{"ConfigureComponents"}}) 2890 .methods( 2891 boost::beast::http::verb:: 2892 post)([](const crow::Request& req, 2893 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2894 std::string diagnosticDataType; 2895 std::string oemDiagnosticDataType; 2896 if (!redfish::json_util::readJson( 2897 req, asyncResp->res, "DiagnosticDataType", 2898 diagnosticDataType, "OEMDiagnosticDataType", 2899 oemDiagnosticDataType)) 2900 { 2901 return; 2902 } 2903 2904 if (diagnosticDataType != "OEM") 2905 { 2906 BMCWEB_LOG_ERROR 2907 << "Only OEM DiagnosticDataType supported for Crashdump"; 2908 messages::actionParameterValueFormatError( 2909 asyncResp->res, diagnosticDataType, "DiagnosticDataType", 2910 "CollectDiagnosticData"); 2911 return; 2912 } 2913 2914 auto collectCrashdumpCallback = [asyncResp, 2915 payload(task::Payload(req))]( 2916 const boost::system::error_code 2917 ec, 2918 const std::string&) mutable { 2919 if (ec) 2920 { 2921 if (ec.value() == 2922 boost::system::errc::operation_not_supported) 2923 { 2924 messages::resourceInStandby(asyncResp->res); 2925 } 2926 else if (ec.value() == 2927 boost::system::errc::device_or_resource_busy) 2928 { 2929 messages::serviceTemporarilyUnavailable(asyncResp->res, 2930 "60"); 2931 } 2932 else 2933 { 2934 messages::internalError(asyncResp->res); 2935 } 2936 return; 2937 } 2938 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2939 [](boost::system::error_code err, 2940 sdbusplus::message::message&, 2941 const std::shared_ptr<task::TaskData>& taskData) { 2942 if (!err) 2943 { 2944 taskData->messages.emplace_back( 2945 messages::taskCompletedOK( 2946 std::to_string(taskData->index))); 2947 taskData->state = "Completed"; 2948 } 2949 return task::completed; 2950 }, 2951 "type='signal',interface='org.freedesktop.DBus." 2952 "Properties'," 2953 "member='PropertiesChanged',arg0namespace='com.intel.crashdump'"); 2954 task->startTimer(std::chrono::minutes(5)); 2955 task->populateResp(asyncResp->res); 2956 task->payload.emplace(std::move(payload)); 2957 }; 2958 2959 if (oemDiagnosticDataType == "OnDemand") 2960 { 2961 crow::connections::systemBus->async_method_call( 2962 std::move(collectCrashdumpCallback), crashdumpObject, 2963 crashdumpPath, crashdumpOnDemandInterface, 2964 "GenerateOnDemandLog"); 2965 } 2966 else if (oemDiagnosticDataType == "Telemetry") 2967 { 2968 crow::connections::systemBus->async_method_call( 2969 std::move(collectCrashdumpCallback), crashdumpObject, 2970 crashdumpPath, crashdumpTelemetryInterface, 2971 "GenerateTelemetryLog"); 2972 } 2973 else 2974 { 2975 BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: " 2976 << oemDiagnosticDataType; 2977 messages::actionParameterValueFormatError( 2978 asyncResp->res, oemDiagnosticDataType, 2979 "OEMDiagnosticDataType", "CollectDiagnosticData"); 2980 return; 2981 } 2982 }); 2983 } 2984 2985 /** 2986 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2987 */ 2988 inline void requestRoutesDBusLogServiceActionsClear(App& app) 2989 { 2990 /** 2991 * Function handles POST method request. 2992 * The Clear Log actions does not require any parameter.The action deletes 2993 * all entries found in the Entries collection for this Log Service. 2994 */ 2995 2996 BMCWEB_ROUTE( 2997 app, 2998 "/redfish/v1/Systems/system/LogServices/EventLog/Actions/LogService.ClearLog/") 2999 .privileges(redfish::privileges::postLogService) 3000 .methods(boost::beast::http::verb::post)( 3001 [](const crow::Request&, 3002 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3003 BMCWEB_LOG_DEBUG << "Do delete all entries."; 3004 3005 // Process response from Logging service. 3006 auto respHandler = [asyncResp]( 3007 const boost::system::error_code ec) { 3008 BMCWEB_LOG_DEBUG 3009 << "doClearLog resp_handler callback: Done"; 3010 if (ec) 3011 { 3012 // TODO Handle for specific error code 3013 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " 3014 << ec; 3015 asyncResp->res.result( 3016 boost::beast::http::status::internal_server_error); 3017 return; 3018 } 3019 3020 asyncResp->res.result( 3021 boost::beast::http::status::no_content); 3022 }; 3023 3024 // Make call to Logging service to request Clear Log 3025 crow::connections::systemBus->async_method_call( 3026 respHandler, "xyz.openbmc_project.Logging", 3027 "/xyz/openbmc_project/logging", 3028 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3029 }); 3030 } 3031 3032 /**************************************************** 3033 * Redfish PostCode interfaces 3034 * using DBUS interface: getPostCodesTS 3035 ******************************************************/ 3036 inline void requestRoutesPostCodesLogService(App& app) 3037 { 3038 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 3039 .privileges(redfish::privileges::getLogService) 3040 .methods( 3041 boost::beast::http::verb:: 3042 get)([](const crow::Request&, 3043 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3044 asyncResp->res.jsonValue = { 3045 {"@odata.id", 3046 "/redfish/v1/Systems/system/LogServices/PostCodes"}, 3047 {"@odata.type", "#LogService.v1_1_0.LogService"}, 3048 {"Name", "POST Code Log Service"}, 3049 {"Description", "POST Code Log Service"}, 3050 {"Id", "BIOS POST Code Log"}, 3051 {"OverWritePolicy", "WrapsWhenFull"}, 3052 {"Entries", 3053 {{"@odata.id", 3054 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 3055 3056 std::pair<std::string, std::string> redfishDateTimeOffset = 3057 crow::utility::getDateTimeOffsetNow(); 3058 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 3059 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 3060 redfishDateTimeOffset.second; 3061 3062 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 3063 {"target", 3064 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog"}}; 3065 }); 3066 } 3067 3068 inline void requestRoutesPostCodesClear(App& app) 3069 { 3070 BMCWEB_ROUTE( 3071 app, 3072 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/LogService.ClearLog/") 3073 // The following privilege is incorrect; It should be ConfigureManager 3074 //.privileges(redfish::privileges::postLogService) 3075 .privileges({{"ConfigureComponents"}}) 3076 .methods(boost::beast::http::verb::post)( 3077 [](const crow::Request&, 3078 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3079 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 3080 3081 // Make call to post-code service to request clear all 3082 crow::connections::systemBus->async_method_call( 3083 [asyncResp](const boost::system::error_code ec) { 3084 if (ec) 3085 { 3086 // TODO Handle for specific error code 3087 BMCWEB_LOG_ERROR 3088 << "doClearPostCodes resp_handler got error " 3089 << ec; 3090 asyncResp->res.result(boost::beast::http::status:: 3091 internal_server_error); 3092 messages::internalError(asyncResp->res); 3093 return; 3094 } 3095 }, 3096 "xyz.openbmc_project.State.Boot.PostCode0", 3097 "/xyz/openbmc_project/State/Boot/PostCode0", 3098 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3099 }); 3100 } 3101 3102 static void fillPostCodeEntry( 3103 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3104 const boost::container::flat_map< 3105 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 3106 const uint16_t bootIndex, const uint64_t codeIndex = 0, 3107 const uint64_t skip = 0, const uint64_t top = 0) 3108 { 3109 // Get the Message from the MessageRegistry 3110 const message_registries::Message* message = 3111 message_registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 3112 3113 uint64_t currentCodeIndex = 0; 3114 nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"]; 3115 3116 uint64_t firstCodeTimeUs = 0; 3117 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3118 code : postcode) 3119 { 3120 currentCodeIndex++; 3121 std::string postcodeEntryID = 3122 "B" + std::to_string(bootIndex) + "-" + 3123 std::to_string(currentCodeIndex); // 1 based index in EntryID string 3124 3125 uint64_t usecSinceEpoch = code.first; 3126 uint64_t usTimeOffset = 0; 3127 3128 if (1 == currentCodeIndex) 3129 { // already incremented 3130 firstCodeTimeUs = code.first; 3131 } 3132 else 3133 { 3134 usTimeOffset = code.first - firstCodeTimeUs; 3135 } 3136 3137 // skip if no specific codeIndex is specified and currentCodeIndex does 3138 // not fall between top and skip 3139 if ((codeIndex == 0) && 3140 (currentCodeIndex <= skip || currentCodeIndex > top)) 3141 { 3142 continue; 3143 } 3144 3145 // skip if a specific codeIndex is specified and does not match the 3146 // currentIndex 3147 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 3148 { 3149 // This is done for simplicity. 1st entry is needed to calculate 3150 // time offset. To improve efficiency, one can get to the entry 3151 // directly (possibly with flatmap's nth method) 3152 continue; 3153 } 3154 3155 // currentCodeIndex is within top and skip or equal to specified code 3156 // index 3157 3158 // Get the Created time from the timestamp 3159 std::string entryTimeStr; 3160 entryTimeStr = 3161 crow::utility::getDateTimeUint(usecSinceEpoch / 1000 / 1000); 3162 3163 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 3164 std::ostringstream hexCode; 3165 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 3166 << std::get<0>(code.second); 3167 std::ostringstream timeOffsetStr; 3168 // Set Fixed -Point Notation 3169 timeOffsetStr << std::fixed; 3170 // Set precision to 4 digits 3171 timeOffsetStr << std::setprecision(4); 3172 // Add double to stream 3173 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 3174 std::vector<std::string> messageArgs = { 3175 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 3176 3177 // Get MessageArgs template from message registry 3178 std::string msg; 3179 if (message != nullptr) 3180 { 3181 msg = message->message; 3182 3183 // fill in this post code value 3184 int i = 0; 3185 for (const std::string& messageArg : messageArgs) 3186 { 3187 std::string argStr = "%" + std::to_string(++i); 3188 size_t argPos = msg.find(argStr); 3189 if (argPos != std::string::npos) 3190 { 3191 msg.replace(argPos, argStr.length(), messageArg); 3192 } 3193 } 3194 } 3195 3196 // Get Severity template from message registry 3197 std::string severity; 3198 if (message != nullptr) 3199 { 3200 severity = message->severity; 3201 } 3202 3203 // add to AsyncResp 3204 logEntryArray.push_back({}); 3205 nlohmann::json& bmcLogEntry = logEntryArray.back(); 3206 bmcLogEntry = { 3207 {"@odata.type", "#LogEntry.v1_8_0.LogEntry"}, 3208 {"@odata.id", 3209 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + 3210 postcodeEntryID}, 3211 {"Name", "POST Code Log Entry"}, 3212 {"Id", postcodeEntryID}, 3213 {"Message", std::move(msg)}, 3214 {"MessageId", "OpenBMC.0.2.BIOSPOSTCode"}, 3215 {"MessageArgs", std::move(messageArgs)}, 3216 {"EntryType", "Event"}, 3217 {"Severity", std::move(severity)}, 3218 {"Created", entryTimeStr}}; 3219 if (!std::get<std::vector<uint8_t>>(code.second).empty()) 3220 { 3221 bmcLogEntry["AdditionalDataURI"] = 3222 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/" + 3223 postcodeEntryID + "/attachment"; 3224 } 3225 } 3226 } 3227 3228 static void getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3229 const uint16_t bootIndex, 3230 const uint64_t codeIndex) 3231 { 3232 crow::connections::systemBus->async_method_call( 3233 [aResp, bootIndex, 3234 codeIndex](const boost::system::error_code ec, 3235 const boost::container::flat_map< 3236 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3237 postcode) { 3238 if (ec) 3239 { 3240 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3241 messages::internalError(aResp->res); 3242 return; 3243 } 3244 3245 // skip the empty postcode boots 3246 if (postcode.empty()) 3247 { 3248 return; 3249 } 3250 3251 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 3252 3253 aResp->res.jsonValue["Members@odata.count"] = 3254 aResp->res.jsonValue["Members"].size(); 3255 }, 3256 "xyz.openbmc_project.State.Boot.PostCode0", 3257 "/xyz/openbmc_project/State/Boot/PostCode0", 3258 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3259 bootIndex); 3260 } 3261 3262 static void getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3263 const uint16_t bootIndex, 3264 const uint16_t bootCount, 3265 const uint64_t entryCount, const uint64_t skip, 3266 const uint64_t top) 3267 { 3268 crow::connections::systemBus->async_method_call( 3269 [aResp, bootIndex, bootCount, entryCount, skip, 3270 top](const boost::system::error_code ec, 3271 const boost::container::flat_map< 3272 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 3273 postcode) { 3274 if (ec) 3275 { 3276 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3277 messages::internalError(aResp->res); 3278 return; 3279 } 3280 3281 uint64_t endCount = entryCount; 3282 if (!postcode.empty()) 3283 { 3284 endCount = entryCount + postcode.size(); 3285 3286 if ((skip < endCount) && ((top + skip) > entryCount)) 3287 { 3288 uint64_t thisBootSkip = 3289 std::max(skip, entryCount) - entryCount; 3290 uint64_t thisBootTop = 3291 std::min(top + skip, endCount) - entryCount; 3292 3293 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 3294 thisBootSkip, thisBootTop); 3295 } 3296 aResp->res.jsonValue["Members@odata.count"] = endCount; 3297 } 3298 3299 // continue to previous bootIndex 3300 if (bootIndex < bootCount) 3301 { 3302 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 3303 bootCount, endCount, skip, top); 3304 } 3305 else 3306 { 3307 aResp->res.jsonValue["Members@odata.nextLink"] = 3308 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries?$skip=" + 3309 std::to_string(skip + top); 3310 } 3311 }, 3312 "xyz.openbmc_project.State.Boot.PostCode0", 3313 "/xyz/openbmc_project/State/Boot/PostCode0", 3314 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3315 bootIndex); 3316 } 3317 3318 static void 3319 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3320 const uint64_t skip, const uint64_t top) 3321 { 3322 uint64_t entryCount = 0; 3323 sdbusplus::asio::getProperty<uint16_t>( 3324 *crow::connections::systemBus, 3325 "xyz.openbmc_project.State.Boot.PostCode0", 3326 "/xyz/openbmc_project/State/Boot/PostCode0", 3327 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount", 3328 [aResp, entryCount, skip, top](const boost::system::error_code ec, 3329 const uint16_t bootCount) { 3330 if (ec) 3331 { 3332 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3333 messages::internalError(aResp->res); 3334 return; 3335 } 3336 getPostCodeForBoot(aResp, 1, bootCount, entryCount, skip, top); 3337 }); 3338 } 3339 3340 inline void requestRoutesPostCodesEntryCollection(App& app) 3341 { 3342 BMCWEB_ROUTE(app, 3343 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 3344 .privileges(redfish::privileges::getLogEntryCollection) 3345 .methods(boost::beast::http::verb::get)( 3346 [](const crow::Request& req, 3347 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3348 asyncResp->res.jsonValue["@odata.type"] = 3349 "#LogEntryCollection.LogEntryCollection"; 3350 asyncResp->res.jsonValue["@odata.id"] = 3351 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3352 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3353 asyncResp->res.jsonValue["Description"] = 3354 "Collection of POST Code Log Entries"; 3355 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3356 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3357 3358 uint64_t skip = 0; 3359 uint64_t top = maxEntriesPerPage; // Show max entries by default 3360 if (!getSkipParam(asyncResp, req, skip)) 3361 { 3362 return; 3363 } 3364 if (!getTopParam(asyncResp, req, top)) 3365 { 3366 return; 3367 } 3368 getCurrentBootNumber(asyncResp, skip, top); 3369 }); 3370 } 3371 3372 /** 3373 * @brief Parse post code ID and get the current value and index value 3374 * eg: postCodeID=B1-2, currentValue=1, index=2 3375 * 3376 * @param[in] postCodeID Post Code ID 3377 * @param[out] currentValue Current value 3378 * @param[out] index Index value 3379 * 3380 * @return bool true if the parsing is successful, false the parsing fails 3381 */ 3382 inline static bool parsePostCode(const std::string& postCodeID, 3383 uint64_t& currentValue, uint16_t& index) 3384 { 3385 std::vector<std::string> split; 3386 boost::algorithm::split(split, postCodeID, boost::is_any_of("-")); 3387 if (split.size() != 2 || split[0].length() < 2 || split[0].front() != 'B') 3388 { 3389 return false; 3390 } 3391 3392 const char* start = split[0].data() + 1; 3393 const char* end = split[0].data() + split[0].size(); 3394 auto [ptrIndex, ecIndex] = std::from_chars(start, end, index); 3395 3396 if (ptrIndex != end || ecIndex != std::errc()) 3397 { 3398 return false; 3399 } 3400 3401 start = split[1].data(); 3402 end = split[1].data() + split[1].size(); 3403 auto [ptrValue, ecValue] = std::from_chars(start, end, currentValue); 3404 if (ptrValue != end || ecValue != std::errc()) 3405 { 3406 return false; 3407 } 3408 3409 return true; 3410 } 3411 3412 inline void requestRoutesPostCodesEntryAdditionalData(App& app) 3413 { 3414 BMCWEB_ROUTE( 3415 app, 3416 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/attachment/") 3417 .privileges(redfish::privileges::getLogEntry) 3418 .methods(boost::beast::http::verb::get)( 3419 [](const crow::Request& req, 3420 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3421 const std::string& postCodeID) { 3422 if (!http_helpers::isOctetAccepted( 3423 req.getHeaderValue("Accept"))) 3424 { 3425 asyncResp->res.result( 3426 boost::beast::http::status::bad_request); 3427 return; 3428 } 3429 3430 uint64_t currentValue = 0; 3431 uint16_t index = 0; 3432 if (!parsePostCode(postCodeID, currentValue, index)) 3433 { 3434 messages::resourceNotFound(asyncResp->res, "LogEntry", 3435 postCodeID); 3436 return; 3437 } 3438 3439 crow::connections::systemBus->async_method_call( 3440 [asyncResp, postCodeID, currentValue]( 3441 const boost::system::error_code ec, 3442 const std::vector<std::tuple< 3443 uint64_t, std::vector<uint8_t>>>& postcodes) { 3444 if (ec.value() == EBADR) 3445 { 3446 messages::resourceNotFound(asyncResp->res, 3447 "LogEntry", postCodeID); 3448 return; 3449 } 3450 if (ec) 3451 { 3452 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3453 messages::internalError(asyncResp->res); 3454 return; 3455 } 3456 3457 size_t value = static_cast<size_t>(currentValue) - 1; 3458 if (value == std::string::npos || 3459 postcodes.size() < currentValue) 3460 { 3461 BMCWEB_LOG_ERROR << "Wrong currentValue value"; 3462 messages::resourceNotFound(asyncResp->res, 3463 "LogEntry", postCodeID); 3464 return; 3465 } 3466 3467 auto& [tID, c] = postcodes[value]; 3468 if (c.empty()) 3469 { 3470 BMCWEB_LOG_INFO << "No found post code data"; 3471 messages::resourceNotFound(asyncResp->res, 3472 "LogEntry", postCodeID); 3473 return; 3474 } 3475 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 3476 const char* d = reinterpret_cast<const char*>(c.data()); 3477 std::string_view strData(d, c.size()); 3478 3479 asyncResp->res.addHeader("Content-Type", 3480 "application/octet-stream"); 3481 asyncResp->res.addHeader("Content-Transfer-Encoding", 3482 "Base64"); 3483 asyncResp->res.body() = 3484 crow::utility::base64encode(strData); 3485 }, 3486 "xyz.openbmc_project.State.Boot.PostCode0", 3487 "/xyz/openbmc_project/State/Boot/PostCode0", 3488 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodes", 3489 index); 3490 }); 3491 } 3492 3493 inline void requestRoutesPostCodesEntry(App& app) 3494 { 3495 BMCWEB_ROUTE( 3496 app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/") 3497 .privileges(redfish::privileges::getLogEntry) 3498 .methods(boost::beast::http::verb::get)( 3499 [](const crow::Request&, 3500 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3501 const std::string& targetID) { 3502 uint16_t bootIndex = 0; 3503 uint64_t codeIndex = 0; 3504 if (!parsePostCode(targetID, codeIndex, bootIndex)) 3505 { 3506 // Requested ID was not found 3507 messages::resourceMissingAtURI(asyncResp->res, targetID); 3508 return; 3509 } 3510 if (bootIndex == 0 || codeIndex == 0) 3511 { 3512 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 3513 << targetID; 3514 } 3515 3516 asyncResp->res.jsonValue["@odata.type"] = 3517 "#LogEntry.v1_4_0.LogEntry"; 3518 asyncResp->res.jsonValue["@odata.id"] = 3519 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3520 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3521 asyncResp->res.jsonValue["Description"] = 3522 "Collection of POST Code Log Entries"; 3523 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3524 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3525 3526 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 3527 }); 3528 } 3529 3530 } // namespace redfish 3531