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 std::string &messageArgsStart = logEntryFields[1]; 588 std::size_t messageArgsSize = logEntryFields.size() - 1; 589 590 // Get the Message from the MessageRegistry 591 const message_registries::Message *message = 592 message_registries::getMessage(messageID); 593 594 std::string msg; 595 std::string severity; 596 if (message != nullptr) 597 { 598 msg = message->message; 599 severity = message->severity; 600 } 601 602 // Get the MessageArgs from the log 603 boost::beast::span messageArgs(&messageArgsStart, messageArgsSize); 604 605 // Fill the MessageArgs into the Message 606 int i = 0; 607 for (const std::string &messageArg : messageArgs) 608 { 609 std::string argStr = "%" + std::to_string(++i); 610 size_t argPos = msg.find(argStr); 611 if (argPos != std::string::npos) 612 { 613 msg.replace(argPos, argStr.length(), messageArg); 614 } 615 } 616 617 // Get the Created time from the timestamp. The log timestamp is in RFC3339 618 // format which matches the Redfish format except for the fractional seconds 619 // between the '.' and the '+', so just remove them. 620 std::size_t dot = timestamp.find_first_of("."); 621 std::size_t plus = timestamp.find_first_of("+"); 622 if (dot != std::string::npos && plus != std::string::npos) 623 { 624 timestamp.erase(dot, plus - dot); 625 } 626 627 // Fill in the log entry with the gathered data 628 logEntryJson = { 629 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 630 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 631 {"@odata.id", 632 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/#" + 633 logEntryID}, 634 {"Name", "System Event Log Entry"}, 635 {"Id", logEntryID}, 636 {"Message", std::move(msg)}, 637 {"MessageId", std::move(messageID)}, 638 {"MessageArgs", std::move(messageArgs)}, 639 {"EntryType", "Event"}, 640 {"Severity", std::move(severity)}, 641 {"Created", std::move(timestamp)}}; 642 return 0; 643 } 644 645 class EventLogEntryCollection : public Node 646 { 647 public: 648 template <typename CrowApp> 649 EventLogEntryCollection(CrowApp &app) : 650 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 651 { 652 entityPrivileges = { 653 {boost::beast::http::verb::get, {{"Login"}}}, 654 {boost::beast::http::verb::head, {{"Login"}}}, 655 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 656 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 657 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 658 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 659 } 660 661 private: 662 void doGet(crow::Response &res, const crow::Request &req, 663 const std::vector<std::string> ¶ms) override 664 { 665 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 666 long skip = 0; 667 long top = maxEntriesPerPage; // Show max entries by default 668 if (!getSkipParam(asyncResp->res, req, skip)) 669 { 670 return; 671 } 672 if (!getTopParam(asyncResp->res, req, top)) 673 { 674 return; 675 } 676 // Collections don't include the static data added by SubRoute because 677 // it has a duplicate entry for members 678 asyncResp->res.jsonValue["@odata.type"] = 679 "#LogEntryCollection.LogEntryCollection"; 680 asyncResp->res.jsonValue["@odata.context"] = 681 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 682 asyncResp->res.jsonValue["@odata.id"] = 683 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 684 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 685 asyncResp->res.jsonValue["Description"] = 686 "Collection of System Event Log Entries"; 687 688 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 689 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 690 logEntryArray = nlohmann::json::array(); 691 // Go through the log files and create a unique ID for each entry 692 std::vector<std::filesystem::path> redfishLogFiles; 693 getRedfishLogFiles(redfishLogFiles); 694 uint64_t entryCount = 0; 695 std::string logEntry; 696 697 // Oldest logs are in the last file, so start there and loop backwards 698 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 699 it++) 700 { 701 std::ifstream logStream(*it); 702 if (!logStream.is_open()) 703 { 704 continue; 705 } 706 707 while (std::getline(logStream, logEntry)) 708 { 709 entryCount++; 710 // Handle paging using skip (number of entries to skip from the 711 // start) and top (number of entries to display) 712 if (entryCount <= skip || entryCount > skip + top) 713 { 714 continue; 715 } 716 717 std::string idStr; 718 if (!getUniqueEntryID(logEntry, idStr)) 719 { 720 continue; 721 } 722 723 logEntryArray.push_back({}); 724 nlohmann::json &bmcLogEntry = logEntryArray.back(); 725 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 726 { 727 messages::internalError(asyncResp->res); 728 return; 729 } 730 } 731 } 732 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 733 if (skip + top < entryCount) 734 { 735 asyncResp->res.jsonValue["Members@odata.nextLink"] = 736 "/redfish/v1/Systems/system/LogServices/EventLog/" 737 "Entries?$skip=" + 738 std::to_string(skip + top); 739 } 740 #else 741 // DBus implementation of EventLog/Entries 742 // Make call to Logging Service to find all log entry objects 743 crow::connections::systemBus->async_method_call( 744 [asyncResp](const boost::system::error_code ec, 745 GetManagedObjectsType &resp) { 746 if (ec) 747 { 748 // TODO Handle for specific error code 749 BMCWEB_LOG_ERROR 750 << "getLogEntriesIfaceData resp_handler got error " 751 << ec; 752 messages::internalError(asyncResp->res); 753 return; 754 } 755 nlohmann::json &entriesArray = 756 asyncResp->res.jsonValue["Members"]; 757 entriesArray = nlohmann::json::array(); 758 for (auto &objectPath : resp) 759 { 760 for (auto &interfaceMap : objectPath.second) 761 { 762 if (interfaceMap.first != 763 "xyz.openbmc_project.Logging.Entry") 764 { 765 BMCWEB_LOG_DEBUG << "Bailing early on " 766 << interfaceMap.first; 767 continue; 768 } 769 entriesArray.push_back({}); 770 nlohmann::json &thisEntry = entriesArray.back(); 771 uint32_t *id; 772 std::time_t timestamp; 773 std::string *severity, *message; 774 bool *resolved; 775 for (auto &propertyMap : interfaceMap.second) 776 { 777 if (propertyMap.first == "Id") 778 { 779 id = sdbusplus::message::variant_ns::get_if< 780 uint32_t>(&propertyMap.second); 781 if (id == nullptr) 782 { 783 messages::propertyMissing(asyncResp->res, 784 "Id"); 785 } 786 } 787 else if (propertyMap.first == "Timestamp") 788 { 789 const uint64_t *millisTimeStamp = 790 std::get_if<uint64_t>(&propertyMap.second); 791 if (millisTimeStamp == nullptr) 792 { 793 messages::propertyMissing(asyncResp->res, 794 "Timestamp"); 795 } 796 // Retrieve Created property with format: 797 // yyyy-mm-ddThh:mm:ss 798 std::chrono::milliseconds chronoTimeStamp( 799 *millisTimeStamp); 800 timestamp = 801 std::chrono::duration_cast< 802 std::chrono::seconds>(chronoTimeStamp) 803 .count(); 804 } 805 else if (propertyMap.first == "Severity") 806 { 807 severity = std::get_if<std::string>( 808 &propertyMap.second); 809 if (severity == nullptr) 810 { 811 messages::propertyMissing(asyncResp->res, 812 "Severity"); 813 } 814 } 815 else if (propertyMap.first == "Message") 816 { 817 message = std::get_if<std::string>( 818 &propertyMap.second); 819 if (message == nullptr) 820 { 821 messages::propertyMissing(asyncResp->res, 822 "Message"); 823 } 824 } 825 } 826 thisEntry = { 827 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 828 {"@odata.context", "/redfish/v1/" 829 "$metadata#LogEntry.LogEntry"}, 830 {"@odata.id", 831 "/redfish/v1/Systems/system/LogServices/EventLog/" 832 "Entries/" + 833 std::to_string(*id)}, 834 {"Name", "System DBus Event Log Entry"}, 835 {"Id", std::to_string(*id)}, 836 {"Message", *message}, 837 {"EntryType", "Event"}, 838 {"Severity", 839 translateSeverityDbusToRedfish(*severity)}, 840 {"Created", crow::utility::getDateTime(timestamp)}}; 841 } 842 } 843 std::sort(entriesArray.begin(), entriesArray.end(), 844 [](const nlohmann::json &left, 845 const nlohmann::json &right) { 846 return (left["Id"] <= right["Id"]); 847 }); 848 asyncResp->res.jsonValue["Members@odata.count"] = 849 entriesArray.size(); 850 }, 851 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 852 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 853 #endif 854 } 855 }; 856 857 class EventLogEntry : public Node 858 { 859 public: 860 EventLogEntry(CrowApp &app) : 861 Node(app, 862 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 863 std::string()) 864 { 865 entityPrivileges = { 866 {boost::beast::http::verb::get, {{"Login"}}}, 867 {boost::beast::http::verb::head, {{"Login"}}}, 868 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 869 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 870 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 871 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 872 } 873 874 private: 875 void doGet(crow::Response &res, const crow::Request &req, 876 const std::vector<std::string> ¶ms) override 877 { 878 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 879 if (params.size() != 1) 880 { 881 messages::internalError(asyncResp->res); 882 return; 883 } 884 const std::string &entryID = params[0]; 885 886 #ifdef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 887 // DBus implementation of EventLog/Entries 888 // Make call to Logging Service to find all log entry objects 889 crow::connections::systemBus->async_method_call( 890 [asyncResp, entryID](const boost::system::error_code ec, 891 GetManagedPropertyType &resp) { 892 if (ec) 893 { 894 BMCWEB_LOG_ERROR 895 << "EventLogEntry (DBus) resp_handler got error " << ec; 896 messages::internalError(asyncResp->res); 897 return; 898 } 899 uint32_t *id; 900 std::time_t timestamp; 901 std::string *severity, *message; 902 bool *resolved; 903 for (auto &propertyMap : resp) 904 { 905 if (propertyMap.first == "Id") 906 { 907 id = std::get_if<uint32_t>(&propertyMap.second); 908 if (id == nullptr) 909 { 910 messages::propertyMissing(asyncResp->res, "Id"); 911 } 912 } 913 else if (propertyMap.first == "Timestamp") 914 { 915 const uint64_t *millisTimeStamp = 916 std::get_if<uint64_t>(&propertyMap.second); 917 if (millisTimeStamp == nullptr) 918 { 919 messages::propertyMissing(asyncResp->res, 920 "Timestamp"); 921 } 922 // Retrieve Created property with format: 923 // yyyy-mm-ddThh:mm:ss 924 std::chrono::milliseconds chronoTimeStamp( 925 *millisTimeStamp); 926 timestamp = 927 std::chrono::duration_cast<std::chrono::seconds>( 928 chronoTimeStamp) 929 .count(); 930 } 931 else if (propertyMap.first == "Severity") 932 { 933 severity = 934 std::get_if<std::string>(&propertyMap.second); 935 if (severity == nullptr) 936 { 937 messages::propertyMissing(asyncResp->res, 938 "Severity"); 939 } 940 } 941 else if (propertyMap.first == "Message") 942 { 943 message = std::get_if<std::string>(&propertyMap.second); 944 if (message == nullptr) 945 { 946 messages::propertyMissing(asyncResp->res, 947 "Message"); 948 } 949 } 950 } 951 asyncResp->res.jsonValue = { 952 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 953 {"@odata.context", "/redfish/v1/" 954 "$metadata#LogEntry.LogEntry"}, 955 {"@odata.id", 956 "/redfish/v1/Systems/system/LogServices/EventLog/" 957 "Entries/" + 958 std::to_string(*id)}, 959 {"Name", "System DBus Event Log Entry"}, 960 {"Id", std::to_string(*id)}, 961 {"Message", *message}, 962 {"EntryType", "Event"}, 963 {"Severity", translateSeverityDbusToRedfish(*severity)}, 964 { "Created", 965 crow::utility::getDateTime(timestamp) }}; 966 }, 967 "xyz.openbmc_project.Logging", 968 "/xyz/openbmc_project/logging/entry/" + entryID, 969 "org.freedesktop.DBus.Properties", "GetAll", 970 "xyz.openbmc_project.Logging.Entry"); 971 #endif 972 } 973 }; 974 975 class BMCLogServiceCollection : public Node 976 { 977 public: 978 template <typename CrowApp> 979 BMCLogServiceCollection(CrowApp &app) : 980 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 981 { 982 entityPrivileges = { 983 {boost::beast::http::verb::get, {{"Login"}}}, 984 {boost::beast::http::verb::head, {{"Login"}}}, 985 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 986 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 987 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 988 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 989 } 990 991 private: 992 /** 993 * Functions triggers appropriate requests on DBus 994 */ 995 void doGet(crow::Response &res, const crow::Request &req, 996 const std::vector<std::string> ¶ms) override 997 { 998 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 999 // Collections don't include the static data added by SubRoute because 1000 // it has a duplicate entry for members 1001 asyncResp->res.jsonValue["@odata.type"] = 1002 "#LogServiceCollection.LogServiceCollection"; 1003 asyncResp->res.jsonValue["@odata.context"] = 1004 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 1005 asyncResp->res.jsonValue["@odata.id"] = 1006 "/redfish/v1/Managers/bmc/LogServices"; 1007 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1008 asyncResp->res.jsonValue["Description"] = 1009 "Collection of LogServices for this Manager"; 1010 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 1011 logServiceArray = nlohmann::json::array(); 1012 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1013 logServiceArray.push_back( 1014 {{ "@odata.id", 1015 "/redfish/v1/Managers/bmc/LogServices/Journal" }}); 1016 #endif 1017 asyncResp->res.jsonValue["Members@odata.count"] = 1018 logServiceArray.size(); 1019 } 1020 }; 1021 1022 class BMCJournalLogService : public Node 1023 { 1024 public: 1025 template <typename CrowApp> 1026 BMCJournalLogService(CrowApp &app) : 1027 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1028 { 1029 entityPrivileges = { 1030 {boost::beast::http::verb::get, {{"Login"}}}, 1031 {boost::beast::http::verb::head, {{"Login"}}}, 1032 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1033 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1034 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1035 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1036 } 1037 1038 private: 1039 void doGet(crow::Response &res, const crow::Request &req, 1040 const std::vector<std::string> ¶ms) override 1041 { 1042 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1043 asyncResp->res.jsonValue["@odata.type"] = 1044 "#LogService.v1_1_0.LogService"; 1045 asyncResp->res.jsonValue["@odata.id"] = 1046 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1047 asyncResp->res.jsonValue["@odata.context"] = 1048 "/redfish/v1/$metadata#LogService.LogService"; 1049 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1050 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1051 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1052 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1053 asyncResp->res.jsonValue["Entries"] = { 1054 {"@odata.id", 1055 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1056 } 1057 }; 1058 1059 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 1060 sd_journal *journal, 1061 nlohmann::json &bmcJournalLogEntryJson) 1062 { 1063 // Get the Log Entry contents 1064 int ret = 0; 1065 1066 std::string_view msg; 1067 ret = getJournalMetadata(journal, "MESSAGE", msg); 1068 if (ret < 0) 1069 { 1070 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1071 return 1; 1072 } 1073 1074 // Get the severity from the PRIORITY field 1075 int severity = 8; // Default to an invalid priority 1076 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1077 if (ret < 0) 1078 { 1079 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1080 return 1; 1081 } 1082 1083 // Get the Created time from the timestamp 1084 std::string entryTimeStr; 1085 if (!getEntryTimestamp(journal, entryTimeStr)) 1086 { 1087 return 1; 1088 } 1089 1090 // Fill in the log entry with the gathered data 1091 bmcJournalLogEntryJson = { 1092 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1093 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1094 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1095 bmcJournalLogEntryID}, 1096 {"Name", "BMC Journal Entry"}, 1097 {"Id", bmcJournalLogEntryID}, 1098 {"Message", msg}, 1099 {"EntryType", "Oem"}, 1100 {"Severity", 1101 severity <= 2 ? "Critical" 1102 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 1103 {"OemRecordFormat", "BMC Journal Entry"}, 1104 {"Created", std::move(entryTimeStr)}}; 1105 return 0; 1106 } 1107 1108 class BMCJournalLogEntryCollection : public Node 1109 { 1110 public: 1111 template <typename CrowApp> 1112 BMCJournalLogEntryCollection(CrowApp &app) : 1113 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1114 { 1115 entityPrivileges = { 1116 {boost::beast::http::verb::get, {{"Login"}}}, 1117 {boost::beast::http::verb::head, {{"Login"}}}, 1118 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1119 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1120 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1121 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1122 } 1123 1124 private: 1125 void doGet(crow::Response &res, const crow::Request &req, 1126 const std::vector<std::string> ¶ms) override 1127 { 1128 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1129 static constexpr const long maxEntriesPerPage = 1000; 1130 long skip = 0; 1131 long top = maxEntriesPerPage; // Show max entries by default 1132 if (!getSkipParam(asyncResp->res, req, skip)) 1133 { 1134 return; 1135 } 1136 if (!getTopParam(asyncResp->res, req, top)) 1137 { 1138 return; 1139 } 1140 // Collections don't include the static data added by SubRoute because 1141 // it has a duplicate entry for members 1142 asyncResp->res.jsonValue["@odata.type"] = 1143 "#LogEntryCollection.LogEntryCollection"; 1144 asyncResp->res.jsonValue["@odata.id"] = 1145 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1146 asyncResp->res.jsonValue["@odata.context"] = 1147 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1148 asyncResp->res.jsonValue["@odata.id"] = 1149 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1150 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1151 asyncResp->res.jsonValue["Description"] = 1152 "Collection of BMC Journal Entries"; 1153 asyncResp->res.jsonValue["@odata.id"] = 1154 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1155 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1156 logEntryArray = nlohmann::json::array(); 1157 1158 // Go through the journal and use the timestamp to create a unique ID 1159 // for each entry 1160 sd_journal *journalTmp = nullptr; 1161 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1162 if (ret < 0) 1163 { 1164 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1165 messages::internalError(asyncResp->res); 1166 return; 1167 } 1168 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1169 journalTmp, sd_journal_close); 1170 journalTmp = nullptr; 1171 uint64_t entryCount = 0; 1172 SD_JOURNAL_FOREACH(journal.get()) 1173 { 1174 entryCount++; 1175 // Handle paging using skip (number of entries to skip from the 1176 // start) and top (number of entries to display) 1177 if (entryCount <= skip || entryCount > skip + top) 1178 { 1179 continue; 1180 } 1181 1182 std::string idStr; 1183 if (!getUniqueEntryID(journal.get(), idStr)) 1184 { 1185 continue; 1186 } 1187 1188 logEntryArray.push_back({}); 1189 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1190 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1191 bmcJournalLogEntry) != 0) 1192 { 1193 messages::internalError(asyncResp->res); 1194 return; 1195 } 1196 } 1197 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1198 if (skip + top < entryCount) 1199 { 1200 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1201 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1202 std::to_string(skip + top); 1203 } 1204 } 1205 }; 1206 1207 class BMCJournalLogEntry : public Node 1208 { 1209 public: 1210 BMCJournalLogEntry(CrowApp &app) : 1211 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1212 std::string()) 1213 { 1214 entityPrivileges = { 1215 {boost::beast::http::verb::get, {{"Login"}}}, 1216 {boost::beast::http::verb::head, {{"Login"}}}, 1217 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1218 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1219 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1220 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1221 } 1222 1223 private: 1224 void doGet(crow::Response &res, const crow::Request &req, 1225 const std::vector<std::string> ¶ms) override 1226 { 1227 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1228 if (params.size() != 1) 1229 { 1230 messages::internalError(asyncResp->res); 1231 return; 1232 } 1233 const std::string &entryID = params[0]; 1234 // Convert the unique ID back to a timestamp to find the entry 1235 uint64_t ts = 0; 1236 uint16_t index = 0; 1237 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1238 { 1239 return; 1240 } 1241 1242 sd_journal *journalTmp = nullptr; 1243 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1244 if (ret < 0) 1245 { 1246 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1247 messages::internalError(asyncResp->res); 1248 return; 1249 } 1250 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1251 journalTmp, sd_journal_close); 1252 journalTmp = nullptr; 1253 // Go to the timestamp in the log and move to the entry at the index 1254 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1255 for (int i = 0; i <= index; i++) 1256 { 1257 sd_journal_next(journal.get()); 1258 } 1259 // Confirm that the entry ID matches what was requested 1260 std::string idStr; 1261 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 1262 { 1263 messages::resourceMissingAtURI(asyncResp->res, entryID); 1264 return; 1265 } 1266 1267 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1268 asyncResp->res.jsonValue) != 0) 1269 { 1270 messages::internalError(asyncResp->res); 1271 return; 1272 } 1273 } 1274 }; 1275 1276 class CrashdumpService : public Node 1277 { 1278 public: 1279 template <typename CrowApp> 1280 CrashdumpService(CrowApp &app) : 1281 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1282 { 1283 entityPrivileges = { 1284 {boost::beast::http::verb::get, {{"Login"}}}, 1285 {boost::beast::http::verb::head, {{"Login"}}}, 1286 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1287 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1288 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1289 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1290 } 1291 1292 private: 1293 /** 1294 * Functions triggers appropriate requests on DBus 1295 */ 1296 void doGet(crow::Response &res, const crow::Request &req, 1297 const std::vector<std::string> ¶ms) override 1298 { 1299 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1300 // Copy over the static data to include the entries added by SubRoute 1301 asyncResp->res.jsonValue["@odata.id"] = 1302 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1303 asyncResp->res.jsonValue["@odata.type"] = 1304 "#LogService.v1_1_0.LogService"; 1305 asyncResp->res.jsonValue["@odata.context"] = 1306 "/redfish/v1/$metadata#LogService.LogService"; 1307 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Service"; 1308 asyncResp->res.jsonValue["Description"] = "Crashdump Service"; 1309 asyncResp->res.jsonValue["Id"] = "Crashdump"; 1310 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1311 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1312 asyncResp->res.jsonValue["Entries"] = { 1313 {"@odata.id", 1314 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1315 asyncResp->res.jsonValue["Actions"] = { 1316 {"Oem", 1317 {{"#Crashdump.OnDemand", 1318 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1319 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1320 1321 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1322 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1323 {"#Crashdump.SendRawPeci", 1324 { { "target", 1325 "/redfish/v1/Systems/system/LogServices/Crashdump/" 1326 "Actions/Oem/Crashdump.SendRawPeci" } }}); 1327 #endif 1328 } 1329 }; 1330 1331 class CrashdumpEntryCollection : public Node 1332 { 1333 public: 1334 template <typename CrowApp> 1335 CrashdumpEntryCollection(CrowApp &app) : 1336 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 1337 { 1338 entityPrivileges = { 1339 {boost::beast::http::verb::get, {{"Login"}}}, 1340 {boost::beast::http::verb::head, {{"Login"}}}, 1341 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1342 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1343 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1344 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1345 } 1346 1347 private: 1348 /** 1349 * Functions triggers appropriate requests on DBus 1350 */ 1351 void doGet(crow::Response &res, const crow::Request &req, 1352 const std::vector<std::string> ¶ms) override 1353 { 1354 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1355 // Collections don't include the static data added by SubRoute because 1356 // it has a duplicate entry for members 1357 auto getLogEntriesCallback = [asyncResp]( 1358 const boost::system::error_code ec, 1359 const std::vector<std::string> &resp) { 1360 if (ec) 1361 { 1362 if (ec.value() != 1363 boost::system::errc::no_such_file_or_directory) 1364 { 1365 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1366 << ec.message(); 1367 messages::internalError(asyncResp->res); 1368 return; 1369 } 1370 } 1371 asyncResp->res.jsonValue["@odata.type"] = 1372 "#LogEntryCollection.LogEntryCollection"; 1373 asyncResp->res.jsonValue["@odata.id"] = 1374 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 1375 asyncResp->res.jsonValue["@odata.context"] = 1376 "/redfish/v1/" 1377 "$metadata#LogEntryCollection.LogEntryCollection"; 1378 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 1379 asyncResp->res.jsonValue["Description"] = 1380 "Collection of Crashdump Entries"; 1381 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1382 logEntryArray = nlohmann::json::array(); 1383 for (const std::string &objpath : resp) 1384 { 1385 // Don't list the on-demand log 1386 if (objpath.compare(CrashdumpOnDemandPath) == 0) 1387 { 1388 continue; 1389 } 1390 std::size_t lastPos = objpath.rfind("/"); 1391 if (lastPos != std::string::npos) 1392 { 1393 logEntryArray.push_back( 1394 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 1395 "Crashdump/Entries/" + 1396 objpath.substr(lastPos + 1)}}); 1397 } 1398 } 1399 asyncResp->res.jsonValue["Members@odata.count"] = 1400 logEntryArray.size(); 1401 }; 1402 crow::connections::systemBus->async_method_call( 1403 std::move(getLogEntriesCallback), 1404 "xyz.openbmc_project.ObjectMapper", 1405 "/xyz/openbmc_project/object_mapper", 1406 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1407 std::array<const char *, 1>{CrashdumpInterface}); 1408 } 1409 }; 1410 1411 std::string getLogCreatedTime(const nlohmann::json &Crashdump) 1412 { 1413 nlohmann::json::const_iterator cdIt = Crashdump.find("crashlog_data"); 1414 if (cdIt != Crashdump.end()) 1415 { 1416 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); 1417 if (siIt != cdIt->end()) 1418 { 1419 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1420 if (tsIt != siIt->end()) 1421 { 1422 const std::string *logTime = 1423 tsIt->get_ptr<const std::string *>(); 1424 if (logTime != nullptr) 1425 { 1426 return *logTime; 1427 } 1428 } 1429 } 1430 } 1431 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1432 1433 return std::string(); 1434 } 1435 1436 class CrashdumpEntry : public Node 1437 { 1438 public: 1439 CrashdumpEntry(CrowApp &app) : 1440 Node(app, 1441 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 1442 std::string()) 1443 { 1444 entityPrivileges = { 1445 {boost::beast::http::verb::get, {{"Login"}}}, 1446 {boost::beast::http::verb::head, {{"Login"}}}, 1447 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1448 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1449 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1450 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1451 } 1452 1453 private: 1454 void doGet(crow::Response &res, const crow::Request &req, 1455 const std::vector<std::string> ¶ms) override 1456 { 1457 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1458 if (params.size() != 1) 1459 { 1460 messages::internalError(asyncResp->res); 1461 return; 1462 } 1463 const uint8_t logId = std::atoi(params[0].c_str()); 1464 auto getStoredLogCallback = [asyncResp, logId]( 1465 const boost::system::error_code ec, 1466 const std::variant<std::string> &resp) { 1467 if (ec) 1468 { 1469 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1470 messages::internalError(asyncResp->res); 1471 return; 1472 } 1473 const std::string *log = std::get_if<std::string>(&resp); 1474 if (log == nullptr) 1475 { 1476 messages::internalError(asyncResp->res); 1477 return; 1478 } 1479 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1480 if (j.is_discarded()) 1481 { 1482 messages::internalError(asyncResp->res); 1483 return; 1484 } 1485 std::string t = getLogCreatedTime(j); 1486 asyncResp->res.jsonValue = { 1487 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1488 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1489 {"@odata.id", 1490 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1491 std::to_string(logId)}, 1492 {"Name", "CPU Crashdump"}, 1493 {"Id", logId}, 1494 {"EntryType", "Oem"}, 1495 {"OemRecordFormat", "Intel Crashdump"}, 1496 {"Oem", {{"Intel", std::move(j)}}}, 1497 {"Created", std::move(t)}}; 1498 }; 1499 crow::connections::systemBus->async_method_call( 1500 std::move(getStoredLogCallback), CrashdumpObject, 1501 CrashdumpPath + std::string("/") + std::to_string(logId), 1502 "org.freedesktop.DBus.Properties", "Get", CrashdumpInterface, 1503 "Log"); 1504 } 1505 }; 1506 1507 class OnDemandCrashdump : public Node 1508 { 1509 public: 1510 OnDemandCrashdump(CrowApp &app) : 1511 Node(app, 1512 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1513 "Crashdump.OnDemand/") 1514 { 1515 entityPrivileges = { 1516 {boost::beast::http::verb::get, {{"Login"}}}, 1517 {boost::beast::http::verb::head, {{"Login"}}}, 1518 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1519 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1520 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1521 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1522 } 1523 1524 private: 1525 void doPost(crow::Response &res, const crow::Request &req, 1526 const std::vector<std::string> ¶ms) override 1527 { 1528 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1529 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher; 1530 1531 // Only allow one OnDemand Log request at a time 1532 if (onDemandLogMatcher != nullptr) 1533 { 1534 asyncResp->res.addHeader("Retry-After", "30"); 1535 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1536 return; 1537 } 1538 // Make this static so it survives outside this method 1539 static boost::asio::deadline_timer timeout(*req.ioService); 1540 1541 timeout.expires_from_now(boost::posix_time::seconds(30)); 1542 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1543 onDemandLogMatcher = nullptr; 1544 if (ec) 1545 { 1546 // operation_aborted is expected if timer is canceled before 1547 // completion. 1548 if (ec != boost::asio::error::operation_aborted) 1549 { 1550 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1551 } 1552 return; 1553 } 1554 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log"; 1555 1556 messages::internalError(asyncResp->res); 1557 }); 1558 1559 auto onDemandLogMatcherCallback = [asyncResp]( 1560 sdbusplus::message::message &m) { 1561 BMCWEB_LOG_DEBUG << "OnDemand log available match fired"; 1562 boost::system::error_code ec; 1563 timeout.cancel(ec); 1564 if (ec) 1565 { 1566 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 1567 } 1568 sdbusplus::message::object_path objPath; 1569 boost::container::flat_map< 1570 std::string, boost::container::flat_map< 1571 std::string, std::variant<std::string>>> 1572 interfacesAdded; 1573 m.read(objPath, interfacesAdded); 1574 const std::string *log = std::get_if<std::string>( 1575 &interfacesAdded[CrashdumpInterface]["Log"]); 1576 if (log == nullptr) 1577 { 1578 messages::internalError(asyncResp->res); 1579 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1580 // match object inside which this lambda is executing. Once it 1581 // is set to nullptr, the match object will be destroyed and the 1582 // lambda will lose its context, including res, so it needs to 1583 // be the last thing done. 1584 onDemandLogMatcher = nullptr; 1585 return; 1586 } 1587 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1588 if (j.is_discarded()) 1589 { 1590 messages::internalError(asyncResp->res); 1591 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1592 // match object inside which this lambda is executing. Once it 1593 // is set to nullptr, the match object will be destroyed and the 1594 // lambda will lose its context, including res, so it needs to 1595 // be the last thing done. 1596 onDemandLogMatcher = nullptr; 1597 return; 1598 } 1599 std::string t = getLogCreatedTime(j); 1600 asyncResp->res.jsonValue = { 1601 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1602 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1603 {"Name", "CPU Crashdump"}, 1604 {"EntryType", "Oem"}, 1605 {"OemRecordFormat", "Intel Crashdump"}, 1606 {"Oem", {{"Intel", std::move(j)}}}, 1607 {"Created", std::move(t)}}; 1608 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1609 // match object inside which this lambda is executing. Once it is 1610 // set to nullptr, the match object will be destroyed and the lambda 1611 // will lose its context, including res, so it needs to be the last 1612 // thing done. 1613 onDemandLogMatcher = nullptr; 1614 }; 1615 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1616 *crow::connections::systemBus, 1617 sdbusplus::bus::match::rules::interfacesAdded() + 1618 sdbusplus::bus::match::rules::argNpath(0, 1619 CrashdumpOnDemandPath), 1620 std::move(onDemandLogMatcherCallback)); 1621 1622 auto generateonDemandLogCallback = 1623 [asyncResp](const boost::system::error_code ec, 1624 const std::string &resp) { 1625 if (ec) 1626 { 1627 if (ec.value() == 1628 boost::system::errc::operation_not_supported) 1629 { 1630 messages::resourceInStandby(asyncResp->res); 1631 } 1632 else 1633 { 1634 messages::internalError(asyncResp->res); 1635 } 1636 boost::system::error_code timeoutec; 1637 timeout.cancel(timeoutec); 1638 if (timeoutec) 1639 { 1640 BMCWEB_LOG_ERROR << "error canceling timer " 1641 << timeoutec; 1642 } 1643 onDemandLogMatcher = nullptr; 1644 return; 1645 } 1646 }; 1647 crow::connections::systemBus->async_method_call( 1648 std::move(generateonDemandLogCallback), CrashdumpObject, 1649 CrashdumpPath, CrashdumpOnDemandInterface, "GenerateOnDemandLog"); 1650 } 1651 }; 1652 1653 class SendRawPECI : public Node 1654 { 1655 public: 1656 SendRawPECI(CrowApp &app) : 1657 Node(app, 1658 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1659 "Crashdump.SendRawPeci/") 1660 { 1661 entityPrivileges = { 1662 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1663 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1664 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1665 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1666 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1667 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1668 } 1669 1670 private: 1671 void doPost(crow::Response &res, const crow::Request &req, 1672 const std::vector<std::string> ¶ms) override 1673 { 1674 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1675 uint8_t clientAddress = 0; 1676 uint8_t readLength = 0; 1677 std::vector<uint8_t> peciCommand; 1678 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1679 "ReadLength", readLength, "PECICommand", 1680 peciCommand)) 1681 { 1682 return; 1683 } 1684 1685 // Callback to return the Raw PECI response 1686 auto sendRawPECICallback = 1687 [asyncResp](const boost::system::error_code ec, 1688 const std::vector<uint8_t> &resp) { 1689 if (ec) 1690 { 1691 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1692 << ec.message(); 1693 messages::internalError(asyncResp->res); 1694 return; 1695 } 1696 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1697 {"PECIResponse", resp}}; 1698 }; 1699 // Call the SendRawPECI command with the provided data 1700 crow::connections::systemBus->async_method_call( 1701 std::move(sendRawPECICallback), CrashdumpObject, CrashdumpPath, 1702 CrashdumpRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1703 peciCommand); 1704 } 1705 }; 1706 1707 /** 1708 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 1709 */ 1710 class DBusLogServiceActionsClear : public Node 1711 { 1712 public: 1713 DBusLogServiceActionsClear(CrowApp &app) : 1714 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 1715 "LogService.Reset") 1716 { 1717 entityPrivileges = { 1718 {boost::beast::http::verb::get, {{"Login"}}}, 1719 {boost::beast::http::verb::head, {{"Login"}}}, 1720 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1721 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1722 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1723 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1724 } 1725 1726 private: 1727 /** 1728 * Function handles POST method request. 1729 * The Clear Log actions does not require any parameter.The action deletes 1730 * all entries found in the Entries collection for this Log Service. 1731 */ 1732 void doPost(crow::Response &res, const crow::Request &req, 1733 const std::vector<std::string> ¶ms) override 1734 { 1735 BMCWEB_LOG_DEBUG << "Do delete all entries."; 1736 1737 auto asyncResp = std::make_shared<AsyncResp>(res); 1738 // Process response from Logging service. 1739 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 1740 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 1741 if (ec) 1742 { 1743 // TODO Handle for specific error code 1744 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 1745 asyncResp->res.result( 1746 boost::beast::http::status::internal_server_error); 1747 return; 1748 } 1749 1750 asyncResp->res.result(boost::beast::http::status::no_content); 1751 }; 1752 1753 // Make call to Logging service to request Clear Log 1754 crow::connections::systemBus->async_method_call( 1755 resp_handler, "xyz.openbmc_project.Logging", 1756 "/xyz/openbmc_project/logging", 1757 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 1758 } 1759 }; 1760 } // namespace redfish 1761