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