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