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