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