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