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