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 23 #include <systemd/sd-journal.h> 24 25 #include <boost/algorithm/string/split.hpp> 26 #include <boost/beast/core/span.hpp> 27 #include <boost/container/flat_map.hpp> 28 #include <error_messages.hpp> 29 #include <filesystem> 30 #include <string_view> 31 #include <variant> 32 33 namespace redfish 34 { 35 36 constexpr char const *CrashdumpObject = "com.intel.crashdump"; 37 constexpr char const *CrashdumpPath = "/com/intel/crashdump"; 38 constexpr char const *CrashdumpOnDemandPath = "/com/intel/crashdump/OnDemand"; 39 constexpr char const *CrashdumpInterface = "com.intel.crashdump"; 40 constexpr char const *CrashdumpOnDemandInterface = 41 "com.intel.crashdump.OnDemand"; 42 constexpr char const *CrashdumpRawPECIInterface = 43 "com.intel.crashdump.SendRawPeci"; 44 45 namespace message_registries 46 { 47 static const Message *getMessageFromRegistry( 48 const std::string &messageKey, 49 const boost::beast::span<const MessageEntry> registry) 50 { 51 boost::beast::span<const MessageEntry>::const_iterator messageIt = 52 std::find_if(registry.cbegin(), registry.cend(), 53 [&messageKey](const MessageEntry &messageEntry) { 54 return !std::strcmp(messageEntry.first, 55 messageKey.c_str()); 56 }); 57 if (messageIt != registry.cend()) 58 { 59 return &messageIt->second; 60 } 61 62 return nullptr; 63 } 64 65 static const Message *getMessage(const std::string_view &messageID) 66 { 67 // Redfish MessageIds are in the form 68 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 69 // the right Message 70 std::vector<std::string> fields; 71 fields.reserve(4); 72 boost::split(fields, messageID, boost::is_any_of(".")); 73 std::string ®istryName = fields[0]; 74 std::string &messageKey = fields[3]; 75 76 // Find the right registry and check it for the MessageKey 77 if (std::string(base::header.registryPrefix) == registryName) 78 { 79 return getMessageFromRegistry( 80 messageKey, boost::beast::span<const MessageEntry>(base::registry)); 81 } 82 if (std::string(openbmc::header.registryPrefix) == registryName) 83 { 84 return getMessageFromRegistry( 85 messageKey, 86 boost::beast::span<const MessageEntry>(openbmc::registry)); 87 } 88 return nullptr; 89 } 90 } // namespace message_registries 91 92 namespace fs = std::filesystem; 93 94 using GetManagedPropertyType = boost::container::flat_map< 95 std::string, 96 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 97 int32_t, uint32_t, int64_t, uint64_t, double>>; 98 99 using GetManagedObjectsType = boost::container::flat_map< 100 sdbusplus::message::object_path, 101 boost::container::flat_map<std::string, GetManagedPropertyType>>; 102 103 inline std::string translateSeverityDbusToRedfish(const std::string &s) 104 { 105 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") 106 { 107 return "Critical"; 108 } 109 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") 110 { 111 return "Critical"; 112 } 113 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") 114 { 115 return "OK"; 116 } 117 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") 118 { 119 return "Critical"; 120 } 121 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") 122 { 123 return "Critical"; 124 } 125 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") 126 { 127 return "OK"; 128 } 129 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") 130 { 131 return "OK"; 132 } 133 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 134 { 135 return "Warning"; 136 } 137 return ""; 138 } 139 140 static int getJournalMetadata(sd_journal *journal, 141 const std::string_view &field, 142 std::string_view &contents) 143 { 144 const char *data = nullptr; 145 size_t length = 0; 146 int ret = 0; 147 // Get the metadata from the requested field of the journal entry 148 ret = sd_journal_get_data(journal, field.data(), 149 reinterpret_cast<const void **>(&data), &length); 150 if (ret < 0) 151 { 152 return ret; 153 } 154 contents = std::string_view(data, length); 155 // Only use the content after the "=" character. 156 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 157 return ret; 158 } 159 160 static int getJournalMetadata(sd_journal *journal, 161 const std::string_view &field, const int &base, 162 long int &contents) 163 { 164 int ret = 0; 165 std::string_view metadata; 166 // Get the metadata from the requested field of the journal entry 167 ret = getJournalMetadata(journal, field, metadata); 168 if (ret < 0) 169 { 170 return ret; 171 } 172 contents = strtol(metadata.data(), nullptr, base); 173 return ret; 174 } 175 176 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 177 { 178 int ret = 0; 179 uint64_t timestamp = 0; 180 ret = sd_journal_get_realtime_usec(journal, ×tamp); 181 if (ret < 0) 182 { 183 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 184 << strerror(-ret); 185 return false; 186 } 187 time_t t = 188 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 189 struct tm *loctime = localtime(&t); 190 char entryTime[64] = {}; 191 if (NULL != loctime) 192 { 193 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 194 } 195 // Insert the ':' into the timezone 196 std::string_view t1(entryTime); 197 std::string_view t2(entryTime); 198 if (t1.size() > 2 && t2.size() > 2) 199 { 200 t1.remove_suffix(2); 201 t2.remove_prefix(t2.size() - 2); 202 } 203 entryTimestamp = std::string(t1) + ":" + std::string(t2); 204 return true; 205 } 206 207 static bool getSkipParam(crow::Response &res, const crow::Request &req, 208 uint64_t &skip) 209 { 210 char *skipParam = req.urlParams.get("$skip"); 211 if (skipParam != nullptr) 212 { 213 char *ptr = nullptr; 214 skip = std::strtoul(skipParam, &ptr, 10); 215 if (*skipParam == '\0' || *ptr != '\0') 216 { 217 218 messages::queryParameterValueTypeError(res, std::string(skipParam), 219 "$skip"); 220 return false; 221 } 222 } 223 return true; 224 } 225 226 static constexpr const uint64_t maxEntriesPerPage = 1000; 227 static bool getTopParam(crow::Response &res, const crow::Request &req, 228 uint64_t &top) 229 { 230 char *topParam = req.urlParams.get("$top"); 231 if (topParam != nullptr) 232 { 233 char *ptr = nullptr; 234 top = std::strtoul(topParam, &ptr, 10); 235 if (*topParam == '\0' || *ptr != '\0') 236 { 237 messages::queryParameterValueTypeError(res, std::string(topParam), 238 "$top"); 239 return false; 240 } 241 if (top < 1U || top > maxEntriesPerPage) 242 { 243 244 messages::queryParameterOutOfRange( 245 res, std::to_string(top), "$top", 246 "1-" + std::to_string(maxEntriesPerPage)); 247 return false; 248 } 249 } 250 return true; 251 } 252 253 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID, 254 const bool firstEntry = true) 255 { 256 int ret = 0; 257 static uint64_t prevTs = 0; 258 static int index = 0; 259 if (firstEntry) 260 { 261 prevTs = 0; 262 } 263 264 // Get the entry timestamp 265 uint64_t curTs = 0; 266 ret = sd_journal_get_realtime_usec(journal, &curTs); 267 if (ret < 0) 268 { 269 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 270 << strerror(-ret); 271 return false; 272 } 273 // If the timestamp isn't unique, increment the index 274 if (curTs == prevTs) 275 { 276 index++; 277 } 278 else 279 { 280 // Otherwise, reset it 281 index = 0; 282 } 283 // Save the timestamp 284 prevTs = curTs; 285 286 entryID = std::to_string(curTs); 287 if (index > 0) 288 { 289 entryID += "_" + std::to_string(index); 290 } 291 return true; 292 } 293 294 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID, 295 const bool firstEntry = true) 296 { 297 static time_t prevTs = 0; 298 static int index = 0; 299 if (firstEntry) 300 { 301 prevTs = 0; 302 } 303 304 // Get the entry timestamp 305 std::time_t curTs = 0; 306 std::tm timeStruct = {}; 307 std::istringstream entryStream(logEntry); 308 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 309 { 310 curTs = std::mktime(&timeStruct); 311 } 312 // If the timestamp isn't unique, increment the index 313 if (curTs == prevTs) 314 { 315 index++; 316 } 317 else 318 { 319 // Otherwise, reset it 320 index = 0; 321 } 322 // Save the timestamp 323 prevTs = curTs; 324 325 entryID = std::to_string(curTs); 326 if (index > 0) 327 { 328 entryID += "_" + std::to_string(index); 329 } 330 return true; 331 } 332 333 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 334 uint64_t ×tamp, uint64_t &index) 335 { 336 if (entryID.empty()) 337 { 338 return false; 339 } 340 // Convert the unique ID back to a timestamp to find the entry 341 std::string_view tsStr(entryID); 342 343 auto underscorePos = tsStr.find("_"); 344 if (underscorePos != tsStr.npos) 345 { 346 // Timestamp has an index 347 tsStr.remove_suffix(tsStr.size() - underscorePos); 348 std::string_view indexStr(entryID); 349 indexStr.remove_prefix(underscorePos + 1); 350 std::size_t pos; 351 try 352 { 353 index = std::stoul(std::string(indexStr), &pos); 354 } 355 catch (std::invalid_argument &) 356 { 357 messages::resourceMissingAtURI(res, entryID); 358 return false; 359 } 360 catch (std::out_of_range &) 361 { 362 messages::resourceMissingAtURI(res, entryID); 363 return false; 364 } 365 if (pos != indexStr.size()) 366 { 367 messages::resourceMissingAtURI(res, entryID); 368 return false; 369 } 370 } 371 // Timestamp has no index 372 std::size_t pos; 373 try 374 { 375 timestamp = std::stoull(std::string(tsStr), &pos); 376 } 377 catch (std::invalid_argument &) 378 { 379 messages::resourceMissingAtURI(res, entryID); 380 return false; 381 } 382 catch (std::out_of_range &) 383 { 384 messages::resourceMissingAtURI(res, entryID); 385 return false; 386 } 387 if (pos != tsStr.size()) 388 { 389 messages::resourceMissingAtURI(res, entryID); 390 return false; 391 } 392 return true; 393 } 394 395 static bool 396 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles) 397 { 398 static const std::filesystem::path redfishLogDir = "/var/log"; 399 static const std::string redfishLogFilename = "redfish"; 400 401 // Loop through the directory looking for redfish log files 402 for (const std::filesystem::directory_entry &dirEnt : 403 std::filesystem::directory_iterator(redfishLogDir)) 404 { 405 // If we find a redfish log file, save the path 406 std::string filename = dirEnt.path().filename(); 407 if (boost::starts_with(filename, redfishLogFilename)) 408 { 409 redfishLogFiles.emplace_back(redfishLogDir / filename); 410 } 411 } 412 // As the log files rotate, they are appended with a ".#" that is higher for 413 // the older logs. Since we don't expect more than 10 log files, we 414 // can just sort the list to get them in order from newest to oldest 415 std::sort(redfishLogFiles.begin(), redfishLogFiles.end()); 416 417 return !redfishLogFiles.empty(); 418 } 419 420 class SystemLogServiceCollection : public Node 421 { 422 public: 423 template <typename CrowApp> 424 SystemLogServiceCollection(CrowApp &app) : 425 Node(app, "/redfish/v1/Systems/system/LogServices/") 426 { 427 entityPrivileges = { 428 {boost::beast::http::verb::get, {{"Login"}}}, 429 {boost::beast::http::verb::head, {{"Login"}}}, 430 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 431 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 432 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 433 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 434 } 435 436 private: 437 /** 438 * Functions triggers appropriate requests on DBus 439 */ 440 void doGet(crow::Response &res, const crow::Request &req, 441 const std::vector<std::string> ¶ms) override 442 { 443 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 444 // Collections don't include the static data added by SubRoute because 445 // it has a duplicate entry for members 446 asyncResp->res.jsonValue["@odata.type"] = 447 "#LogServiceCollection.LogServiceCollection"; 448 asyncResp->res.jsonValue["@odata.context"] = 449 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 450 asyncResp->res.jsonValue["@odata.id"] = 451 "/redfish/v1/Systems/system/LogServices"; 452 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 453 asyncResp->res.jsonValue["Description"] = 454 "Collection of LogServices for this Computer System"; 455 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 456 logServiceArray = nlohmann::json::array(); 457 logServiceArray.push_back( 458 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 459 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 460 logServiceArray.push_back( 461 {{"@odata.id", 462 "/redfish/v1/Systems/system/LogServices/Crashdump"}}); 463 #endif 464 asyncResp->res.jsonValue["Members@odata.count"] = 465 logServiceArray.size(); 466 } 467 }; 468 469 class EventLogService : public Node 470 { 471 public: 472 template <typename CrowApp> 473 EventLogService(CrowApp &app) : 474 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 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 void doGet(crow::Response &res, const crow::Request &req, 487 const std::vector<std::string> ¶ms) override 488 { 489 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 490 491 asyncResp->res.jsonValue["@odata.id"] = 492 "/redfish/v1/Systems/system/LogServices/EventLog"; 493 asyncResp->res.jsonValue["@odata.type"] = 494 "#LogService.v1_1_0.LogService"; 495 asyncResp->res.jsonValue["@odata.context"] = 496 "/redfish/v1/$metadata#LogService.LogService"; 497 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 498 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 499 asyncResp->res.jsonValue["Id"] = "Event Log"; 500 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 501 asyncResp->res.jsonValue["Entries"] = { 502 {"@odata.id", 503 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 504 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 505 506 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 507 "Actions/LogService.ClearLog"}}; 508 } 509 }; 510 511 class EventLogClear : public Node 512 { 513 public: 514 EventLogClear(CrowApp &app) : 515 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 516 "LogService.ClearLog/") 517 { 518 entityPrivileges = { 519 {boost::beast::http::verb::get, {{"Login"}}}, 520 {boost::beast::http::verb::head, {{"Login"}}}, 521 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 522 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 523 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 524 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 525 } 526 527 private: 528 void doPost(crow::Response &res, const crow::Request &req, 529 const std::vector<std::string> ¶ms) override 530 { 531 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 532 533 // Clear the EventLog by deleting the log files 534 std::vector<std::filesystem::path> redfishLogFiles; 535 if (getRedfishLogFiles(redfishLogFiles)) 536 { 537 for (const std::filesystem::path &file : redfishLogFiles) 538 { 539 std::error_code ec; 540 std::filesystem::remove(file, ec); 541 } 542 } 543 544 // Reload rsyslog so it knows to start new log files 545 crow::connections::systemBus->async_method_call( 546 [asyncResp](const boost::system::error_code ec) { 547 if (ec) 548 { 549 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec; 550 messages::internalError(asyncResp->res); 551 return; 552 } 553 554 messages::success(asyncResp->res); 555 }, 556 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 557 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 558 "replace"); 559 } 560 }; 561 562 static int fillEventLogEntryJson(const std::string &logEntryID, 563 const std::string logEntry, 564 nlohmann::json &logEntryJson) 565 { 566 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 567 // First get the Timestamp 568 size_t space = logEntry.find_first_of(" "); 569 if (space == std::string::npos) 570 { 571 return 1; 572 } 573 std::string timestamp = logEntry.substr(0, space); 574 // Then get the log contents 575 size_t entryStart = logEntry.find_first_not_of(" ", space); 576 if (entryStart == std::string::npos) 577 { 578 return 1; 579 } 580 std::string_view entry(logEntry); 581 entry.remove_prefix(entryStart); 582 // Use split to separate the entry into its fields 583 std::vector<std::string> logEntryFields; 584 boost::split(logEntryFields, entry, boost::is_any_of(","), 585 boost::token_compress_on); 586 // We need at least a MessageId to be valid 587 if (logEntryFields.size() < 1) 588 { 589 return 1; 590 } 591 std::string &messageID = logEntryFields[0]; 592 593 // Get the Message from the MessageRegistry 594 const message_registries::Message *message = 595 message_registries::getMessage(messageID); 596 597 std::string msg; 598 std::string severity; 599 if (message != nullptr) 600 { 601 msg = message->message; 602 severity = message->severity; 603 } 604 605 // Get the MessageArgs from the log if there are any 606 boost::beast::span<std::string> messageArgs; 607 if (logEntryFields.size() > 1) 608 { 609 std::string &messageArgsStart = logEntryFields[1]; 610 // If the first string is empty, assume there are no MessageArgs 611 std::size_t messageArgsSize = 0; 612 if (!messageArgsStart.empty()) 613 { 614 messageArgsSize = logEntryFields.size() - 1; 615 } 616 617 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize); 618 619 // Fill the MessageArgs into the Message 620 int i = 0; 621 for (const std::string &messageArg : messageArgs) 622 { 623 std::string argStr = "%" + std::to_string(++i); 624 size_t argPos = msg.find(argStr); 625 if (argPos != std::string::npos) 626 { 627 msg.replace(argPos, argStr.length(), messageArg); 628 } 629 } 630 } 631 632 // Get the Created time from the timestamp. The log timestamp is in RFC3339 633 // format which matches the Redfish format except for the fractional seconds 634 // between the '.' and the '+', so just remove them. 635 std::size_t dot = timestamp.find_first_of("."); 636 std::size_t plus = timestamp.find_first_of("+"); 637 if (dot != std::string::npos && plus != std::string::npos) 638 { 639 timestamp.erase(dot, plus - dot); 640 } 641 642 // Fill in the log entry with the gathered data 643 logEntryJson = { 644 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 645 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 646 {"@odata.id", 647 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 648 logEntryID}, 649 {"Name", "System Event Log Entry"}, 650 {"Id", logEntryID}, 651 {"Message", std::move(msg)}, 652 {"MessageId", std::move(messageID)}, 653 {"MessageArgs", std::move(messageArgs)}, 654 {"EntryType", "Event"}, 655 {"Severity", std::move(severity)}, 656 {"Created", std::move(timestamp)}}; 657 return 0; 658 } 659 660 class JournalEventLogEntryCollection : public Node 661 { 662 public: 663 template <typename CrowApp> 664 JournalEventLogEntryCollection(CrowApp &app) : 665 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 666 { 667 entityPrivileges = { 668 {boost::beast::http::verb::get, {{"Login"}}}, 669 {boost::beast::http::verb::head, {{"Login"}}}, 670 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 671 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 672 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 673 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 674 } 675 676 private: 677 void doGet(crow::Response &res, const crow::Request &req, 678 const std::vector<std::string> ¶ms) override 679 { 680 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 681 uint64_t skip = 0; 682 uint64_t top = maxEntriesPerPage; // Show max entries by default 683 if (!getSkipParam(asyncResp->res, req, skip)) 684 { 685 return; 686 } 687 if (!getTopParam(asyncResp->res, req, top)) 688 { 689 return; 690 } 691 // Collections don't include the static data added by SubRoute because 692 // it has a duplicate entry for members 693 asyncResp->res.jsonValue["@odata.type"] = 694 "#LogEntryCollection.LogEntryCollection"; 695 asyncResp->res.jsonValue["@odata.context"] = 696 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 697 asyncResp->res.jsonValue["@odata.id"] = 698 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 699 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 700 asyncResp->res.jsonValue["Description"] = 701 "Collection of System Event Log Entries"; 702 703 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 704 logEntryArray = nlohmann::json::array(); 705 // Go through the log files and create a unique ID for each entry 706 std::vector<std::filesystem::path> redfishLogFiles; 707 getRedfishLogFiles(redfishLogFiles); 708 uint64_t entryCount = 0; 709 std::string logEntry; 710 711 // Oldest logs are in the last file, so start there and loop backwards 712 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 713 it++) 714 { 715 std::ifstream logStream(*it); 716 if (!logStream.is_open()) 717 { 718 continue; 719 } 720 721 // Reset the unique ID on the first entry 722 bool firstEntry = true; 723 while (std::getline(logStream, logEntry)) 724 { 725 entryCount++; 726 // Handle paging using skip (number of entries to skip from the 727 // start) and top (number of entries to display) 728 if (entryCount <= skip || entryCount > skip + top) 729 { 730 continue; 731 } 732 733 std::string idStr; 734 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 735 { 736 continue; 737 } 738 739 if (firstEntry) 740 { 741 firstEntry = false; 742 } 743 744 logEntryArray.push_back({}); 745 nlohmann::json &bmcLogEntry = logEntryArray.back(); 746 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 747 { 748 messages::internalError(asyncResp->res); 749 return; 750 } 751 } 752 } 753 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 754 if (skip + top < entryCount) 755 { 756 asyncResp->res.jsonValue["Members@odata.nextLink"] = 757 "/redfish/v1/Systems/system/LogServices/EventLog/" 758 "Entries?$skip=" + 759 std::to_string(skip + top); 760 } 761 } 762 }; 763 764 class JournalEventLogEntry : public Node 765 { 766 public: 767 JournalEventLogEntry(CrowApp &app) : 768 Node(app, 769 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 770 std::string()) 771 { 772 entityPrivileges = { 773 {boost::beast::http::verb::get, {{"Login"}}}, 774 {boost::beast::http::verb::head, {{"Login"}}}, 775 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 776 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 777 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 778 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 779 } 780 781 private: 782 void doGet(crow::Response &res, const crow::Request &req, 783 const std::vector<std::string> ¶ms) override 784 { 785 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 786 if (params.size() != 1) 787 { 788 messages::internalError(asyncResp->res); 789 return; 790 } 791 const std::string &targetID = params[0]; 792 793 // Go through the log files and check the unique ID for each entry to 794 // find the target entry 795 std::vector<std::filesystem::path> redfishLogFiles; 796 getRedfishLogFiles(redfishLogFiles); 797 std::string logEntry; 798 799 // Oldest logs are in the last file, so start there and loop backwards 800 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 801 it++) 802 { 803 std::ifstream logStream(*it); 804 if (!logStream.is_open()) 805 { 806 continue; 807 } 808 809 // Reset the unique ID on the first entry 810 bool firstEntry = true; 811 while (std::getline(logStream, logEntry)) 812 { 813 std::string idStr; 814 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 815 { 816 continue; 817 } 818 819 if (firstEntry) 820 { 821 firstEntry = false; 822 } 823 824 if (idStr == targetID) 825 { 826 if (fillEventLogEntryJson(idStr, logEntry, 827 asyncResp->res.jsonValue) != 0) 828 { 829 messages::internalError(asyncResp->res); 830 return; 831 } 832 return; 833 } 834 } 835 } 836 // Requested ID was not found 837 messages::resourceMissingAtURI(asyncResp->res, targetID); 838 } 839 }; 840 841 class DBusEventLogEntryCollection : public Node 842 { 843 public: 844 template <typename CrowApp> 845 DBusEventLogEntryCollection(CrowApp &app) : 846 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 847 { 848 entityPrivileges = { 849 {boost::beast::http::verb::get, {{"Login"}}}, 850 {boost::beast::http::verb::head, {{"Login"}}}, 851 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 852 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 853 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 854 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 855 } 856 857 private: 858 void doGet(crow::Response &res, const crow::Request &req, 859 const std::vector<std::string> ¶ms) override 860 { 861 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 862 863 // Collections don't include the static data added by SubRoute because 864 // it has a duplicate entry for members 865 asyncResp->res.jsonValue["@odata.type"] = 866 "#LogEntryCollection.LogEntryCollection"; 867 asyncResp->res.jsonValue["@odata.context"] = 868 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 869 asyncResp->res.jsonValue["@odata.id"] = 870 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 871 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 872 asyncResp->res.jsonValue["Description"] = 873 "Collection of System Event Log Entries"; 874 875 // DBus implementation of EventLog/Entries 876 // Make call to Logging Service to find all log entry objects 877 crow::connections::systemBus->async_method_call( 878 [asyncResp](const boost::system::error_code ec, 879 GetManagedObjectsType &resp) { 880 if (ec) 881 { 882 // TODO Handle for specific error code 883 BMCWEB_LOG_ERROR 884 << "getLogEntriesIfaceData resp_handler got error " 885 << ec; 886 messages::internalError(asyncResp->res); 887 return; 888 } 889 nlohmann::json &entriesArray = 890 asyncResp->res.jsonValue["Members"]; 891 entriesArray = nlohmann::json::array(); 892 for (auto &objectPath : resp) 893 { 894 for (auto &interfaceMap : objectPath.second) 895 { 896 if (interfaceMap.first != 897 "xyz.openbmc_project.Logging.Entry") 898 { 899 BMCWEB_LOG_DEBUG << "Bailing early on " 900 << interfaceMap.first; 901 continue; 902 } 903 entriesArray.push_back({}); 904 nlohmann::json &thisEntry = entriesArray.back(); 905 uint32_t *id; 906 std::time_t timestamp; 907 std::string *severity, *message; 908 for (auto &propertyMap : interfaceMap.second) 909 { 910 if (propertyMap.first == "Id") 911 { 912 id = sdbusplus::message::variant_ns::get_if< 913 uint32_t>(&propertyMap.second); 914 if (id == nullptr) 915 { 916 messages::propertyMissing(asyncResp->res, 917 "Id"); 918 } 919 } 920 else if (propertyMap.first == "Timestamp") 921 { 922 const uint64_t *millisTimeStamp = 923 std::get_if<uint64_t>(&propertyMap.second); 924 if (millisTimeStamp == nullptr) 925 { 926 messages::propertyMissing(asyncResp->res, 927 "Timestamp"); 928 continue; 929 } 930 // Retrieve Created property with format: 931 // yyyy-mm-ddThh:mm:ss 932 std::chrono::milliseconds chronoTimeStamp( 933 *millisTimeStamp); 934 timestamp = std::chrono::duration_cast< 935 std::chrono::duration<int>>( 936 chronoTimeStamp) 937 .count(); 938 } 939 else if (propertyMap.first == "Severity") 940 { 941 severity = std::get_if<std::string>( 942 &propertyMap.second); 943 if (severity == nullptr) 944 { 945 messages::propertyMissing(asyncResp->res, 946 "Severity"); 947 } 948 } 949 else if (propertyMap.first == "Message") 950 { 951 message = std::get_if<std::string>( 952 &propertyMap.second); 953 if (message == nullptr) 954 { 955 messages::propertyMissing(asyncResp->res, 956 "Message"); 957 } 958 } 959 } 960 thisEntry = { 961 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 962 {"@odata.context", "/redfish/v1/" 963 "$metadata#LogEntry.LogEntry"}, 964 {"@odata.id", 965 "/redfish/v1/Systems/system/LogServices/EventLog/" 966 "Entries/" + 967 std::to_string(*id)}, 968 {"Name", "System Event Log Entry"}, 969 {"Id", std::to_string(*id)}, 970 {"Message", *message}, 971 {"EntryType", "Event"}, 972 {"Severity", 973 translateSeverityDbusToRedfish(*severity)}, 974 {"Created", crow::utility::getDateTime(timestamp)}}; 975 } 976 } 977 std::sort(entriesArray.begin(), entriesArray.end(), 978 [](const nlohmann::json &left, 979 const nlohmann::json &right) { 980 return (left["Id"] <= right["Id"]); 981 }); 982 asyncResp->res.jsonValue["Members@odata.count"] = 983 entriesArray.size(); 984 }, 985 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 986 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 987 } 988 }; 989 990 class DBusEventLogEntry : public Node 991 { 992 public: 993 DBusEventLogEntry(CrowApp &app) : 994 Node(app, 995 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 996 std::string()) 997 { 998 entityPrivileges = { 999 {boost::beast::http::verb::get, {{"Login"}}}, 1000 {boost::beast::http::verb::head, {{"Login"}}}, 1001 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1002 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1003 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1004 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1005 } 1006 1007 private: 1008 void doGet(crow::Response &res, const crow::Request &req, 1009 const std::vector<std::string> ¶ms) override 1010 { 1011 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1012 if (params.size() != 1) 1013 { 1014 messages::internalError(asyncResp->res); 1015 return; 1016 } 1017 const std::string &entryID = params[0]; 1018 1019 // DBus implementation of EventLog/Entries 1020 // Make call to Logging Service to find all log entry objects 1021 crow::connections::systemBus->async_method_call( 1022 [asyncResp, entryID](const boost::system::error_code ec, 1023 GetManagedPropertyType &resp) { 1024 if (ec) 1025 { 1026 BMCWEB_LOG_ERROR 1027 << "EventLogEntry (DBus) resp_handler got error " << ec; 1028 messages::internalError(asyncResp->res); 1029 return; 1030 } 1031 uint32_t *id; 1032 std::time_t timestamp; 1033 std::string *severity, *message; 1034 for (auto &propertyMap : resp) 1035 { 1036 if (propertyMap.first == "Id") 1037 { 1038 id = std::get_if<uint32_t>(&propertyMap.second); 1039 if (id == nullptr) 1040 { 1041 messages::propertyMissing(asyncResp->res, "Id"); 1042 } 1043 } 1044 else if (propertyMap.first == "Timestamp") 1045 { 1046 const uint64_t *millisTimeStamp = 1047 std::get_if<uint64_t>(&propertyMap.second); 1048 if (millisTimeStamp == nullptr) 1049 { 1050 messages::propertyMissing(asyncResp->res, 1051 "Timestamp"); 1052 continue; 1053 } 1054 // Retrieve Created property with format: 1055 // yyyy-mm-ddThh:mm:ss 1056 std::chrono::milliseconds chronoTimeStamp( 1057 *millisTimeStamp); 1058 timestamp = 1059 std::chrono::duration_cast< 1060 std::chrono::duration<int>>(chronoTimeStamp) 1061 .count(); 1062 } 1063 else if (propertyMap.first == "Severity") 1064 { 1065 severity = 1066 std::get_if<std::string>(&propertyMap.second); 1067 if (severity == nullptr) 1068 { 1069 messages::propertyMissing(asyncResp->res, 1070 "Severity"); 1071 } 1072 } 1073 else if (propertyMap.first == "Message") 1074 { 1075 message = std::get_if<std::string>(&propertyMap.second); 1076 if (message == nullptr) 1077 { 1078 messages::propertyMissing(asyncResp->res, 1079 "Message"); 1080 } 1081 } 1082 } 1083 if (id == nullptr || message == nullptr || severity == nullptr) 1084 { 1085 return; 1086 } 1087 asyncResp->res.jsonValue = { 1088 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1089 {"@odata.context", "/redfish/v1/" 1090 "$metadata#LogEntry.LogEntry"}, 1091 {"@odata.id", 1092 "/redfish/v1/Systems/system/LogServices/EventLog/" 1093 "Entries/" + 1094 std::to_string(*id)}, 1095 {"Name", "System Event Log Entry"}, 1096 {"Id", std::to_string(*id)}, 1097 {"Message", *message}, 1098 {"EntryType", "Event"}, 1099 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1100 {"Created", crow::utility::getDateTime(timestamp)}}; 1101 }, 1102 "xyz.openbmc_project.Logging", 1103 "/xyz/openbmc_project/logging/entry/" + entryID, 1104 "org.freedesktop.DBus.Properties", "GetAll", 1105 "xyz.openbmc_project.Logging.Entry"); 1106 } 1107 1108 void doDelete(crow::Response &res, const crow::Request &req, 1109 const std::vector<std::string> ¶ms) override 1110 { 1111 1112 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1113 1114 auto asyncResp = std::make_shared<AsyncResp>(res); 1115 1116 if (params.size() != 1) 1117 { 1118 messages::internalError(asyncResp->res); 1119 return; 1120 } 1121 std::string entryID = params[0]; 1122 1123 dbus::utility::escapePathForDbus(entryID); 1124 1125 // Process response from Logging service. 1126 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1127 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1128 if (ec) 1129 { 1130 // TODO Handle for specific error code 1131 BMCWEB_LOG_ERROR 1132 << "EventLogEntry (DBus) doDelete respHandler got error " 1133 << ec; 1134 asyncResp->res.result( 1135 boost::beast::http::status::internal_server_error); 1136 return; 1137 } 1138 1139 asyncResp->res.result(boost::beast::http::status::ok); 1140 }; 1141 1142 // Make call to Logging service to request Delete Log 1143 crow::connections::systemBus->async_method_call( 1144 respHandler, "xyz.openbmc_project.Logging", 1145 "/xyz/openbmc_project/logging/entry/" + entryID, 1146 "xyz.openbmc_project.Object.Delete", "Delete"); 1147 } 1148 }; 1149 1150 class BMCLogServiceCollection : public Node 1151 { 1152 public: 1153 template <typename CrowApp> 1154 BMCLogServiceCollection(CrowApp &app) : 1155 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1156 { 1157 entityPrivileges = { 1158 {boost::beast::http::verb::get, {{"Login"}}}, 1159 {boost::beast::http::verb::head, {{"Login"}}}, 1160 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1161 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1162 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1163 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1164 } 1165 1166 private: 1167 /** 1168 * Functions triggers appropriate requests on DBus 1169 */ 1170 void doGet(crow::Response &res, const crow::Request &req, 1171 const std::vector<std::string> ¶ms) override 1172 { 1173 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1174 // Collections don't include the static data added by SubRoute because 1175 // it has a duplicate entry for members 1176 asyncResp->res.jsonValue["@odata.type"] = 1177 "#LogServiceCollection.LogServiceCollection"; 1178 asyncResp->res.jsonValue["@odata.context"] = 1179 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 1180 asyncResp->res.jsonValue["@odata.id"] = 1181 "/redfish/v1/Managers/bmc/LogServices"; 1182 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1183 asyncResp->res.jsonValue["Description"] = 1184 "Collection of LogServices for this Manager"; 1185 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 1186 logServiceArray = nlohmann::json::array(); 1187 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1188 logServiceArray.push_back( 1189 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1190 #endif 1191 asyncResp->res.jsonValue["Members@odata.count"] = 1192 logServiceArray.size(); 1193 } 1194 }; 1195 1196 class BMCJournalLogService : public Node 1197 { 1198 public: 1199 template <typename CrowApp> 1200 BMCJournalLogService(CrowApp &app) : 1201 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1202 { 1203 entityPrivileges = { 1204 {boost::beast::http::verb::get, {{"Login"}}}, 1205 {boost::beast::http::verb::head, {{"Login"}}}, 1206 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1207 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1208 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1209 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1210 } 1211 1212 private: 1213 void doGet(crow::Response &res, const crow::Request &req, 1214 const std::vector<std::string> ¶ms) override 1215 { 1216 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1217 asyncResp->res.jsonValue["@odata.type"] = 1218 "#LogService.v1_1_0.LogService"; 1219 asyncResp->res.jsonValue["@odata.id"] = 1220 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1221 asyncResp->res.jsonValue["@odata.context"] = 1222 "/redfish/v1/$metadata#LogService.LogService"; 1223 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1224 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1225 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1226 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1227 asyncResp->res.jsonValue["Entries"] = { 1228 {"@odata.id", 1229 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1230 } 1231 }; 1232 1233 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 1234 sd_journal *journal, 1235 nlohmann::json &bmcJournalLogEntryJson) 1236 { 1237 // Get the Log Entry contents 1238 int ret = 0; 1239 1240 std::string_view msg; 1241 ret = getJournalMetadata(journal, "MESSAGE", msg); 1242 if (ret < 0) 1243 { 1244 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1245 return 1; 1246 } 1247 1248 // Get the severity from the PRIORITY field 1249 long int severity = 8; // Default to an invalid priority 1250 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1251 if (ret < 0) 1252 { 1253 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1254 } 1255 1256 // Get the Created time from the timestamp 1257 std::string entryTimeStr; 1258 if (!getEntryTimestamp(journal, entryTimeStr)) 1259 { 1260 return 1; 1261 } 1262 1263 // Fill in the log entry with the gathered data 1264 bmcJournalLogEntryJson = { 1265 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1266 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1267 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1268 bmcJournalLogEntryID}, 1269 {"Name", "BMC Journal Entry"}, 1270 {"Id", bmcJournalLogEntryID}, 1271 {"Message", msg}, 1272 {"EntryType", "Oem"}, 1273 {"Severity", 1274 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1275 {"OemRecordFormat", "BMC Journal Entry"}, 1276 {"Created", std::move(entryTimeStr)}}; 1277 return 0; 1278 } 1279 1280 class BMCJournalLogEntryCollection : public Node 1281 { 1282 public: 1283 template <typename CrowApp> 1284 BMCJournalLogEntryCollection(CrowApp &app) : 1285 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1286 { 1287 entityPrivileges = { 1288 {boost::beast::http::verb::get, {{"Login"}}}, 1289 {boost::beast::http::verb::head, {{"Login"}}}, 1290 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1291 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1292 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1293 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1294 } 1295 1296 private: 1297 void doGet(crow::Response &res, const crow::Request &req, 1298 const std::vector<std::string> ¶ms) override 1299 { 1300 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1301 static constexpr const long maxEntriesPerPage = 1000; 1302 uint64_t skip = 0; 1303 uint64_t top = maxEntriesPerPage; // Show max entries by default 1304 if (!getSkipParam(asyncResp->res, req, skip)) 1305 { 1306 return; 1307 } 1308 if (!getTopParam(asyncResp->res, req, top)) 1309 { 1310 return; 1311 } 1312 // Collections don't include the static data added by SubRoute because 1313 // it has a duplicate entry for members 1314 asyncResp->res.jsonValue["@odata.type"] = 1315 "#LogEntryCollection.LogEntryCollection"; 1316 asyncResp->res.jsonValue["@odata.id"] = 1317 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1318 asyncResp->res.jsonValue["@odata.context"] = 1319 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1320 asyncResp->res.jsonValue["@odata.id"] = 1321 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1322 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1323 asyncResp->res.jsonValue["Description"] = 1324 "Collection of BMC Journal Entries"; 1325 asyncResp->res.jsonValue["@odata.id"] = 1326 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1327 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1328 logEntryArray = nlohmann::json::array(); 1329 1330 // Go through the journal and use the timestamp to create a unique ID 1331 // for each entry 1332 sd_journal *journalTmp = nullptr; 1333 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1334 if (ret < 0) 1335 { 1336 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1337 messages::internalError(asyncResp->res); 1338 return; 1339 } 1340 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1341 journalTmp, sd_journal_close); 1342 journalTmp = nullptr; 1343 uint64_t entryCount = 0; 1344 // Reset the unique ID on the first entry 1345 bool firstEntry = true; 1346 SD_JOURNAL_FOREACH(journal.get()) 1347 { 1348 entryCount++; 1349 // Handle paging using skip (number of entries to skip from the 1350 // start) and top (number of entries to display) 1351 if (entryCount <= skip || entryCount > skip + top) 1352 { 1353 continue; 1354 } 1355 1356 std::string idStr; 1357 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1358 { 1359 continue; 1360 } 1361 1362 if (firstEntry) 1363 { 1364 firstEntry = false; 1365 } 1366 1367 logEntryArray.push_back({}); 1368 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1369 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1370 bmcJournalLogEntry) != 0) 1371 { 1372 messages::internalError(asyncResp->res); 1373 return; 1374 } 1375 } 1376 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1377 if (skip + top < entryCount) 1378 { 1379 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1380 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1381 std::to_string(skip + top); 1382 } 1383 } 1384 }; 1385 1386 class BMCJournalLogEntry : public Node 1387 { 1388 public: 1389 BMCJournalLogEntry(CrowApp &app) : 1390 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1391 std::string()) 1392 { 1393 entityPrivileges = { 1394 {boost::beast::http::verb::get, {{"Login"}}}, 1395 {boost::beast::http::verb::head, {{"Login"}}}, 1396 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1397 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1398 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1399 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1400 } 1401 1402 private: 1403 void doGet(crow::Response &res, const crow::Request &req, 1404 const std::vector<std::string> ¶ms) override 1405 { 1406 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1407 if (params.size() != 1) 1408 { 1409 messages::internalError(asyncResp->res); 1410 return; 1411 } 1412 const std::string &entryID = params[0]; 1413 // Convert the unique ID back to a timestamp to find the entry 1414 uint64_t ts = 0; 1415 uint64_t index = 0; 1416 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1417 { 1418 return; 1419 } 1420 1421 sd_journal *journalTmp = nullptr; 1422 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1423 if (ret < 0) 1424 { 1425 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1426 messages::internalError(asyncResp->res); 1427 return; 1428 } 1429 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1430 journalTmp, sd_journal_close); 1431 journalTmp = nullptr; 1432 // Go to the timestamp in the log and move to the entry at the index 1433 // tracking the unique ID 1434 std::string idStr; 1435 bool firstEntry = true; 1436 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1437 for (uint64_t i = 0; i <= index; i++) 1438 { 1439 sd_journal_next(journal.get()); 1440 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1441 { 1442 messages::internalError(asyncResp->res); 1443 return; 1444 } 1445 if (firstEntry) 1446 { 1447 firstEntry = false; 1448 } 1449 } 1450 // Confirm that the entry ID matches what was requested 1451 if (idStr != entryID) 1452 { 1453 messages::resourceMissingAtURI(asyncResp->res, entryID); 1454 return; 1455 } 1456 1457 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1458 asyncResp->res.jsonValue) != 0) 1459 { 1460 messages::internalError(asyncResp->res); 1461 return; 1462 } 1463 } 1464 }; 1465 1466 class CrashdumpService : public Node 1467 { 1468 public: 1469 template <typename CrowApp> 1470 CrashdumpService(CrowApp &app) : 1471 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1472 { 1473 entityPrivileges = { 1474 {boost::beast::http::verb::get, {{"Login"}}}, 1475 {boost::beast::http::verb::head, {{"Login"}}}, 1476 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1477 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1478 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1479 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1480 } 1481 1482 private: 1483 /** 1484 * Functions triggers appropriate requests on DBus 1485 */ 1486 void doGet(crow::Response &res, const crow::Request &req, 1487 const std::vector<std::string> ¶ms) override 1488 { 1489 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1490 // Copy over the static data to include the entries added by SubRoute 1491 asyncResp->res.jsonValue["@odata.id"] = 1492 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1493 asyncResp->res.jsonValue["@odata.type"] = 1494 "#LogService.v1_1_0.LogService"; 1495 asyncResp->res.jsonValue["@odata.context"] = 1496 "/redfish/v1/$metadata#LogService.LogService"; 1497 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service"; 1498 asyncResp->res.jsonValue["Description"] = "Crashdump Service"; 1499 asyncResp->res.jsonValue["Id"] = "Crashdump"; 1500 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1501 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1502 asyncResp->res.jsonValue["Entries"] = { 1503 {"@odata.id", 1504 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1505 asyncResp->res.jsonValue["Actions"] = { 1506 {"Oem", 1507 {{"#Crashdump.OnDemand", 1508 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1509 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1510 1511 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1512 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1513 {"#Crashdump.SendRawPeci", 1514 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1515 "Actions/Oem/Crashdump.SendRawPeci"}}}); 1516 #endif 1517 } 1518 }; 1519 1520 class CrashdumpEntryCollection : public Node 1521 { 1522 public: 1523 template <typename CrowApp> 1524 CrashdumpEntryCollection(CrowApp &app) : 1525 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 1526 { 1527 entityPrivileges = { 1528 {boost::beast::http::verb::get, {{"Login"}}}, 1529 {boost::beast::http::verb::head, {{"Login"}}}, 1530 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1531 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1532 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1533 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1534 } 1535 1536 private: 1537 /** 1538 * Functions triggers appropriate requests on DBus 1539 */ 1540 void doGet(crow::Response &res, const crow::Request &req, 1541 const std::vector<std::string> ¶ms) override 1542 { 1543 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1544 // Collections don't include the static data added by SubRoute because 1545 // it has a duplicate entry for members 1546 auto getLogEntriesCallback = [asyncResp]( 1547 const boost::system::error_code ec, 1548 const std::vector<std::string> &resp) { 1549 if (ec) 1550 { 1551 if (ec.value() != 1552 boost::system::errc::no_such_file_or_directory) 1553 { 1554 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1555 << ec.message(); 1556 messages::internalError(asyncResp->res); 1557 return; 1558 } 1559 } 1560 asyncResp->res.jsonValue["@odata.type"] = 1561 "#LogEntryCollection.LogEntryCollection"; 1562 asyncResp->res.jsonValue["@odata.id"] = 1563 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 1564 asyncResp->res.jsonValue["@odata.context"] = 1565 "/redfish/v1/" 1566 "$metadata#LogEntryCollection.LogEntryCollection"; 1567 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 1568 asyncResp->res.jsonValue["Description"] = 1569 "Collection of Crashdump Entries"; 1570 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1571 logEntryArray = nlohmann::json::array(); 1572 for (const std::string &objpath : resp) 1573 { 1574 // Don't list the on-demand log 1575 if (objpath.compare(CrashdumpOnDemandPath) == 0) 1576 { 1577 continue; 1578 } 1579 std::size_t lastPos = objpath.rfind("/"); 1580 if (lastPos != std::string::npos) 1581 { 1582 logEntryArray.push_back( 1583 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 1584 "Crashdump/Entries/" + 1585 objpath.substr(lastPos + 1)}}); 1586 } 1587 } 1588 asyncResp->res.jsonValue["Members@odata.count"] = 1589 logEntryArray.size(); 1590 }; 1591 crow::connections::systemBus->async_method_call( 1592 std::move(getLogEntriesCallback), 1593 "xyz.openbmc_project.ObjectMapper", 1594 "/xyz/openbmc_project/object_mapper", 1595 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1596 std::array<const char *, 1>{CrashdumpInterface}); 1597 } 1598 }; 1599 1600 std::string getLogCreatedTime(const nlohmann::json &Crashdump) 1601 { 1602 nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data"); 1603 if (cdIt != Crashdump.end()) 1604 { 1605 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); 1606 if (siIt != cdIt->end()) 1607 { 1608 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1609 if (tsIt != siIt->end()) 1610 { 1611 const std::string *logTime = 1612 tsIt->get_ptr<const std::string *>(); 1613 if (logTime != nullptr) 1614 { 1615 return *logTime; 1616 } 1617 } 1618 } 1619 } 1620 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1621 1622 return std::string(); 1623 } 1624 1625 class CrashdumpEntry : public Node 1626 { 1627 public: 1628 CrashdumpEntry(CrowApp &app) : 1629 Node(app, 1630 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 1631 std::string()) 1632 { 1633 entityPrivileges = { 1634 {boost::beast::http::verb::get, {{"Login"}}}, 1635 {boost::beast::http::verb::head, {{"Login"}}}, 1636 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1637 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1638 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1639 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1640 } 1641 1642 private: 1643 void doGet(crow::Response &res, const crow::Request &req, 1644 const std::vector<std::string> ¶ms) override 1645 { 1646 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1647 if (params.size() != 1) 1648 { 1649 messages::internalError(asyncResp->res); 1650 return; 1651 } 1652 const int logId = std::atoi(params[0].c_str()); 1653 auto getStoredLogCallback = [asyncResp, logId]( 1654 const boost::system::error_code ec, 1655 const std::variant<std::string> &resp) { 1656 if (ec) 1657 { 1658 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1659 messages::internalError(asyncResp->res); 1660 return; 1661 } 1662 const std::string *log = std::get_if<std::string>(&resp); 1663 if (log == nullptr) 1664 { 1665 messages::internalError(asyncResp->res); 1666 return; 1667 } 1668 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1669 if (j.is_discarded()) 1670 { 1671 messages::internalError(asyncResp->res); 1672 return; 1673 } 1674 std::string t = getLogCreatedTime(j); 1675 asyncResp->res.jsonValue = { 1676 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1677 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1678 {"@odata.id", 1679 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1680 std::to_string(logId)}, 1681 {"Name", "CPU Crashdump"}, 1682 {"Id", logId}, 1683 {"EntryType", "Oem"}, 1684 {"OemRecordFormat", "Intel Crashdump"}, 1685 {"Oem", {{"Intel", std::move(j)}}}, 1686 {"Created", std::move(t)}}; 1687 }; 1688 crow::connections::systemBus->async_method_call( 1689 std::move(getStoredLogCallback), CrashdumpObject, 1690 CrashdumpPath + std::string("/") + std::to_string(logId), 1691 "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface, 1692 "Log"); 1693 } 1694 }; 1695 1696 class OnDemandCrashdump : public Node 1697 { 1698 public: 1699 OnDemandCrashdump(CrowApp &app) : 1700 Node(app, 1701 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1702 "Crashdump.OnDemand/") 1703 { 1704 entityPrivileges = { 1705 {boost::beast::http::verb::get, {{"Login"}}}, 1706 {boost::beast::http::verb::head, {{"Login"}}}, 1707 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1708 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1709 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1710 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1711 } 1712 1713 private: 1714 void doPost(crow::Response &res, const crow::Request &req, 1715 const std::vector<std::string> ¶ms) override 1716 { 1717 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1718 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher; 1719 1720 // Only allow one OnDemand Log request at a time 1721 if (onDemandLogMatcher != nullptr) 1722 { 1723 asyncResp->res.addHeader("Retry-After", "30"); 1724 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1725 return; 1726 } 1727 // Make this static so it survives outside this method 1728 static boost::asio::steady_timer timeout(*req.ioService); 1729 1730 timeout.expires_after(std::chrono::seconds(30)); 1731 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1732 onDemandLogMatcher = nullptr; 1733 if (ec) 1734 { 1735 // operation_aborted is expected if timer is canceled before 1736 // completion. 1737 if (ec != boost::asio::error::operation_aborted) 1738 { 1739 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1740 } 1741 return; 1742 } 1743 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log"; 1744 1745 messages::internalError(asyncResp->res); 1746 }); 1747 1748 auto onDemandLogMatcherCallback = [asyncResp]( 1749 sdbusplus::message::message &m) { 1750 BMCWEB_LOG_DEBUG << "OnDemand log available match fired"; 1751 timeout.cancel(); 1752 1753 sdbusplus::message::object_path objPath; 1754 boost::container::flat_map< 1755 std::string, boost::container::flat_map< 1756 std::string, std::variant<std::string>>> 1757 interfacesAdded; 1758 m.read(objPath, interfacesAdded); 1759 const std::string *log = std::get_if<std::string>( 1760 &interfacesAdded[CrashdumpInterface]["Log"]); 1761 if (log == nullptr) 1762 { 1763 messages::internalError(asyncResp->res); 1764 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1765 // match object inside which this lambda is executing. Once it 1766 // is set to nullptr, the match object will be destroyed and the 1767 // lambda will lose its context, including res, so it needs to 1768 // be the last thing done. 1769 onDemandLogMatcher = nullptr; 1770 return; 1771 } 1772 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1773 if (j.is_discarded()) 1774 { 1775 messages::internalError(asyncResp->res); 1776 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1777 // match object inside which this lambda is executing. Once it 1778 // is set to nullptr, the match object will be destroyed and the 1779 // lambda will lose its context, including res, so it needs to 1780 // be the last thing done. 1781 onDemandLogMatcher = nullptr; 1782 return; 1783 } 1784 std::string t = getLogCreatedTime(j); 1785 asyncResp->res.jsonValue = { 1786 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1787 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1788 {"Name", "CPU Crashdump"}, 1789 {"EntryType", "Oem"}, 1790 {"OemRecordFormat", "Intel Crashdump"}, 1791 {"Oem", {{"Intel", std::move(j)}}}, 1792 {"Created", std::move(t)}}; 1793 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1794 // match object inside which this lambda is executing. Once it is 1795 // set to nullptr, the match object will be destroyed and the lambda 1796 // will lose its context, including res, so it needs to be the last 1797 // thing done. 1798 onDemandLogMatcher = nullptr; 1799 }; 1800 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1801 *crow::connections::systemBus, 1802 sdbusplus::bus::match::rules::interfacesAdded() + 1803 sdbusplus::bus::match::rules::argNpath(0, 1804 CrashdumpOnDemandPath), 1805 std::move(onDemandLogMatcherCallback)); 1806 1807 auto generateonDemandLogCallback = 1808 [asyncResp](const boost::system::error_code ec, 1809 const std::string &resp) { 1810 if (ec) 1811 { 1812 if (ec.value() == 1813 boost::system::errc::operation_not_supported) 1814 { 1815 messages::resourceInStandby(asyncResp->res); 1816 } 1817 else 1818 { 1819 messages::internalError(asyncResp->res); 1820 } 1821 1822 timeout.cancel(); 1823 onDemandLogMatcher = nullptr; 1824 return; 1825 } 1826 }; 1827 crow::connections::systemBus->async_method_call( 1828 std::move(generateonDemandLogCallback), CrashdumpObject, 1829 CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog"); 1830 } 1831 }; 1832 1833 class SendRawPECI : public Node 1834 { 1835 public: 1836 SendRawPECI(CrowApp &app) : 1837 Node(app, 1838 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1839 "Crashdump.SendRawPeci/") 1840 { 1841 entityPrivileges = { 1842 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1843 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1844 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1845 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1846 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1847 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1848 } 1849 1850 private: 1851 void doPost(crow::Response &res, const crow::Request &req, 1852 const std::vector<std::string> ¶ms) override 1853 { 1854 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1855 uint8_t clientAddress = 0; 1856 uint8_t readLength = 0; 1857 std::vector<uint8_t> peciCommand; 1858 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1859 "ReadLength", readLength, "PECICommand", 1860 peciCommand)) 1861 { 1862 return; 1863 } 1864 1865 // Callback to return the Raw PECI response 1866 auto sendRawPECICallback = 1867 [asyncResp](const boost::system::error_code ec, 1868 const std::vector<uint8_t> &resp) { 1869 if (ec) 1870 { 1871 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1872 << ec.message(); 1873 messages::internalError(asyncResp->res); 1874 return; 1875 } 1876 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1877 {"PECIResponse", resp}}; 1878 }; 1879 // Call the SendRawPECI command with the provided data 1880 crow::connections::systemBus->async_method_call( 1881 std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath, 1882 CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1883 peciCommand); 1884 } 1885 }; 1886 1887 /** 1888 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 1889 */ 1890 class DBusLogServiceActionsClear : public Node 1891 { 1892 public: 1893 DBusLogServiceActionsClear(CrowApp &app) : 1894 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 1895 "LogService.Reset") 1896 { 1897 entityPrivileges = { 1898 {boost::beast::http::verb::get, {{"Login"}}}, 1899 {boost::beast::http::verb::head, {{"Login"}}}, 1900 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1901 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1902 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1903 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1904 } 1905 1906 private: 1907 /** 1908 * Function handles POST method request. 1909 * The Clear Log actions does not require any parameter.The action deletes 1910 * all entries found in the Entries collection for this Log Service. 1911 */ 1912 void doPost(crow::Response &res, const crow::Request &req, 1913 const std::vector<std::string> ¶ms) override 1914 { 1915 BMCWEB_LOG_DEBUG << "Do delete all entries."; 1916 1917 auto asyncResp = std::make_shared<AsyncResp>(res); 1918 // Process response from Logging service. 1919 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 1920 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 1921 if (ec) 1922 { 1923 // TODO Handle for specific error code 1924 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 1925 asyncResp->res.result( 1926 boost::beast::http::status::internal_server_error); 1927 return; 1928 } 1929 1930 asyncResp->res.result(boost::beast::http::status::no_content); 1931 }; 1932 1933 // Make call to Logging service to request Clear Log 1934 crow::connections::systemBus->async_method_call( 1935 resp_handler, "xyz.openbmc_project.Logging", 1936 "/xyz/openbmc_project/logging", 1937 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 1938 } 1939 }; 1940 } // namespace redfish 1941