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