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