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