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