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 "node.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 26 #include <boost/algorithm/string/split.hpp> 27 #include <boost/beast/core/span.hpp> 28 #include <boost/container/flat_map.hpp> 29 #include <boost/system/linux_error.hpp> 30 #include <error_messages.hpp> 31 #include <filesystem> 32 #include <string_view> 33 #include <variant> 34 35 namespace redfish 36 { 37 38 constexpr char const *crashdumpObject = "com.intel.crashdump"; 39 constexpr char const *crashdumpPath = "/com/intel/crashdump"; 40 constexpr char const *crashdumpOnDemandPath = "/com/intel/crashdump/OnDemand"; 41 constexpr char const *crashdumpInterface = "com.intel.crashdump"; 42 constexpr char const *deleteAllInterface = 43 "xyz.openbmc_project.Collection.DeleteAll"; 44 constexpr char const *crashdumpOnDemandInterface = 45 "com.intel.crashdump.OnDemand"; 46 constexpr char const *crashdumpRawPECIInterface = 47 "com.intel.crashdump.SendRawPeci"; 48 49 namespace message_registries 50 { 51 static const Message *getMessageFromRegistry( 52 const std::string &messageKey, 53 const boost::beast::span<const MessageEntry> registry) 54 { 55 boost::beast::span<const MessageEntry>::const_iterator messageIt = 56 std::find_if(registry.cbegin(), registry.cend(), 57 [&messageKey](const MessageEntry &messageEntry) { 58 return !std::strcmp(messageEntry.first, 59 messageKey.c_str()); 60 }); 61 if (messageIt != registry.cend()) 62 { 63 return &messageIt->second; 64 } 65 66 return nullptr; 67 } 68 69 static const Message *getMessage(const std::string_view &messageID) 70 { 71 // Redfish MessageIds are in the form 72 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 73 // the right Message 74 std::vector<std::string> fields; 75 fields.reserve(4); 76 boost::split(fields, messageID, boost::is_any_of(".")); 77 std::string ®istryName = fields[0]; 78 std::string &messageKey = fields[3]; 79 80 // Find the right registry and check it for the MessageKey 81 if (std::string(base::header.registryPrefix) == registryName) 82 { 83 return getMessageFromRegistry( 84 messageKey, boost::beast::span<const MessageEntry>(base::registry)); 85 } 86 if (std::string(openbmc::header.registryPrefix) == registryName) 87 { 88 return getMessageFromRegistry( 89 messageKey, 90 boost::beast::span<const MessageEntry>(openbmc::registry)); 91 } 92 return nullptr; 93 } 94 } // namespace message_registries 95 96 namespace fs = std::filesystem; 97 98 using GetManagedPropertyType = boost::container::flat_map< 99 std::string, 100 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 101 int32_t, uint32_t, int64_t, uint64_t, double>>; 102 103 using GetManagedObjectsType = boost::container::flat_map< 104 sdbusplus::message::object_path, 105 boost::container::flat_map<std::string, GetManagedPropertyType>>; 106 107 inline std::string translateSeverityDbusToRedfish(const std::string &s) 108 { 109 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") 110 { 111 return "Critical"; 112 } 113 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") 114 { 115 return "Critical"; 116 } 117 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") 118 { 119 return "OK"; 120 } 121 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") 122 { 123 return "Critical"; 124 } 125 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") 126 { 127 return "Critical"; 128 } 129 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") 130 { 131 return "OK"; 132 } 133 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") 134 { 135 return "OK"; 136 } 137 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 138 { 139 return "Warning"; 140 } 141 return ""; 142 } 143 144 static int getJournalMetadata(sd_journal *journal, 145 const std::string_view &field, 146 std::string_view &contents) 147 { 148 const char *data = nullptr; 149 size_t length = 0; 150 int ret = 0; 151 // Get the metadata from the requested field of the journal entry 152 ret = sd_journal_get_data(journal, field.data(), 153 reinterpret_cast<const void **>(&data), &length); 154 if (ret < 0) 155 { 156 return ret; 157 } 158 contents = std::string_view(data, length); 159 // Only use the content after the "=" character. 160 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 161 return ret; 162 } 163 164 static int getJournalMetadata(sd_journal *journal, 165 const std::string_view &field, const int &base, 166 long int &contents) 167 { 168 int ret = 0; 169 std::string_view metadata; 170 // Get the metadata from the requested field of the journal entry 171 ret = getJournalMetadata(journal, field, metadata); 172 if (ret < 0) 173 { 174 return ret; 175 } 176 contents = strtol(metadata.data(), nullptr, base); 177 return ret; 178 } 179 180 static bool getTimestampStr(const uint64_t usecSinceEpoch, 181 std::string &entryTimestamp) 182 { 183 time_t t = static_cast<time_t>(usecSinceEpoch / 1000 / 1000); 184 struct tm *loctime = localtime(&t); 185 char entryTime[64] = {}; 186 if (nullptr != loctime) 187 { 188 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 189 } 190 // Insert the ':' into the timezone 191 std::string_view t1(entryTime); 192 std::string_view t2(entryTime); 193 if (t1.size() > 2 && t2.size() > 2) 194 { 195 t1.remove_suffix(2); 196 t2.remove_prefix(t2.size() - 2); 197 } 198 entryTimestamp = std::string(t1) + ":" + std::string(t2); 199 return true; 200 } 201 202 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 203 { 204 int ret = 0; 205 uint64_t timestamp = 0; 206 ret = sd_journal_get_realtime_usec(journal, ×tamp); 207 if (ret < 0) 208 { 209 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 210 << strerror(-ret); 211 return false; 212 } 213 return getTimestampStr(timestamp, entryTimestamp); 214 } 215 216 static bool getSkipParam(crow::Response &res, const crow::Request &req, 217 uint64_t &skip) 218 { 219 char *skipParam = req.urlParams.get("$skip"); 220 if (skipParam != nullptr) 221 { 222 char *ptr = nullptr; 223 skip = std::strtoul(skipParam, &ptr, 10); 224 if (*skipParam == '\0' || *ptr != '\0') 225 { 226 227 messages::queryParameterValueTypeError(res, std::string(skipParam), 228 "$skip"); 229 return false; 230 } 231 } 232 return true; 233 } 234 235 static constexpr const uint64_t maxEntriesPerPage = 1000; 236 static bool getTopParam(crow::Response &res, const crow::Request &req, 237 uint64_t &top) 238 { 239 char *topParam = req.urlParams.get("$top"); 240 if (topParam != nullptr) 241 { 242 char *ptr = nullptr; 243 top = std::strtoul(topParam, &ptr, 10); 244 if (*topParam == '\0' || *ptr != '\0') 245 { 246 messages::queryParameterValueTypeError(res, std::string(topParam), 247 "$top"); 248 return false; 249 } 250 if (top < 1U || top > maxEntriesPerPage) 251 { 252 253 messages::queryParameterOutOfRange( 254 res, std::to_string(top), "$top", 255 "1-" + std::to_string(maxEntriesPerPage)); 256 return false; 257 } 258 } 259 return true; 260 } 261 262 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID, 263 const bool firstEntry = true) 264 { 265 int ret = 0; 266 static uint64_t prevTs = 0; 267 static int index = 0; 268 if (firstEntry) 269 { 270 prevTs = 0; 271 } 272 273 // Get the entry timestamp 274 uint64_t curTs = 0; 275 ret = sd_journal_get_realtime_usec(journal, &curTs); 276 if (ret < 0) 277 { 278 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 279 << strerror(-ret); 280 return false; 281 } 282 // If the timestamp isn't unique, increment the index 283 if (curTs == prevTs) 284 { 285 index++; 286 } 287 else 288 { 289 // Otherwise, reset it 290 index = 0; 291 } 292 // Save the timestamp 293 prevTs = curTs; 294 295 entryID = std::to_string(curTs); 296 if (index > 0) 297 { 298 entryID += "_" + std::to_string(index); 299 } 300 return true; 301 } 302 303 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID, 304 const bool firstEntry = true) 305 { 306 static time_t prevTs = 0; 307 static int index = 0; 308 if (firstEntry) 309 { 310 prevTs = 0; 311 } 312 313 // Get the entry timestamp 314 std::time_t curTs = 0; 315 std::tm timeStruct = {}; 316 std::istringstream entryStream(logEntry); 317 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 318 { 319 curTs = std::mktime(&timeStruct); 320 } 321 // If the timestamp isn't unique, increment the index 322 if (curTs == prevTs) 323 { 324 index++; 325 } 326 else 327 { 328 // Otherwise, reset it 329 index = 0; 330 } 331 // Save the timestamp 332 prevTs = curTs; 333 334 entryID = std::to_string(curTs); 335 if (index > 0) 336 { 337 entryID += "_" + std::to_string(index); 338 } 339 return true; 340 } 341 342 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 343 uint64_t ×tamp, uint64_t &index) 344 { 345 if (entryID.empty()) 346 { 347 return false; 348 } 349 // Convert the unique ID back to a timestamp to find the entry 350 std::string_view tsStr(entryID); 351 352 auto underscorePos = tsStr.find("_"); 353 if (underscorePos != tsStr.npos) 354 { 355 // Timestamp has an index 356 tsStr.remove_suffix(tsStr.size() - underscorePos); 357 std::string_view indexStr(entryID); 358 indexStr.remove_prefix(underscorePos + 1); 359 std::size_t pos; 360 try 361 { 362 index = std::stoul(std::string(indexStr), &pos); 363 } 364 catch (std::invalid_argument &) 365 { 366 messages::resourceMissingAtURI(res, entryID); 367 return false; 368 } 369 catch (std::out_of_range &) 370 { 371 messages::resourceMissingAtURI(res, entryID); 372 return false; 373 } 374 if (pos != indexStr.size()) 375 { 376 messages::resourceMissingAtURI(res, entryID); 377 return false; 378 } 379 } 380 // Timestamp has no index 381 std::size_t pos; 382 try 383 { 384 timestamp = std::stoull(std::string(tsStr), &pos); 385 } 386 catch (std::invalid_argument &) 387 { 388 messages::resourceMissingAtURI(res, entryID); 389 return false; 390 } 391 catch (std::out_of_range &) 392 { 393 messages::resourceMissingAtURI(res, entryID); 394 return false; 395 } 396 if (pos != tsStr.size()) 397 { 398 messages::resourceMissingAtURI(res, entryID); 399 return false; 400 } 401 return true; 402 } 403 404 static bool 405 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles) 406 { 407 static const std::filesystem::path redfishLogDir = "/var/log"; 408 static const std::string redfishLogFilename = "redfish"; 409 410 // Loop through the directory looking for redfish log files 411 for (const std::filesystem::directory_entry &dirEnt : 412 std::filesystem::directory_iterator(redfishLogDir)) 413 { 414 // If we find a redfish log file, save the path 415 std::string filename = dirEnt.path().filename(); 416 if (boost::starts_with(filename, redfishLogFilename)) 417 { 418 redfishLogFiles.emplace_back(redfishLogDir / filename); 419 } 420 } 421 // As the log files rotate, they are appended with a ".#" that is higher for 422 // the older logs. Since we don't expect more than 10 log files, we 423 // can just sort the list to get them in order from newest to oldest 424 std::sort(redfishLogFiles.begin(), redfishLogFiles.end()); 425 426 return !redfishLogFiles.empty(); 427 } 428 429 static void ParseCrashdumpParameters( 430 const std::vector<std::pair<std::string, VariantType>> ¶ms, 431 std::string &filename, std::string ×tamp, std::string &logfile) 432 { 433 for (auto property : params) 434 { 435 if (property.first == "Timestamp") 436 { 437 const std::string *value = 438 sdbusplus::message::variant_ns::get_if<std::string>( 439 &property.second); 440 if (value != nullptr) 441 { 442 timestamp = *value; 443 } 444 } 445 else if (property.first == "Filename") 446 { 447 const std::string *value = 448 sdbusplus::message::variant_ns::get_if<std::string>( 449 &property.second); 450 if (value != nullptr) 451 { 452 filename = *value; 453 } 454 } 455 else if (property.first == "Log") 456 { 457 const std::string *value = 458 sdbusplus::message::variant_ns::get_if<std::string>( 459 &property.second); 460 if (value != nullptr) 461 { 462 logfile = *value; 463 } 464 } 465 } 466 } 467 468 constexpr char const *postCodeIface = "xyz.openbmc_project.State.Boot.PostCode"; 469 class SystemLogServiceCollection : public Node 470 { 471 public: 472 template <typename CrowApp> 473 SystemLogServiceCollection(CrowApp &app) : 474 Node(app, "/redfish/v1/Systems/system/LogServices/") 475 { 476 entityPrivileges = { 477 {boost::beast::http::verb::get, {{"Login"}}}, 478 {boost::beast::http::verb::head, {{"Login"}}}, 479 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 480 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 481 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 482 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 483 } 484 485 private: 486 /** 487 * Functions triggers appropriate requests on DBus 488 */ 489 void doGet(crow::Response &res, const crow::Request &req, 490 const std::vector<std::string> ¶ms) override 491 { 492 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 493 // Collections don't include the static data added by SubRoute because 494 // it has a duplicate entry for members 495 asyncResp->res.jsonValue["@odata.type"] = 496 "#LogServiceCollection.LogServiceCollection"; 497 asyncResp->res.jsonValue["@odata.id"] = 498 "/redfish/v1/Systems/system/LogServices"; 499 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 500 asyncResp->res.jsonValue["Description"] = 501 "Collection of LogServices for this Computer System"; 502 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 503 logServiceArray = nlohmann::json::array(); 504 logServiceArray.push_back( 505 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 506 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 507 logServiceArray.push_back( 508 {{"@odata.id", 509 "/redfish/v1/Systems/system/LogServices/Crashdump"}}); 510 #endif 511 asyncResp->res.jsonValue["Members@odata.count"] = 512 logServiceArray.size(); 513 514 crow::connections::systemBus->async_method_call( 515 [asyncResp](const boost::system::error_code ec, 516 const std::vector<std::string> &subtreePath) { 517 if (ec) 518 { 519 BMCWEB_LOG_ERROR << ec; 520 return; 521 } 522 523 for (auto &pathStr : subtreePath) 524 { 525 if (pathStr.find("PostCode") != std::string::npos) 526 { 527 nlohmann::json &logServiceArray = 528 asyncResp->res.jsonValue["Members"]; 529 logServiceArray.push_back( 530 {{"@odata.id", "/redfish/v1/Systems/system/" 531 "LogServices/PostCodes"}}); 532 asyncResp->res.jsonValue["Members@odata.count"] = 533 logServiceArray.size(); 534 return; 535 } 536 } 537 }, 538 "xyz.openbmc_project.ObjectMapper", 539 "/xyz/openbmc_project/object_mapper", 540 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 0, 541 std::array<const char *, 1>{postCodeIface}); 542 } 543 }; 544 545 class EventLogService : public Node 546 { 547 public: 548 template <typename CrowApp> 549 EventLogService(CrowApp &app) : 550 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 551 { 552 entityPrivileges = { 553 {boost::beast::http::verb::get, {{"Login"}}}, 554 {boost::beast::http::verb::head, {{"Login"}}}, 555 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 556 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 557 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 558 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 559 } 560 561 private: 562 void doGet(crow::Response &res, const crow::Request &req, 563 const std::vector<std::string> ¶ms) override 564 { 565 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 566 567 asyncResp->res.jsonValue["@odata.id"] = 568 "/redfish/v1/Systems/system/LogServices/EventLog"; 569 asyncResp->res.jsonValue["@odata.type"] = 570 "#LogService.v1_1_0.LogService"; 571 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 572 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 573 asyncResp->res.jsonValue["Id"] = "EventLog"; 574 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 575 asyncResp->res.jsonValue["Entries"] = { 576 {"@odata.id", 577 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 578 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 579 580 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 581 "Actions/LogService.ClearLog"}}; 582 } 583 }; 584 585 class JournalEventLogClear : public Node 586 { 587 public: 588 JournalEventLogClear(CrowApp &app) : 589 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 590 "LogService.ClearLog/") 591 { 592 entityPrivileges = { 593 {boost::beast::http::verb::get, {{"Login"}}}, 594 {boost::beast::http::verb::head, {{"Login"}}}, 595 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 596 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 597 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 598 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 599 } 600 601 private: 602 void doPost(crow::Response &res, const crow::Request &req, 603 const std::vector<std::string> ¶ms) override 604 { 605 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 606 607 // Clear the EventLog by deleting the log files 608 std::vector<std::filesystem::path> redfishLogFiles; 609 if (getRedfishLogFiles(redfishLogFiles)) 610 { 611 for (const std::filesystem::path &file : redfishLogFiles) 612 { 613 std::error_code ec; 614 std::filesystem::remove(file, ec); 615 } 616 } 617 618 // Reload rsyslog so it knows to start new log files 619 crow::connections::systemBus->async_method_call( 620 [asyncResp](const boost::system::error_code ec) { 621 if (ec) 622 { 623 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec; 624 messages::internalError(asyncResp->res); 625 return; 626 } 627 628 messages::success(asyncResp->res); 629 }, 630 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 631 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 632 "replace"); 633 } 634 }; 635 636 static int fillEventLogEntryJson(const std::string &logEntryID, 637 const std::string logEntry, 638 nlohmann::json &logEntryJson) 639 { 640 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 641 // First get the Timestamp 642 size_t space = logEntry.find_first_of(" "); 643 if (space == std::string::npos) 644 { 645 return 1; 646 } 647 std::string timestamp = logEntry.substr(0, space); 648 // Then get the log contents 649 size_t entryStart = logEntry.find_first_not_of(" ", space); 650 if (entryStart == std::string::npos) 651 { 652 return 1; 653 } 654 std::string_view entry(logEntry); 655 entry.remove_prefix(entryStart); 656 // Use split to separate the entry into its fields 657 std::vector<std::string> logEntryFields; 658 boost::split(logEntryFields, entry, boost::is_any_of(","), 659 boost::token_compress_on); 660 // We need at least a MessageId to be valid 661 if (logEntryFields.size() < 1) 662 { 663 return 1; 664 } 665 std::string &messageID = logEntryFields[0]; 666 667 // Get the Message from the MessageRegistry 668 const message_registries::Message *message = 669 message_registries::getMessage(messageID); 670 671 std::string msg; 672 std::string severity; 673 if (message != nullptr) 674 { 675 msg = message->message; 676 severity = message->severity; 677 } 678 679 // Get the MessageArgs from the log if there are any 680 boost::beast::span<std::string> messageArgs; 681 if (logEntryFields.size() > 1) 682 { 683 std::string &messageArgsStart = logEntryFields[1]; 684 // If the first string is empty, assume there are no MessageArgs 685 std::size_t messageArgsSize = 0; 686 if (!messageArgsStart.empty()) 687 { 688 messageArgsSize = logEntryFields.size() - 1; 689 } 690 691 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize); 692 693 // Fill the MessageArgs into the Message 694 int i = 0; 695 for (const std::string &messageArg : messageArgs) 696 { 697 std::string argStr = "%" + std::to_string(++i); 698 size_t argPos = msg.find(argStr); 699 if (argPos != std::string::npos) 700 { 701 msg.replace(argPos, argStr.length(), messageArg); 702 } 703 } 704 } 705 706 // Get the Created time from the timestamp. The log timestamp is in RFC3339 707 // format which matches the Redfish format except for the fractional seconds 708 // between the '.' and the '+', so just remove them. 709 std::size_t dot = timestamp.find_first_of("."); 710 std::size_t plus = timestamp.find_first_of("+"); 711 if (dot != std::string::npos && plus != std::string::npos) 712 { 713 timestamp.erase(dot, plus - dot); 714 } 715 716 // Fill in the log entry with the gathered data 717 logEntryJson = { 718 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 719 {"@odata.id", 720 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 721 logEntryID}, 722 {"Name", "System Event Log Entry"}, 723 {"Id", logEntryID}, 724 {"Message", std::move(msg)}, 725 {"MessageId", std::move(messageID)}, 726 {"MessageArgs", std::move(messageArgs)}, 727 {"EntryType", "Event"}, 728 {"Severity", std::move(severity)}, 729 {"Created", std::move(timestamp)}}; 730 return 0; 731 } 732 733 class JournalEventLogEntryCollection : public Node 734 { 735 public: 736 template <typename CrowApp> 737 JournalEventLogEntryCollection(CrowApp &app) : 738 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 739 { 740 entityPrivileges = { 741 {boost::beast::http::verb::get, {{"Login"}}}, 742 {boost::beast::http::verb::head, {{"Login"}}}, 743 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 744 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 745 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 746 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 747 } 748 749 private: 750 void doGet(crow::Response &res, const crow::Request &req, 751 const std::vector<std::string> ¶ms) override 752 { 753 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 754 uint64_t skip = 0; 755 uint64_t top = maxEntriesPerPage; // Show max entries by default 756 if (!getSkipParam(asyncResp->res, req, skip)) 757 { 758 return; 759 } 760 if (!getTopParam(asyncResp->res, req, top)) 761 { 762 return; 763 } 764 // Collections don't include the static data added by SubRoute because 765 // it has a duplicate entry for members 766 asyncResp->res.jsonValue["@odata.type"] = 767 "#LogEntryCollection.LogEntryCollection"; 768 asyncResp->res.jsonValue["@odata.id"] = 769 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 770 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 771 asyncResp->res.jsonValue["Description"] = 772 "Collection of System Event Log Entries"; 773 774 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 775 logEntryArray = nlohmann::json::array(); 776 // Go through the log files and create a unique ID for each entry 777 std::vector<std::filesystem::path> redfishLogFiles; 778 getRedfishLogFiles(redfishLogFiles); 779 uint64_t entryCount = 0; 780 std::string logEntry; 781 782 // Oldest logs are in the last file, so start there and loop backwards 783 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 784 it++) 785 { 786 std::ifstream logStream(*it); 787 if (!logStream.is_open()) 788 { 789 continue; 790 } 791 792 // Reset the unique ID on the first entry 793 bool firstEntry = true; 794 while (std::getline(logStream, logEntry)) 795 { 796 entryCount++; 797 // Handle paging using skip (number of entries to skip from the 798 // start) and top (number of entries to display) 799 if (entryCount <= skip || entryCount > skip + top) 800 { 801 continue; 802 } 803 804 std::string idStr; 805 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 806 { 807 continue; 808 } 809 810 if (firstEntry) 811 { 812 firstEntry = false; 813 } 814 815 logEntryArray.push_back({}); 816 nlohmann::json &bmcLogEntry = logEntryArray.back(); 817 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 818 { 819 messages::internalError(asyncResp->res); 820 return; 821 } 822 } 823 } 824 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 825 if (skip + top < entryCount) 826 { 827 asyncResp->res.jsonValue["Members@odata.nextLink"] = 828 "/redfish/v1/Systems/system/LogServices/EventLog/" 829 "Entries?$skip=" + 830 std::to_string(skip + top); 831 } 832 } 833 }; 834 835 class JournalEventLogEntry : public Node 836 { 837 public: 838 JournalEventLogEntry(CrowApp &app) : 839 Node(app, 840 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 841 std::string()) 842 { 843 entityPrivileges = { 844 {boost::beast::http::verb::get, {{"Login"}}}, 845 {boost::beast::http::verb::head, {{"Login"}}}, 846 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 847 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 848 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 849 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 850 } 851 852 private: 853 void doGet(crow::Response &res, const crow::Request &req, 854 const std::vector<std::string> ¶ms) override 855 { 856 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 857 if (params.size() != 1) 858 { 859 messages::internalError(asyncResp->res); 860 return; 861 } 862 const std::string &targetID = params[0]; 863 864 // Go through the log files and check the unique ID for each entry to 865 // find the target entry 866 std::vector<std::filesystem::path> redfishLogFiles; 867 getRedfishLogFiles(redfishLogFiles); 868 std::string logEntry; 869 870 // Oldest logs are in the last file, so start there and loop backwards 871 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 872 it++) 873 { 874 std::ifstream logStream(*it); 875 if (!logStream.is_open()) 876 { 877 continue; 878 } 879 880 // Reset the unique ID on the first entry 881 bool firstEntry = true; 882 while (std::getline(logStream, logEntry)) 883 { 884 std::string idStr; 885 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 886 { 887 continue; 888 } 889 890 if (firstEntry) 891 { 892 firstEntry = false; 893 } 894 895 if (idStr == targetID) 896 { 897 if (fillEventLogEntryJson(idStr, logEntry, 898 asyncResp->res.jsonValue) != 0) 899 { 900 messages::internalError(asyncResp->res); 901 return; 902 } 903 return; 904 } 905 } 906 } 907 // Requested ID was not found 908 messages::resourceMissingAtURI(asyncResp->res, targetID); 909 } 910 }; 911 912 class DBusEventLogEntryCollection : public Node 913 { 914 public: 915 template <typename CrowApp> 916 DBusEventLogEntryCollection(CrowApp &app) : 917 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 918 { 919 entityPrivileges = { 920 {boost::beast::http::verb::get, {{"Login"}}}, 921 {boost::beast::http::verb::head, {{"Login"}}}, 922 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 923 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 924 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 925 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 926 } 927 928 private: 929 void doGet(crow::Response &res, const crow::Request &req, 930 const std::vector<std::string> ¶ms) override 931 { 932 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 933 934 // Collections don't include the static data added by SubRoute because 935 // it has a duplicate entry for members 936 asyncResp->res.jsonValue["@odata.type"] = 937 "#LogEntryCollection.LogEntryCollection"; 938 asyncResp->res.jsonValue["@odata.id"] = 939 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 940 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 941 asyncResp->res.jsonValue["Description"] = 942 "Collection of System Event Log Entries"; 943 944 // DBus implementation of EventLog/Entries 945 // Make call to Logging Service to find all log entry objects 946 crow::connections::systemBus->async_method_call( 947 [asyncResp](const boost::system::error_code ec, 948 GetManagedObjectsType &resp) { 949 if (ec) 950 { 951 // TODO Handle for specific error code 952 BMCWEB_LOG_ERROR 953 << "getLogEntriesIfaceData resp_handler got error " 954 << ec; 955 messages::internalError(asyncResp->res); 956 return; 957 } 958 nlohmann::json &entriesArray = 959 asyncResp->res.jsonValue["Members"]; 960 entriesArray = nlohmann::json::array(); 961 for (auto &objectPath : resp) 962 { 963 for (auto &interfaceMap : objectPath.second) 964 { 965 if (interfaceMap.first != 966 "xyz.openbmc_project.Logging.Entry") 967 { 968 BMCWEB_LOG_DEBUG << "Bailing early on " 969 << interfaceMap.first; 970 continue; 971 } 972 entriesArray.push_back({}); 973 nlohmann::json &thisEntry = entriesArray.back(); 974 uint32_t *id = nullptr; 975 std::time_t timestamp{}; 976 std::string *severity = nullptr; 977 std::string *message = nullptr; 978 for (auto &propertyMap : interfaceMap.second) 979 { 980 if (propertyMap.first == "Id") 981 { 982 id = sdbusplus::message::variant_ns::get_if< 983 uint32_t>(&propertyMap.second); 984 if (id == nullptr) 985 { 986 messages::propertyMissing(asyncResp->res, 987 "Id"); 988 } 989 } 990 else if (propertyMap.first == "Timestamp") 991 { 992 const uint64_t *millisTimeStamp = 993 std::get_if<uint64_t>(&propertyMap.second); 994 if (millisTimeStamp == nullptr) 995 { 996 messages::propertyMissing(asyncResp->res, 997 "Timestamp"); 998 continue; 999 } 1000 // Retrieve Created property with format: 1001 // yyyy-mm-ddThh:mm:ss 1002 std::chrono::milliseconds chronoTimeStamp( 1003 *millisTimeStamp); 1004 timestamp = std::chrono::duration_cast< 1005 std::chrono::duration<int>>( 1006 chronoTimeStamp) 1007 .count(); 1008 } 1009 else if (propertyMap.first == "Severity") 1010 { 1011 severity = std::get_if<std::string>( 1012 &propertyMap.second); 1013 if (severity == nullptr) 1014 { 1015 messages::propertyMissing(asyncResp->res, 1016 "Severity"); 1017 } 1018 } 1019 else if (propertyMap.first == "Message") 1020 { 1021 message = std::get_if<std::string>( 1022 &propertyMap.second); 1023 if (message == nullptr) 1024 { 1025 messages::propertyMissing(asyncResp->res, 1026 "Message"); 1027 } 1028 } 1029 } 1030 thisEntry = { 1031 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1032 {"@odata.id", 1033 "/redfish/v1/Systems/system/LogServices/EventLog/" 1034 "Entries/" + 1035 std::to_string(*id)}, 1036 {"Name", "System Event Log Entry"}, 1037 {"Id", std::to_string(*id)}, 1038 {"Message", *message}, 1039 {"EntryType", "Event"}, 1040 {"Severity", 1041 translateSeverityDbusToRedfish(*severity)}, 1042 {"Created", crow::utility::getDateTime(timestamp)}}; 1043 } 1044 } 1045 std::sort(entriesArray.begin(), entriesArray.end(), 1046 [](const nlohmann::json &left, 1047 const nlohmann::json &right) { 1048 return (left["Id"] <= right["Id"]); 1049 }); 1050 asyncResp->res.jsonValue["Members@odata.count"] = 1051 entriesArray.size(); 1052 }, 1053 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 1054 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1055 } 1056 }; 1057 1058 class DBusEventLogEntry : public Node 1059 { 1060 public: 1061 DBusEventLogEntry(CrowApp &app) : 1062 Node(app, 1063 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 1064 std::string()) 1065 { 1066 entityPrivileges = { 1067 {boost::beast::http::verb::get, {{"Login"}}}, 1068 {boost::beast::http::verb::head, {{"Login"}}}, 1069 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1070 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1071 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1072 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1073 } 1074 1075 private: 1076 void doGet(crow::Response &res, const crow::Request &req, 1077 const std::vector<std::string> ¶ms) override 1078 { 1079 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1080 if (params.size() != 1) 1081 { 1082 messages::internalError(asyncResp->res); 1083 return; 1084 } 1085 const std::string &entryID = params[0]; 1086 1087 // DBus implementation of EventLog/Entries 1088 // Make call to Logging Service to find all log entry objects 1089 crow::connections::systemBus->async_method_call( 1090 [asyncResp, entryID](const boost::system::error_code ec, 1091 GetManagedPropertyType &resp) { 1092 if (ec) 1093 { 1094 BMCWEB_LOG_ERROR 1095 << "EventLogEntry (DBus) resp_handler got error " << ec; 1096 messages::internalError(asyncResp->res); 1097 return; 1098 } 1099 uint32_t *id = nullptr; 1100 std::time_t timestamp{}; 1101 std::string *severity = nullptr; 1102 std::string *message = nullptr; 1103 for (auto &propertyMap : resp) 1104 { 1105 if (propertyMap.first == "Id") 1106 { 1107 id = std::get_if<uint32_t>(&propertyMap.second); 1108 if (id == nullptr) 1109 { 1110 messages::propertyMissing(asyncResp->res, "Id"); 1111 } 1112 } 1113 else if (propertyMap.first == "Timestamp") 1114 { 1115 const uint64_t *millisTimeStamp = 1116 std::get_if<uint64_t>(&propertyMap.second); 1117 if (millisTimeStamp == nullptr) 1118 { 1119 messages::propertyMissing(asyncResp->res, 1120 "Timestamp"); 1121 continue; 1122 } 1123 // Retrieve Created property with format: 1124 // yyyy-mm-ddThh:mm:ss 1125 std::chrono::milliseconds chronoTimeStamp( 1126 *millisTimeStamp); 1127 timestamp = 1128 std::chrono::duration_cast< 1129 std::chrono::duration<int>>(chronoTimeStamp) 1130 .count(); 1131 } 1132 else if (propertyMap.first == "Severity") 1133 { 1134 severity = 1135 std::get_if<std::string>(&propertyMap.second); 1136 if (severity == nullptr) 1137 { 1138 messages::propertyMissing(asyncResp->res, 1139 "Severity"); 1140 } 1141 } 1142 else if (propertyMap.first == "Message") 1143 { 1144 message = std::get_if<std::string>(&propertyMap.second); 1145 if (message == nullptr) 1146 { 1147 messages::propertyMissing(asyncResp->res, 1148 "Message"); 1149 } 1150 } 1151 } 1152 if (id == nullptr || message == nullptr || severity == nullptr) 1153 { 1154 return; 1155 } 1156 asyncResp->res.jsonValue = { 1157 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1158 {"@odata.id", 1159 "/redfish/v1/Systems/system/LogServices/EventLog/" 1160 "Entries/" + 1161 std::to_string(*id)}, 1162 {"Name", "System Event Log Entry"}, 1163 {"Id", std::to_string(*id)}, 1164 {"Message", *message}, 1165 {"EntryType", "Event"}, 1166 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1167 {"Created", crow::utility::getDateTime(timestamp)}}; 1168 }, 1169 "xyz.openbmc_project.Logging", 1170 "/xyz/openbmc_project/logging/entry/" + entryID, 1171 "org.freedesktop.DBus.Properties", "GetAll", 1172 "xyz.openbmc_project.Logging.Entry"); 1173 } 1174 1175 void doDelete(crow::Response &res, const crow::Request &req, 1176 const std::vector<std::string> ¶ms) override 1177 { 1178 1179 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1180 1181 auto asyncResp = std::make_shared<AsyncResp>(res); 1182 1183 if (params.size() != 1) 1184 { 1185 messages::internalError(asyncResp->res); 1186 return; 1187 } 1188 std::string entryID = params[0]; 1189 1190 dbus::utility::escapePathForDbus(entryID); 1191 1192 // Process response from Logging service. 1193 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1194 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1195 if (ec) 1196 { 1197 // TODO Handle for specific error code 1198 BMCWEB_LOG_ERROR 1199 << "EventLogEntry (DBus) doDelete respHandler got error " 1200 << ec; 1201 asyncResp->res.result( 1202 boost::beast::http::status::internal_server_error); 1203 return; 1204 } 1205 1206 asyncResp->res.result(boost::beast::http::status::ok); 1207 }; 1208 1209 // Make call to Logging service to request Delete Log 1210 crow::connections::systemBus->async_method_call( 1211 respHandler, "xyz.openbmc_project.Logging", 1212 "/xyz/openbmc_project/logging/entry/" + entryID, 1213 "xyz.openbmc_project.Object.Delete", "Delete"); 1214 } 1215 }; 1216 1217 class BMCLogServiceCollection : public Node 1218 { 1219 public: 1220 template <typename CrowApp> 1221 BMCLogServiceCollection(CrowApp &app) : 1222 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1223 { 1224 entityPrivileges = { 1225 {boost::beast::http::verb::get, {{"Login"}}}, 1226 {boost::beast::http::verb::head, {{"Login"}}}, 1227 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1228 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1229 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1230 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1231 } 1232 1233 private: 1234 /** 1235 * Functions triggers appropriate requests on DBus 1236 */ 1237 void doGet(crow::Response &res, const crow::Request &req, 1238 const std::vector<std::string> ¶ms) override 1239 { 1240 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1241 // Collections don't include the static data added by SubRoute because 1242 // it has a duplicate entry for members 1243 asyncResp->res.jsonValue["@odata.type"] = 1244 "#LogServiceCollection.LogServiceCollection"; 1245 asyncResp->res.jsonValue["@odata.id"] = 1246 "/redfish/v1/Managers/bmc/LogServices"; 1247 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1248 asyncResp->res.jsonValue["Description"] = 1249 "Collection of LogServices for this Manager"; 1250 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 1251 logServiceArray = nlohmann::json::array(); 1252 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1253 logServiceArray.push_back( 1254 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1255 #endif 1256 asyncResp->res.jsonValue["Members@odata.count"] = 1257 logServiceArray.size(); 1258 } 1259 }; 1260 1261 class BMCJournalLogService : public Node 1262 { 1263 public: 1264 template <typename CrowApp> 1265 BMCJournalLogService(CrowApp &app) : 1266 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1267 { 1268 entityPrivileges = { 1269 {boost::beast::http::verb::get, {{"Login"}}}, 1270 {boost::beast::http::verb::head, {{"Login"}}}, 1271 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1272 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1273 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1274 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1275 } 1276 1277 private: 1278 void doGet(crow::Response &res, const crow::Request &req, 1279 const std::vector<std::string> ¶ms) override 1280 { 1281 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1282 asyncResp->res.jsonValue["@odata.type"] = 1283 "#LogService.v1_1_0.LogService"; 1284 asyncResp->res.jsonValue["@odata.id"] = 1285 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1286 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1287 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1288 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1289 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1290 asyncResp->res.jsonValue["Entries"] = { 1291 {"@odata.id", 1292 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1293 } 1294 }; 1295 1296 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 1297 sd_journal *journal, 1298 nlohmann::json &bmcJournalLogEntryJson) 1299 { 1300 // Get the Log Entry contents 1301 int ret = 0; 1302 1303 std::string_view msg; 1304 ret = getJournalMetadata(journal, "MESSAGE", msg); 1305 if (ret < 0) 1306 { 1307 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1308 return 1; 1309 } 1310 1311 // Get the severity from the PRIORITY field 1312 long int severity = 8; // Default to an invalid priority 1313 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1314 if (ret < 0) 1315 { 1316 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1317 } 1318 1319 // Get the Created time from the timestamp 1320 std::string entryTimeStr; 1321 if (!getEntryTimestamp(journal, entryTimeStr)) 1322 { 1323 return 1; 1324 } 1325 1326 // Fill in the log entry with the gathered data 1327 bmcJournalLogEntryJson = { 1328 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1329 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1330 bmcJournalLogEntryID}, 1331 {"Name", "BMC Journal Entry"}, 1332 {"Id", bmcJournalLogEntryID}, 1333 {"Message", msg}, 1334 {"EntryType", "Oem"}, 1335 {"Severity", 1336 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1337 {"OemRecordFormat", "BMC Journal Entry"}, 1338 {"Created", std::move(entryTimeStr)}}; 1339 return 0; 1340 } 1341 1342 class BMCJournalLogEntryCollection : public Node 1343 { 1344 public: 1345 template <typename CrowApp> 1346 BMCJournalLogEntryCollection(CrowApp &app) : 1347 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1348 { 1349 entityPrivileges = { 1350 {boost::beast::http::verb::get, {{"Login"}}}, 1351 {boost::beast::http::verb::head, {{"Login"}}}, 1352 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1353 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1354 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1355 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1356 } 1357 1358 private: 1359 void doGet(crow::Response &res, const crow::Request &req, 1360 const std::vector<std::string> ¶ms) override 1361 { 1362 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1363 static constexpr const long maxEntriesPerPage = 1000; 1364 uint64_t skip = 0; 1365 uint64_t top = maxEntriesPerPage; // Show max entries by default 1366 if (!getSkipParam(asyncResp->res, req, skip)) 1367 { 1368 return; 1369 } 1370 if (!getTopParam(asyncResp->res, req, top)) 1371 { 1372 return; 1373 } 1374 // Collections don't include the static data added by SubRoute because 1375 // it has a duplicate entry for members 1376 asyncResp->res.jsonValue["@odata.type"] = 1377 "#LogEntryCollection.LogEntryCollection"; 1378 asyncResp->res.jsonValue["@odata.id"] = 1379 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1380 asyncResp->res.jsonValue["@odata.id"] = 1381 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1382 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1383 asyncResp->res.jsonValue["Description"] = 1384 "Collection of BMC Journal Entries"; 1385 asyncResp->res.jsonValue["@odata.id"] = 1386 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1387 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1388 logEntryArray = nlohmann::json::array(); 1389 1390 // Go through the journal and use the timestamp to create a unique ID 1391 // for each entry 1392 sd_journal *journalTmp = nullptr; 1393 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1394 if (ret < 0) 1395 { 1396 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1397 messages::internalError(asyncResp->res); 1398 return; 1399 } 1400 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1401 journalTmp, sd_journal_close); 1402 journalTmp = nullptr; 1403 uint64_t entryCount = 0; 1404 // Reset the unique ID on the first entry 1405 bool firstEntry = true; 1406 SD_JOURNAL_FOREACH(journal.get()) 1407 { 1408 entryCount++; 1409 // Handle paging using skip (number of entries to skip from the 1410 // start) and top (number of entries to display) 1411 if (entryCount <= skip || entryCount > skip + top) 1412 { 1413 continue; 1414 } 1415 1416 std::string idStr; 1417 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1418 { 1419 continue; 1420 } 1421 1422 if (firstEntry) 1423 { 1424 firstEntry = false; 1425 } 1426 1427 logEntryArray.push_back({}); 1428 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1429 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1430 bmcJournalLogEntry) != 0) 1431 { 1432 messages::internalError(asyncResp->res); 1433 return; 1434 } 1435 } 1436 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1437 if (skip + top < entryCount) 1438 { 1439 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1440 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1441 std::to_string(skip + top); 1442 } 1443 } 1444 }; 1445 1446 class BMCJournalLogEntry : public Node 1447 { 1448 public: 1449 BMCJournalLogEntry(CrowApp &app) : 1450 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1451 std::string()) 1452 { 1453 entityPrivileges = { 1454 {boost::beast::http::verb::get, {{"Login"}}}, 1455 {boost::beast::http::verb::head, {{"Login"}}}, 1456 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1457 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1458 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1459 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1460 } 1461 1462 private: 1463 void doGet(crow::Response &res, const crow::Request &req, 1464 const std::vector<std::string> ¶ms) override 1465 { 1466 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1467 if (params.size() != 1) 1468 { 1469 messages::internalError(asyncResp->res); 1470 return; 1471 } 1472 const std::string &entryID = params[0]; 1473 // Convert the unique ID back to a timestamp to find the entry 1474 uint64_t ts = 0; 1475 uint64_t index = 0; 1476 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1477 { 1478 return; 1479 } 1480 1481 sd_journal *journalTmp = nullptr; 1482 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1483 if (ret < 0) 1484 { 1485 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1486 messages::internalError(asyncResp->res); 1487 return; 1488 } 1489 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1490 journalTmp, sd_journal_close); 1491 journalTmp = nullptr; 1492 // Go to the timestamp in the log and move to the entry at the index 1493 // tracking the unique ID 1494 std::string idStr; 1495 bool firstEntry = true; 1496 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1497 for (uint64_t i = 0; i <= index; i++) 1498 { 1499 sd_journal_next(journal.get()); 1500 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1501 { 1502 messages::internalError(asyncResp->res); 1503 return; 1504 } 1505 if (firstEntry) 1506 { 1507 firstEntry = false; 1508 } 1509 } 1510 // Confirm that the entry ID matches what was requested 1511 if (idStr != entryID) 1512 { 1513 messages::resourceMissingAtURI(asyncResp->res, entryID); 1514 return; 1515 } 1516 1517 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1518 asyncResp->res.jsonValue) != 0) 1519 { 1520 messages::internalError(asyncResp->res); 1521 return; 1522 } 1523 } 1524 }; 1525 1526 class CrashdumpService : public Node 1527 { 1528 public: 1529 template <typename CrowApp> 1530 CrashdumpService(CrowApp &app) : 1531 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1532 { 1533 // Note: Deviated from redfish privilege registry for GET & HEAD 1534 // method for security reasons. 1535 entityPrivileges = { 1536 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1537 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1538 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1539 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1540 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1541 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1542 } 1543 1544 private: 1545 /** 1546 * Functions triggers appropriate requests on DBus 1547 */ 1548 void doGet(crow::Response &res, const crow::Request &req, 1549 const std::vector<std::string> ¶ms) override 1550 { 1551 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1552 // Copy over the static data to include the entries added by SubRoute 1553 asyncResp->res.jsonValue["@odata.id"] = 1554 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1555 asyncResp->res.jsonValue["@odata.type"] = 1556 "#LogService.v1_1_0.LogService"; 1557 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 1558 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 1559 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 1560 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1561 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1562 asyncResp->res.jsonValue["Entries"] = { 1563 {"@odata.id", 1564 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1565 asyncResp->res.jsonValue["Actions"] = { 1566 {"#LogService.ClearLog", 1567 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1568 "Actions/LogService.ClearLog"}}}, 1569 {"Oem", 1570 {{"#Crashdump.OnDemand", 1571 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1572 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1573 1574 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1575 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1576 {"#Crashdump.SendRawPeci", 1577 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1578 "Actions/Oem/Crashdump.SendRawPeci"}}}); 1579 #endif 1580 } 1581 }; 1582 1583 class CrashdumpClear : public Node 1584 { 1585 public: 1586 CrashdumpClear(CrowApp &app) : 1587 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 1588 "LogService.ClearLog/") 1589 { 1590 // Note: Deviated from redfish privilege registry for GET & HEAD 1591 // method for security reasons. 1592 entityPrivileges = { 1593 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1594 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1595 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1596 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1597 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1598 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1599 } 1600 1601 private: 1602 void doPost(crow::Response &res, const crow::Request &req, 1603 const std::vector<std::string> ¶ms) override 1604 { 1605 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1606 1607 crow::connections::systemBus->async_method_call( 1608 [asyncResp](const boost::system::error_code ec, 1609 const std::string &resp) { 1610 if (ec) 1611 { 1612 messages::internalError(asyncResp->res); 1613 return; 1614 } 1615 messages::success(asyncResp->res); 1616 }, 1617 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 1618 } 1619 }; 1620 1621 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp, 1622 const std::string &logID, 1623 nlohmann::json &logEntryJson) 1624 { 1625 auto getStoredLogCallback = 1626 [asyncResp, logID, &logEntryJson]( 1627 const boost::system::error_code ec, 1628 const std::vector<std::pair<std::string, VariantType>> ¶ms) { 1629 if (ec) 1630 { 1631 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1632 if (ec.value() == 1633 boost::system::linux_error::bad_request_descriptor) 1634 { 1635 messages::resourceNotFound(asyncResp->res, "LogEntry", 1636 logID); 1637 } 1638 else 1639 { 1640 messages::internalError(asyncResp->res); 1641 } 1642 return; 1643 } 1644 1645 std::string timestamp{}; 1646 std::string filename{}; 1647 std::string logfile{}; 1648 ParseCrashdumpParameters(params, filename, timestamp, logfile); 1649 1650 if (filename.empty() || timestamp.empty()) 1651 { 1652 messages::resourceMissingAtURI(asyncResp->res, logID); 1653 return; 1654 } 1655 1656 std::string crashdumpURI = 1657 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1658 logID + "/" + filename; 1659 logEntryJson = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1660 {"@odata.id", "/redfish/v1/Systems/system/" 1661 "LogServices/Crashdump/Entries/" + 1662 logID}, 1663 {"Name", "CPU Crashdump"}, 1664 {"Id", logID}, 1665 {"EntryType", "Oem"}, 1666 {"OemRecordFormat", "Crashdump URI"}, 1667 {"Message", std::move(crashdumpURI)}, 1668 {"Created", std::move(timestamp)}}; 1669 }; 1670 crow::connections::systemBus->async_method_call( 1671 std::move(getStoredLogCallback), crashdumpObject, 1672 crashdumpPath + std::string("/") + logID, 1673 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 1674 } 1675 1676 class CrashdumpEntryCollection : public Node 1677 { 1678 public: 1679 template <typename CrowApp> 1680 CrashdumpEntryCollection(CrowApp &app) : 1681 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 1682 { 1683 // Note: Deviated from redfish privilege registry for GET & HEAD 1684 // method for security reasons. 1685 entityPrivileges = { 1686 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1687 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1688 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1689 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1690 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1691 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1692 } 1693 1694 private: 1695 /** 1696 * Functions triggers appropriate requests on DBus 1697 */ 1698 void doGet(crow::Response &res, const crow::Request &req, 1699 const std::vector<std::string> ¶ms) override 1700 { 1701 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1702 // Collections don't include the static data added by SubRoute because 1703 // it has a duplicate entry for members 1704 auto getLogEntriesCallback = [asyncResp]( 1705 const boost::system::error_code ec, 1706 const std::vector<std::string> &resp) { 1707 if (ec) 1708 { 1709 if (ec.value() != 1710 boost::system::errc::no_such_file_or_directory) 1711 { 1712 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1713 << ec.message(); 1714 messages::internalError(asyncResp->res); 1715 return; 1716 } 1717 } 1718 asyncResp->res.jsonValue["@odata.type"] = 1719 "#LogEntryCollection.LogEntryCollection"; 1720 asyncResp->res.jsonValue["@odata.id"] = 1721 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 1722 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 1723 asyncResp->res.jsonValue["Description"] = 1724 "Collection of Crashdump Entries"; 1725 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1726 logEntryArray = nlohmann::json::array(); 1727 std::vector<std::string> logIDs; 1728 // Get the list of log entries and build up an empty array big 1729 // enough to hold them 1730 for (const std::string &objpath : resp) 1731 { 1732 // Get the log ID 1733 std::size_t lastPos = objpath.rfind("/"); 1734 if (lastPos == std::string::npos) 1735 { 1736 continue; 1737 } 1738 logIDs.emplace_back(objpath.substr(lastPos + 1)); 1739 1740 // Add a space for the log entry to the array 1741 logEntryArray.push_back({}); 1742 } 1743 // Now go through and set up async calls to fill in the entries 1744 size_t index = 0; 1745 for (const std::string &logID : logIDs) 1746 { 1747 // Add the log entry to the array 1748 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 1749 } 1750 asyncResp->res.jsonValue["Members@odata.count"] = 1751 logEntryArray.size(); 1752 }; 1753 crow::connections::systemBus->async_method_call( 1754 std::move(getLogEntriesCallback), 1755 "xyz.openbmc_project.ObjectMapper", 1756 "/xyz/openbmc_project/object_mapper", 1757 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1758 std::array<const char *, 1>{crashdumpInterface}); 1759 } 1760 }; 1761 1762 class CrashdumpEntry : public Node 1763 { 1764 public: 1765 CrashdumpEntry(CrowApp &app) : 1766 Node(app, 1767 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 1768 std::string()) 1769 { 1770 // Note: Deviated from redfish privilege registry for GET & HEAD 1771 // method for security reasons. 1772 entityPrivileges = { 1773 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1774 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1775 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1776 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1777 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1778 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1779 } 1780 1781 private: 1782 void doGet(crow::Response &res, const crow::Request &req, 1783 const std::vector<std::string> ¶ms) override 1784 { 1785 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1786 if (params.size() != 1) 1787 { 1788 messages::internalError(asyncResp->res); 1789 return; 1790 } 1791 const std::string &logID = params[0]; 1792 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 1793 } 1794 }; 1795 1796 class CrashdumpFile : public Node 1797 { 1798 public: 1799 CrashdumpFile(CrowApp &app) : 1800 Node(app, 1801 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/" 1802 "<str>/", 1803 std::string(), std::string()) 1804 { 1805 // Note: Deviated from redfish privilege registry for GET & HEAD 1806 // method for security reasons. 1807 entityPrivileges = { 1808 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1809 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1810 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1811 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1812 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1813 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1814 } 1815 1816 private: 1817 void doGet(crow::Response &res, const crow::Request &req, 1818 const std::vector<std::string> ¶ms) override 1819 { 1820 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1821 if (params.size() != 2) 1822 { 1823 messages::internalError(asyncResp->res); 1824 return; 1825 } 1826 const std::string &logID = params[0]; 1827 const std::string &fileName = params[1]; 1828 1829 auto getStoredLogCallback = 1830 [asyncResp, logID, fileName]( 1831 const boost::system::error_code ec, 1832 const std::vector<std::pair<std::string, VariantType>> &resp) { 1833 if (ec) 1834 { 1835 BMCWEB_LOG_DEBUG << "failed to get log ec: " 1836 << ec.message(); 1837 messages::internalError(asyncResp->res); 1838 return; 1839 } 1840 1841 std::string dbusFilename{}; 1842 std::string dbusTimestamp{}; 1843 std::string dbusFilepath{}; 1844 1845 ParseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 1846 dbusFilepath); 1847 1848 if (dbusFilename.empty() || dbusTimestamp.empty() || 1849 dbusFilepath.empty()) 1850 { 1851 messages::resourceMissingAtURI(asyncResp->res, fileName); 1852 return; 1853 } 1854 1855 // Verify the file name parameter is correct 1856 if (fileName != dbusFilename) 1857 { 1858 messages::resourceMissingAtURI(asyncResp->res, fileName); 1859 return; 1860 } 1861 1862 if (!std::filesystem::exists(dbusFilepath)) 1863 { 1864 messages::resourceMissingAtURI(asyncResp->res, fileName); 1865 return; 1866 } 1867 std::ifstream ifs(dbusFilepath, std::ios::in | 1868 std::ios::binary | 1869 std::ios::ate); 1870 std::ifstream::pos_type fileSize = ifs.tellg(); 1871 if (fileSize < 0) 1872 { 1873 messages::generalError(asyncResp->res); 1874 return; 1875 } 1876 ifs.seekg(0, std::ios::beg); 1877 1878 auto crashData = std::make_unique<char[]>( 1879 static_cast<unsigned int>(fileSize)); 1880 1881 ifs.read(crashData.get(), static_cast<int>(fileSize)); 1882 1883 // The cast to std::string is intentional in order to use the 1884 // assign() that applies move mechanics 1885 asyncResp->res.body().assign( 1886 static_cast<std::string>(crashData.get())); 1887 1888 // Configure this to be a file download when accessed from 1889 // a browser 1890 asyncResp->res.addHeader("Content-Disposition", "attachment"); 1891 }; 1892 crow::connections::systemBus->async_method_call( 1893 std::move(getStoredLogCallback), crashdumpObject, 1894 crashdumpPath + std::string("/") + logID, 1895 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 1896 } 1897 }; 1898 1899 class OnDemandCrashdump : public Node 1900 { 1901 public: 1902 OnDemandCrashdump(CrowApp &app) : 1903 Node(app, 1904 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1905 "Crashdump.OnDemand/") 1906 { 1907 // Note: Deviated from redfish privilege registry for GET & HEAD 1908 // method for security reasons. 1909 entityPrivileges = { 1910 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1911 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1912 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1913 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1914 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1915 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1916 } 1917 1918 private: 1919 void doPost(crow::Response &res, const crow::Request &req, 1920 const std::vector<std::string> ¶ms) override 1921 { 1922 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1923 1924 auto generateonDemandLogCallback = [asyncResp, 1925 req](const boost::system::error_code 1926 ec, 1927 const std::string &resp) { 1928 if (ec) 1929 { 1930 if (ec.value() == boost::system::errc::operation_not_supported) 1931 { 1932 messages::resourceInStandby(asyncResp->res); 1933 } 1934 else if (ec.value() == 1935 boost::system::errc::device_or_resource_busy) 1936 { 1937 messages::serviceTemporarilyUnavailable(asyncResp->res, 1938 "60"); 1939 } 1940 else 1941 { 1942 messages::internalError(asyncResp->res); 1943 } 1944 return; 1945 } 1946 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 1947 [](boost::system::error_code err, sdbusplus::message::message &, 1948 const std::shared_ptr<task::TaskData> &taskData) { 1949 if (!err) 1950 { 1951 taskData->messages.emplace_back(messages::success()); 1952 taskData->state = "Completed"; 1953 } 1954 return task::completed; 1955 }, 1956 "type='signal',interface='org.freedesktop.DBus.Properties'," 1957 "member='PropertiesChanged',arg0namespace='com.intel." 1958 "crashdump'"); 1959 task->startTimer(std::chrono::minutes(5)); 1960 task->populateResp(asyncResp->res); 1961 task->payload.emplace(req); 1962 }; 1963 crow::connections::systemBus->async_method_call( 1964 std::move(generateonDemandLogCallback), crashdumpObject, 1965 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog"); 1966 } 1967 }; 1968 1969 class SendRawPECI : public Node 1970 { 1971 public: 1972 SendRawPECI(CrowApp &app) : 1973 Node(app, 1974 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1975 "Crashdump.SendRawPeci/") 1976 { 1977 // Note: Deviated from redfish privilege registry for GET & HEAD 1978 // method for security reasons. 1979 entityPrivileges = { 1980 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1981 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1982 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1983 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1984 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1985 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1986 } 1987 1988 private: 1989 void doPost(crow::Response &res, const crow::Request &req, 1990 const std::vector<std::string> ¶ms) override 1991 { 1992 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1993 std::vector<std::vector<uint8_t>> peciCommands; 1994 1995 nlohmann::json reqJson = 1996 nlohmann::json::parse(req.body, nullptr, false); 1997 if (reqJson.find("PECICommands") != reqJson.end()) 1998 { 1999 if (!json_util::readJson(req, res, "PECICommands", peciCommands)) 2000 { 2001 return; 2002 } 2003 uint32_t idx = 0; 2004 for (auto const &cmd : peciCommands) 2005 { 2006 if (cmd.size() < 3) 2007 { 2008 std::string s("["); 2009 for (auto const &val : cmd) 2010 { 2011 if (val != *cmd.begin()) 2012 { 2013 s += ","; 2014 } 2015 s += std::to_string(val); 2016 } 2017 s += "]"; 2018 messages::actionParameterValueFormatError( 2019 res, s, "PECICommands[" + std::to_string(idx) + "]", 2020 "SendRawPeci"); 2021 return; 2022 } 2023 idx++; 2024 } 2025 } 2026 else 2027 { 2028 /* This interface is deprecated */ 2029 uint8_t clientAddress = 0; 2030 uint8_t readLength = 0; 2031 std::vector<uint8_t> peciCommand; 2032 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 2033 "ReadLength", readLength, "PECICommand", 2034 peciCommand)) 2035 { 2036 return; 2037 } 2038 peciCommands.push_back({clientAddress, 0, readLength}); 2039 peciCommands[0].insert(peciCommands[0].end(), peciCommand.begin(), 2040 peciCommand.end()); 2041 } 2042 // Callback to return the Raw PECI response 2043 auto sendRawPECICallback = 2044 [asyncResp](const boost::system::error_code ec, 2045 const std::vector<std::vector<uint8_t>> &resp) { 2046 if (ec) 2047 { 2048 BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: " 2049 << ec.message(); 2050 messages::internalError(asyncResp->res); 2051 return; 2052 } 2053 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 2054 {"PECIResponse", resp}}; 2055 }; 2056 // Call the SendRawPECI command with the provided data 2057 crow::connections::systemBus->async_method_call( 2058 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath, 2059 crashdumpRawPECIInterface, "SendRawPeci", peciCommands); 2060 } 2061 }; 2062 2063 /** 2064 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2065 */ 2066 class DBusLogServiceActionsClear : public Node 2067 { 2068 public: 2069 DBusLogServiceActionsClear(CrowApp &app) : 2070 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2071 "LogService.ClearLog/") 2072 { 2073 entityPrivileges = { 2074 {boost::beast::http::verb::get, {{"Login"}}}, 2075 {boost::beast::http::verb::head, {{"Login"}}}, 2076 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2077 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2078 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2079 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2080 } 2081 2082 private: 2083 /** 2084 * Function handles POST method request. 2085 * The Clear Log actions does not require any parameter.The action deletes 2086 * all entries found in the Entries collection for this Log Service. 2087 */ 2088 void doPost(crow::Response &res, const crow::Request &req, 2089 const std::vector<std::string> ¶ms) override 2090 { 2091 BMCWEB_LOG_DEBUG << "Do delete all entries."; 2092 2093 auto asyncResp = std::make_shared<AsyncResp>(res); 2094 // Process response from Logging service. 2095 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 2096 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 2097 if (ec) 2098 { 2099 // TODO Handle for specific error code 2100 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 2101 asyncResp->res.result( 2102 boost::beast::http::status::internal_server_error); 2103 return; 2104 } 2105 2106 asyncResp->res.result(boost::beast::http::status::no_content); 2107 }; 2108 2109 // Make call to Logging service to request Clear Log 2110 crow::connections::systemBus->async_method_call( 2111 resp_handler, "xyz.openbmc_project.Logging", 2112 "/xyz/openbmc_project/logging", 2113 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2114 } 2115 }; 2116 2117 /**************************************************** 2118 * Redfish PostCode interfaces 2119 * using DBUS interface: getPostCodesTS 2120 ******************************************************/ 2121 class PostCodesLogService : public Node 2122 { 2123 public: 2124 PostCodesLogService(CrowApp &app) : 2125 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 2126 { 2127 entityPrivileges = { 2128 {boost::beast::http::verb::get, {{"Login"}}}, 2129 {boost::beast::http::verb::head, {{"Login"}}}, 2130 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2131 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2132 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2133 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2134 } 2135 2136 private: 2137 void doGet(crow::Response &res, const crow::Request &req, 2138 const std::vector<std::string> ¶ms) override 2139 { 2140 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2141 2142 asyncResp->res.jsonValue = { 2143 {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"}, 2144 {"@odata.type", "#LogService.v1_1_0.LogService"}, 2145 {"@odata.context", "/redfish/v1/$metadata#LogService.LogService"}, 2146 {"Name", "POST Code Log Service"}, 2147 {"Description", "POST Code Log Service"}, 2148 {"Id", "BIOS POST Code Log"}, 2149 {"OverWritePolicy", "WrapsWhenFull"}, 2150 {"Entries", 2151 {{"@odata.id", 2152 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 2153 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 2154 {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/" 2155 "Actions/LogService.ClearLog"}}; 2156 } 2157 }; 2158 2159 class PostCodesClear : public Node 2160 { 2161 public: 2162 PostCodesClear(CrowApp &app) : 2163 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/" 2164 "LogService.ClearLog/") 2165 { 2166 entityPrivileges = { 2167 {boost::beast::http::verb::get, {{"Login"}}}, 2168 {boost::beast::http::verb::head, {{"Login"}}}, 2169 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2170 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2171 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2172 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2173 } 2174 2175 private: 2176 void doPost(crow::Response &res, const crow::Request &req, 2177 const std::vector<std::string> ¶ms) override 2178 { 2179 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 2180 2181 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2182 // Make call to post-code service to request clear all 2183 crow::connections::systemBus->async_method_call( 2184 [asyncResp](const boost::system::error_code ec) { 2185 if (ec) 2186 { 2187 // TODO Handle for specific error code 2188 BMCWEB_LOG_ERROR 2189 << "doClearPostCodes resp_handler got error " << ec; 2190 asyncResp->res.result( 2191 boost::beast::http::status::internal_server_error); 2192 messages::internalError(asyncResp->res); 2193 return; 2194 } 2195 }, 2196 "xyz.openbmc_project.State.Boot.PostCode", 2197 "/xyz/openbmc_project/State/Boot/PostCode", 2198 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2199 } 2200 }; 2201 2202 static void fillPostCodeEntry( 2203 std::shared_ptr<AsyncResp> aResp, 2204 const boost::container::flat_map<uint64_t, uint64_t> &postcode, 2205 const uint16_t bootIndex, const uint64_t codeIndex = 0, 2206 const uint64_t skip = 0, const uint64_t top = 0) 2207 { 2208 // Get the Message from the MessageRegistry 2209 const message_registries::Message *message = 2210 message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode"); 2211 std::string severity; 2212 if (message != nullptr) 2213 { 2214 severity = message->severity; 2215 } 2216 2217 uint64_t currentCodeIndex = 0; 2218 nlohmann::json &logEntryArray = aResp->res.jsonValue["Members"]; 2219 2220 uint64_t firstCodeTimeUs = 0; 2221 for (const std::pair<uint64_t, uint64_t> &code : postcode) 2222 { 2223 currentCodeIndex++; 2224 std::string postcodeEntryID = 2225 "B" + std::to_string(bootIndex) + "-" + 2226 std::to_string(currentCodeIndex); // 1 based index in EntryID string 2227 2228 uint64_t usecSinceEpoch = code.first; 2229 uint64_t usTimeOffset = 0; 2230 2231 if (1 == currentCodeIndex) 2232 { // already incremented 2233 firstCodeTimeUs = code.first; 2234 } 2235 else 2236 { 2237 usTimeOffset = code.first - firstCodeTimeUs; 2238 } 2239 2240 // skip if no specific codeIndex is specified and currentCodeIndex does 2241 // not fall between top and skip 2242 if ((codeIndex == 0) && 2243 (currentCodeIndex <= skip || currentCodeIndex > top)) 2244 { 2245 continue; 2246 } 2247 2248 // skip if a sepcific codeIndex is specified and does not match the 2249 // currentIndex 2250 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 2251 { 2252 // This is done for simplicity. 1st entry is needed to calculate 2253 // time offset. To improve efficiency, one can get to the entry 2254 // directly (possibly with flatmap's nth method) 2255 continue; 2256 } 2257 2258 // currentCodeIndex is within top and skip or equal to specified code 2259 // index 2260 2261 // Get the Created time from the timestamp 2262 std::string entryTimeStr; 2263 if (!getTimestampStr(usecSinceEpoch, entryTimeStr)) 2264 { 2265 continue; 2266 } 2267 2268 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 2269 std::ostringstream hexCode; 2270 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 2271 << code.second; 2272 std::ostringstream timeOffsetStr; 2273 // Set Fixed -Point Notation 2274 timeOffsetStr << std::fixed; 2275 // Set precision to 4 digits 2276 timeOffsetStr << std::setprecision(4); 2277 // Add double to stream 2278 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 2279 std::vector<std::string> messageArgs = { 2280 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 2281 2282 // Get MessageArgs template from message registry 2283 std::string msg; 2284 if (message != nullptr) 2285 { 2286 msg = message->message; 2287 2288 // fill in this post code value 2289 int i = 0; 2290 for (const std::string &messageArg : messageArgs) 2291 { 2292 std::string argStr = "%" + std::to_string(++i); 2293 size_t argPos = msg.find(argStr); 2294 if (argPos != std::string::npos) 2295 { 2296 msg.replace(argPos, argStr.length(), messageArg); 2297 } 2298 } 2299 } 2300 2301 // add to AsyncResp 2302 logEntryArray.push_back({}); 2303 nlohmann::json &bmcLogEntry = logEntryArray.back(); 2304 bmcLogEntry = { 2305 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2306 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 2307 {"@odata.id", "/redfish/v1/Systems/system/LogServices/" 2308 "PostCodes/Entries/" + 2309 postcodeEntryID}, 2310 {"Name", "POST Code Log Entry"}, 2311 {"Id", postcodeEntryID}, 2312 {"Message", std::move(msg)}, 2313 {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"}, 2314 {"MessageArgs", std::move(messageArgs)}, 2315 {"EntryType", "Event"}, 2316 {"Severity", std::move(severity)}, 2317 {"Created", std::move(entryTimeStr)}}; 2318 } 2319 } 2320 2321 static void getPostCodeForEntry(std::shared_ptr<AsyncResp> aResp, 2322 const uint16_t bootIndex, 2323 const uint64_t codeIndex) 2324 { 2325 crow::connections::systemBus->async_method_call( 2326 [aResp, bootIndex, codeIndex]( 2327 const boost::system::error_code ec, 2328 const boost::container::flat_map<uint64_t, uint64_t> &postcode) { 2329 if (ec) 2330 { 2331 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2332 messages::internalError(aResp->res); 2333 return; 2334 } 2335 2336 // skip the empty postcode boots 2337 if (postcode.empty()) 2338 { 2339 return; 2340 } 2341 2342 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 2343 2344 aResp->res.jsonValue["Members@odata.count"] = 2345 aResp->res.jsonValue["Members"].size(); 2346 }, 2347 "xyz.openbmc_project.State.Boot.PostCode", 2348 "/xyz/openbmc_project/State/Boot/PostCode", 2349 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2350 bootIndex); 2351 } 2352 2353 static void getPostCodeForBoot(std::shared_ptr<AsyncResp> aResp, 2354 const uint16_t bootIndex, 2355 const uint16_t bootCount, 2356 const uint64_t entryCount, const uint64_t skip, 2357 const uint64_t top) 2358 { 2359 crow::connections::systemBus->async_method_call( 2360 [aResp, bootIndex, bootCount, entryCount, skip, 2361 top](const boost::system::error_code ec, 2362 const boost::container::flat_map<uint64_t, uint64_t> &postcode) { 2363 if (ec) 2364 { 2365 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2366 messages::internalError(aResp->res); 2367 return; 2368 } 2369 2370 uint64_t endCount = entryCount; 2371 if (!postcode.empty()) 2372 { 2373 endCount = entryCount + postcode.size(); 2374 2375 if ((skip < endCount) && ((top + skip) > entryCount)) 2376 { 2377 uint64_t thisBootSkip = 2378 std::max(skip, entryCount) - entryCount; 2379 uint64_t thisBootTop = 2380 std::min(top + skip, endCount) - entryCount; 2381 2382 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 2383 thisBootSkip, thisBootTop); 2384 } 2385 aResp->res.jsonValue["Members@odata.count"] = endCount; 2386 } 2387 2388 // continue to previous bootIndex 2389 if (bootIndex < bootCount) 2390 { 2391 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 2392 bootCount, endCount, skip, top); 2393 } 2394 else 2395 { 2396 aResp->res.jsonValue["Members@odata.nextLink"] = 2397 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2398 "Entries?$skip=" + 2399 std::to_string(skip + top); 2400 } 2401 }, 2402 "xyz.openbmc_project.State.Boot.PostCode", 2403 "/xyz/openbmc_project/State/Boot/PostCode", 2404 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2405 bootIndex); 2406 } 2407 2408 static void getCurrentBootNumber(std::shared_ptr<AsyncResp> aResp, 2409 const uint64_t skip, const uint64_t top) 2410 { 2411 uint64_t entryCount = 0; 2412 crow::connections::systemBus->async_method_call( 2413 [aResp, entryCount, skip, 2414 top](const boost::system::error_code ec, 2415 const std::variant<uint16_t> &bootCount) { 2416 if (ec) 2417 { 2418 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 2419 messages::internalError(aResp->res); 2420 return; 2421 } 2422 auto pVal = std::get_if<uint16_t>(&bootCount); 2423 if (pVal) 2424 { 2425 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top); 2426 } 2427 else 2428 { 2429 BMCWEB_LOG_DEBUG << "Post code boot index failed."; 2430 } 2431 }, 2432 "xyz.openbmc_project.State.Boot.PostCode", 2433 "/xyz/openbmc_project/State/Boot/PostCode", 2434 "org.freedesktop.DBus.Properties", "Get", 2435 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount"); 2436 } 2437 2438 class PostCodesEntryCollection : public Node 2439 { 2440 public: 2441 template <typename CrowApp> 2442 PostCodesEntryCollection(CrowApp &app) : 2443 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 2444 { 2445 entityPrivileges = { 2446 {boost::beast::http::verb::get, {{"Login"}}}, 2447 {boost::beast::http::verb::head, {{"Login"}}}, 2448 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2449 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2450 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2451 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2452 } 2453 2454 private: 2455 void doGet(crow::Response &res, const crow::Request &req, 2456 const std::vector<std::string> ¶ms) override 2457 { 2458 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2459 2460 asyncResp->res.jsonValue["@odata.type"] = 2461 "#LogEntryCollection.LogEntryCollection"; 2462 asyncResp->res.jsonValue["@odata.context"] = 2463 "/redfish/v1/" 2464 "$metadata#LogEntryCollection.LogEntryCollection"; 2465 asyncResp->res.jsonValue["@odata.id"] = 2466 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 2467 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 2468 asyncResp->res.jsonValue["Description"] = 2469 "Collection of POST Code Log Entries"; 2470 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 2471 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2472 2473 uint64_t skip = 0; 2474 uint64_t top = maxEntriesPerPage; // Show max entries by default 2475 if (!getSkipParam(asyncResp->res, req, skip)) 2476 { 2477 return; 2478 } 2479 if (!getTopParam(asyncResp->res, req, top)) 2480 { 2481 return; 2482 } 2483 getCurrentBootNumber(asyncResp, skip, top); 2484 } 2485 }; 2486 2487 class PostCodesEntry : public Node 2488 { 2489 public: 2490 PostCodesEntry(CrowApp &app) : 2491 Node(app, 2492 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/", 2493 std::string()) 2494 { 2495 entityPrivileges = { 2496 {boost::beast::http::verb::get, {{"Login"}}}, 2497 {boost::beast::http::verb::head, {{"Login"}}}, 2498 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2499 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2500 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2501 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2502 } 2503 2504 private: 2505 void doGet(crow::Response &res, const crow::Request &req, 2506 const std::vector<std::string> ¶ms) override 2507 { 2508 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2509 if (params.size() != 1) 2510 { 2511 messages::internalError(asyncResp->res); 2512 return; 2513 } 2514 2515 const std::string &targetID = params[0]; 2516 2517 size_t bootPos = targetID.find('B'); 2518 if (bootPos == std::string::npos) 2519 { 2520 // Requested ID was not found 2521 messages::resourceMissingAtURI(asyncResp->res, targetID); 2522 return; 2523 } 2524 std::string_view bootIndexStr(targetID); 2525 bootIndexStr.remove_prefix(bootPos + 1); 2526 uint16_t bootIndex = 0; 2527 uint64_t codeIndex = 0; 2528 size_t dashPos = bootIndexStr.find('-'); 2529 2530 if (dashPos == std::string::npos) 2531 { 2532 return; 2533 } 2534 std::string_view codeIndexStr(bootIndexStr); 2535 bootIndexStr.remove_suffix(dashPos); 2536 codeIndexStr.remove_prefix(dashPos + 1); 2537 2538 bootIndex = static_cast<uint16_t>( 2539 strtoul(std::string(bootIndexStr).c_str(), NULL, 0)); 2540 codeIndex = strtoul(std::string(codeIndexStr).c_str(), NULL, 0); 2541 if (bootIndex == 0 || codeIndex == 0) 2542 { 2543 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 2544 << params[0]; 2545 } 2546 2547 asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry"; 2548 asyncResp->res.jsonValue["@odata.context"] = 2549 "/redfish/v1/$metadata#LogEntry.LogEntry"; 2550 asyncResp->res.jsonValue["@odata.id"] = 2551 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2552 "Entries"; 2553 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 2554 asyncResp->res.jsonValue["Description"] = 2555 "Collection of POST Code Log Entries"; 2556 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 2557 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2558 2559 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 2560 } 2561 }; 2562 2563 } // namespace redfish 2564