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