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