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 #include "task.hpp" 23 24 #include <systemd/sd-journal.h> 25 26 #include <boost/algorithm/string/split.hpp> 27 #include <boost/beast/core/span.hpp> 28 #include <boost/container/flat_map.hpp> 29 #include <boost/system/linux_error.hpp> 30 #include <error_messages.hpp> 31 #include <filesystem> 32 #include <string_view> 33 #include <variant> 34 35 namespace redfish 36 { 37 38 constexpr char const *crashdumpObject = "com.intel.crashdump"; 39 constexpr char const *crashdumpPath = "/com/intel/crashdump"; 40 constexpr char const *crashdumpOnDemandPath = "/com/intel/crashdump/OnDemand"; 41 constexpr char const *crashdumpInterface = "com.intel.crashdump"; 42 constexpr char const *deleteAllInterface = 43 "xyz.openbmc_project.Collection.DeleteAll"; 44 constexpr char const *crashdumpOnDemandInterface = 45 "com.intel.crashdump.OnDemand"; 46 constexpr char const *crashdumpRawPECIInterface = 47 "com.intel.crashdump.SendRawPeci"; 48 49 namespace message_registries 50 { 51 static const Message *getMessageFromRegistry( 52 const std::string &messageKey, 53 const boost::beast::span<const MessageEntry> registry) 54 { 55 boost::beast::span<const MessageEntry>::const_iterator messageIt = 56 std::find_if(registry.cbegin(), registry.cend(), 57 [&messageKey](const MessageEntry &messageEntry) { 58 return !std::strcmp(messageEntry.first, 59 messageKey.c_str()); 60 }); 61 if (messageIt != registry.cend()) 62 { 63 return &messageIt->second; 64 } 65 66 return nullptr; 67 } 68 69 static const Message *getMessage(const std::string_view &messageID) 70 { 71 // Redfish MessageIds are in the form 72 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 73 // the right Message 74 std::vector<std::string> fields; 75 fields.reserve(4); 76 boost::split(fields, messageID, boost::is_any_of(".")); 77 std::string ®istryName = fields[0]; 78 std::string &messageKey = fields[3]; 79 80 // Find the right registry and check it for the MessageKey 81 if (std::string(base::header.registryPrefix) == registryName) 82 { 83 return getMessageFromRegistry( 84 messageKey, boost::beast::span<const MessageEntry>(base::registry)); 85 } 86 if (std::string(openbmc::header.registryPrefix) == registryName) 87 { 88 return getMessageFromRegistry( 89 messageKey, 90 boost::beast::span<const MessageEntry>(openbmc::registry)); 91 } 92 return nullptr; 93 } 94 } // namespace message_registries 95 96 namespace fs = std::filesystem; 97 98 using GetManagedPropertyType = boost::container::flat_map< 99 std::string, 100 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 101 int32_t, uint32_t, int64_t, uint64_t, double>>; 102 103 using GetManagedObjectsType = boost::container::flat_map< 104 sdbusplus::message::object_path, 105 boost::container::flat_map<std::string, GetManagedPropertyType>>; 106 107 inline std::string translateSeverityDbusToRedfish(const std::string &s) 108 { 109 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") 110 { 111 return "Critical"; 112 } 113 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") 114 { 115 return "Critical"; 116 } 117 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") 118 { 119 return "OK"; 120 } 121 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") 122 { 123 return "Critical"; 124 } 125 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") 126 { 127 return "Critical"; 128 } 129 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") 130 { 131 return "OK"; 132 } 133 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") 134 { 135 return "OK"; 136 } 137 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 138 { 139 return "Warning"; 140 } 141 return ""; 142 } 143 144 static int getJournalMetadata(sd_journal *journal, 145 const std::string_view &field, 146 std::string_view &contents) 147 { 148 const char *data = nullptr; 149 size_t length = 0; 150 int ret = 0; 151 // Get the metadata from the requested field of the journal entry 152 ret = sd_journal_get_data(journal, field.data(), 153 reinterpret_cast<const void **>(&data), &length); 154 if (ret < 0) 155 { 156 return ret; 157 } 158 contents = std::string_view(data, length); 159 // Only use the content after the "=" character. 160 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 161 return ret; 162 } 163 164 static int getJournalMetadata(sd_journal *journal, 165 const std::string_view &field, const int &base, 166 long int &contents) 167 { 168 int ret = 0; 169 std::string_view metadata; 170 // Get the metadata from the requested field of the journal entry 171 ret = getJournalMetadata(journal, field, metadata); 172 if (ret < 0) 173 { 174 return ret; 175 } 176 contents = strtol(metadata.data(), nullptr, base); 177 return ret; 178 } 179 180 static bool getTimestampStr(const uint64_t usecSinceEpoch, 181 std::string &entryTimestamp) 182 { 183 time_t t = static_cast<time_t>(usecSinceEpoch / 1000 / 1000); 184 struct tm *loctime = localtime(&t); 185 char entryTime[64] = {}; 186 if (nullptr != loctime) 187 { 188 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 189 } 190 // Insert the ':' into the timezone 191 std::string_view t1(entryTime); 192 std::string_view t2(entryTime); 193 if (t1.size() > 2 && t2.size() > 2) 194 { 195 t1.remove_suffix(2); 196 t2.remove_prefix(t2.size() - 2); 197 } 198 entryTimestamp = std::string(t1) + ":" + std::string(t2); 199 return true; 200 } 201 202 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 203 { 204 int ret = 0; 205 uint64_t timestamp = 0; 206 ret = sd_journal_get_realtime_usec(journal, ×tamp); 207 if (ret < 0) 208 { 209 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 210 << strerror(-ret); 211 return false; 212 } 213 return getTimestampStr(timestamp, entryTimestamp); 214 } 215 216 static bool getSkipParam(crow::Response &res, const crow::Request &req, 217 uint64_t &skip) 218 { 219 char *skipParam = req.urlParams.get("$skip"); 220 if (skipParam != nullptr) 221 { 222 char *ptr = nullptr; 223 skip = std::strtoul(skipParam, &ptr, 10); 224 if (*skipParam == '\0' || *ptr != '\0') 225 { 226 227 messages::queryParameterValueTypeError(res, std::string(skipParam), 228 "$skip"); 229 return false; 230 } 231 } 232 return true; 233 } 234 235 static constexpr const uint64_t maxEntriesPerPage = 1000; 236 static bool getTopParam(crow::Response &res, const crow::Request &req, 237 uint64_t &top) 238 { 239 char *topParam = req.urlParams.get("$top"); 240 if (topParam != nullptr) 241 { 242 char *ptr = nullptr; 243 top = std::strtoul(topParam, &ptr, 10); 244 if (*topParam == '\0' || *ptr != '\0') 245 { 246 messages::queryParameterValueTypeError(res, std::string(topParam), 247 "$top"); 248 return false; 249 } 250 if (top < 1U || top > maxEntriesPerPage) 251 { 252 253 messages::queryParameterOutOfRange( 254 res, std::to_string(top), "$top", 255 "1-" + std::to_string(maxEntriesPerPage)); 256 return false; 257 } 258 } 259 return true; 260 } 261 262 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID, 263 const bool firstEntry = true) 264 { 265 int ret = 0; 266 static uint64_t prevTs = 0; 267 static int index = 0; 268 if (firstEntry) 269 { 270 prevTs = 0; 271 } 272 273 // Get the entry timestamp 274 uint64_t curTs = 0; 275 ret = sd_journal_get_realtime_usec(journal, &curTs); 276 if (ret < 0) 277 { 278 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 279 << strerror(-ret); 280 return false; 281 } 282 // If the timestamp isn't unique, increment the index 283 if (curTs == prevTs) 284 { 285 index++; 286 } 287 else 288 { 289 // Otherwise, reset it 290 index = 0; 291 } 292 // Save the timestamp 293 prevTs = curTs; 294 295 entryID = std::to_string(curTs); 296 if (index > 0) 297 { 298 entryID += "_" + std::to_string(index); 299 } 300 return true; 301 } 302 303 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID, 304 const bool firstEntry = true) 305 { 306 static time_t prevTs = 0; 307 static int index = 0; 308 if (firstEntry) 309 { 310 prevTs = 0; 311 } 312 313 // Get the entry timestamp 314 std::time_t curTs = 0; 315 std::tm timeStruct = {}; 316 std::istringstream entryStream(logEntry); 317 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 318 { 319 curTs = std::mktime(&timeStruct); 320 } 321 // If the timestamp isn't unique, increment the index 322 if (curTs == prevTs) 323 { 324 index++; 325 } 326 else 327 { 328 // Otherwise, reset it 329 index = 0; 330 } 331 // Save the timestamp 332 prevTs = curTs; 333 334 entryID = std::to_string(curTs); 335 if (index > 0) 336 { 337 entryID += "_" + std::to_string(index); 338 } 339 return true; 340 } 341 342 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 343 uint64_t ×tamp, uint64_t &index) 344 { 345 if (entryID.empty()) 346 { 347 return false; 348 } 349 // Convert the unique ID back to a timestamp to find the entry 350 std::string_view tsStr(entryID); 351 352 auto underscorePos = tsStr.find("_"); 353 if (underscorePos != tsStr.npos) 354 { 355 // Timestamp has an index 356 tsStr.remove_suffix(tsStr.size() - underscorePos); 357 std::string_view indexStr(entryID); 358 indexStr.remove_prefix(underscorePos + 1); 359 std::size_t pos; 360 try 361 { 362 index = std::stoul(std::string(indexStr), &pos); 363 } 364 catch (std::invalid_argument &) 365 { 366 messages::resourceMissingAtURI(res, entryID); 367 return false; 368 } 369 catch (std::out_of_range &) 370 { 371 messages::resourceMissingAtURI(res, entryID); 372 return false; 373 } 374 if (pos != indexStr.size()) 375 { 376 messages::resourceMissingAtURI(res, entryID); 377 return false; 378 } 379 } 380 // Timestamp has no index 381 std::size_t pos; 382 try 383 { 384 timestamp = std::stoull(std::string(tsStr), &pos); 385 } 386 catch (std::invalid_argument &) 387 { 388 messages::resourceMissingAtURI(res, entryID); 389 return false; 390 } 391 catch (std::out_of_range &) 392 { 393 messages::resourceMissingAtURI(res, entryID); 394 return false; 395 } 396 if (pos != tsStr.size()) 397 { 398 messages::resourceMissingAtURI(res, entryID); 399 return false; 400 } 401 return true; 402 } 403 404 static bool 405 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles) 406 { 407 static const std::filesystem::path redfishLogDir = "/var/log"; 408 static const std::string redfishLogFilename = "redfish"; 409 410 // Loop through the directory looking for redfish log files 411 for (const std::filesystem::directory_entry &dirEnt : 412 std::filesystem::directory_iterator(redfishLogDir)) 413 { 414 // If we find a redfish log file, save the path 415 std::string filename = dirEnt.path().filename(); 416 if (boost::starts_with(filename, redfishLogFilename)) 417 { 418 redfishLogFiles.emplace_back(redfishLogDir / filename); 419 } 420 } 421 // As the log files rotate, they are appended with a ".#" that is higher for 422 // the older logs. Since we don't expect more than 10 log files, we 423 // can just sort the list to get them in order from newest to oldest 424 std::sort(redfishLogFiles.begin(), redfishLogFiles.end()); 425 426 return !redfishLogFiles.empty(); 427 } 428 429 constexpr char const *postCodeIface = "xyz.openbmc_project.State.Boot.PostCode"; 430 class SystemLogServiceCollection : public Node 431 { 432 public: 433 template <typename CrowApp> 434 SystemLogServiceCollection(CrowApp &app) : 435 Node(app, "/redfish/v1/Systems/system/LogServices/") 436 { 437 entityPrivileges = { 438 {boost::beast::http::verb::get, {{"Login"}}}, 439 {boost::beast::http::verb::head, {{"Login"}}}, 440 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 441 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 442 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 443 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 444 } 445 446 private: 447 /** 448 * Functions triggers appropriate requests on DBus 449 */ 450 void doGet(crow::Response &res, const crow::Request &req, 451 const std::vector<std::string> ¶ms) override 452 { 453 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 454 // Collections don't include the static data added by SubRoute because 455 // it has a duplicate entry for members 456 asyncResp->res.jsonValue["@odata.type"] = 457 "#LogServiceCollection.LogServiceCollection"; 458 asyncResp->res.jsonValue["@odata.id"] = 459 "/redfish/v1/Systems/system/LogServices"; 460 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 461 asyncResp->res.jsonValue["Description"] = 462 "Collection of LogServices for this Computer System"; 463 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 464 logServiceArray = nlohmann::json::array(); 465 logServiceArray.push_back( 466 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 467 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 468 logServiceArray.push_back( 469 {{"@odata.id", 470 "/redfish/v1/Systems/system/LogServices/Crashdump"}}); 471 #endif 472 asyncResp->res.jsonValue["Members@odata.count"] = 473 logServiceArray.size(); 474 475 crow::connections::systemBus->async_method_call( 476 [asyncResp](const boost::system::error_code ec, 477 const std::vector<std::string> &subtreePath) { 478 if (ec) 479 { 480 BMCWEB_LOG_ERROR << ec; 481 return; 482 } 483 484 for (auto &pathStr : subtreePath) 485 { 486 if (pathStr.find("PostCode") != std::string::npos) 487 { 488 nlohmann::json &logServiceArray = 489 asyncResp->res.jsonValue["Members"]; 490 logServiceArray.push_back( 491 {{"@odata.id", "/redfish/v1/Systems/system/" 492 "LogServices/PostCodes"}}); 493 asyncResp->res.jsonValue["Members@odata.count"] = 494 logServiceArray.size(); 495 return; 496 } 497 } 498 }, 499 "xyz.openbmc_project.ObjectMapper", 500 "/xyz/openbmc_project/object_mapper", 501 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 0, 502 std::array<const char *, 1>{postCodeIface}); 503 } 504 }; 505 506 class EventLogService : public Node 507 { 508 public: 509 template <typename CrowApp> 510 EventLogService(CrowApp &app) : 511 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 512 { 513 entityPrivileges = { 514 {boost::beast::http::verb::get, {{"Login"}}}, 515 {boost::beast::http::verb::head, {{"Login"}}}, 516 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 517 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 518 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 519 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 520 } 521 522 private: 523 void doGet(crow::Response &res, const crow::Request &req, 524 const std::vector<std::string> ¶ms) override 525 { 526 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 527 528 asyncResp->res.jsonValue["@odata.id"] = 529 "/redfish/v1/Systems/system/LogServices/EventLog"; 530 asyncResp->res.jsonValue["@odata.type"] = 531 "#LogService.v1_1_0.LogService"; 532 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 533 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 534 asyncResp->res.jsonValue["Id"] = "Event Log"; 535 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 536 asyncResp->res.jsonValue["Entries"] = { 537 {"@odata.id", 538 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 539 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 540 541 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 542 "Actions/LogService.ClearLog"}}; 543 } 544 }; 545 546 class JournalEventLogClear : public Node 547 { 548 public: 549 JournalEventLogClear(CrowApp &app) : 550 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 551 "LogService.ClearLog/") 552 { 553 entityPrivileges = { 554 {boost::beast::http::verb::get, {{"Login"}}}, 555 {boost::beast::http::verb::head, {{"Login"}}}, 556 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 557 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 558 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 559 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 560 } 561 562 private: 563 void doPost(crow::Response &res, const crow::Request &req, 564 const std::vector<std::string> ¶ms) override 565 { 566 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 567 568 // Clear the EventLog by deleting the log files 569 std::vector<std::filesystem::path> redfishLogFiles; 570 if (getRedfishLogFiles(redfishLogFiles)) 571 { 572 for (const std::filesystem::path &file : redfishLogFiles) 573 { 574 std::error_code ec; 575 std::filesystem::remove(file, ec); 576 } 577 } 578 579 // Reload rsyslog so it knows to start new log files 580 crow::connections::systemBus->async_method_call( 581 [asyncResp](const boost::system::error_code ec) { 582 if (ec) 583 { 584 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec; 585 messages::internalError(asyncResp->res); 586 return; 587 } 588 589 messages::success(asyncResp->res); 590 }, 591 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 592 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 593 "replace"); 594 } 595 }; 596 597 static int fillEventLogEntryJson(const std::string &logEntryID, 598 const std::string logEntry, 599 nlohmann::json &logEntryJson) 600 { 601 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 602 // First get the Timestamp 603 size_t space = logEntry.find_first_of(" "); 604 if (space == std::string::npos) 605 { 606 return 1; 607 } 608 std::string timestamp = logEntry.substr(0, space); 609 // Then get the log contents 610 size_t entryStart = logEntry.find_first_not_of(" ", space); 611 if (entryStart == std::string::npos) 612 { 613 return 1; 614 } 615 std::string_view entry(logEntry); 616 entry.remove_prefix(entryStart); 617 // Use split to separate the entry into its fields 618 std::vector<std::string> logEntryFields; 619 boost::split(logEntryFields, entry, boost::is_any_of(","), 620 boost::token_compress_on); 621 // We need at least a MessageId to be valid 622 if (logEntryFields.size() < 1) 623 { 624 return 1; 625 } 626 std::string &messageID = logEntryFields[0]; 627 628 // Get the Message from the MessageRegistry 629 const message_registries::Message *message = 630 message_registries::getMessage(messageID); 631 632 std::string msg; 633 std::string severity; 634 if (message != nullptr) 635 { 636 msg = message->message; 637 severity = message->severity; 638 } 639 640 // Get the MessageArgs from the log if there are any 641 boost::beast::span<std::string> messageArgs; 642 if (logEntryFields.size() > 1) 643 { 644 std::string &messageArgsStart = logEntryFields[1]; 645 // If the first string is empty, assume there are no MessageArgs 646 std::size_t messageArgsSize = 0; 647 if (!messageArgsStart.empty()) 648 { 649 messageArgsSize = logEntryFields.size() - 1; 650 } 651 652 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize); 653 654 // Fill the MessageArgs into the Message 655 int i = 0; 656 for (const std::string &messageArg : messageArgs) 657 { 658 std::string argStr = "%" + std::to_string(++i); 659 size_t argPos = msg.find(argStr); 660 if (argPos != std::string::npos) 661 { 662 msg.replace(argPos, argStr.length(), messageArg); 663 } 664 } 665 } 666 667 // Get the Created time from the timestamp. The log timestamp is in RFC3339 668 // format which matches the Redfish format except for the fractional seconds 669 // between the '.' and the '+', so just remove them. 670 std::size_t dot = timestamp.find_first_of("."); 671 std::size_t plus = timestamp.find_first_of("+"); 672 if (dot != std::string::npos && plus != std::string::npos) 673 { 674 timestamp.erase(dot, plus - dot); 675 } 676 677 // Fill in the log entry with the gathered data 678 logEntryJson = { 679 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 680 {"@odata.id", 681 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 682 logEntryID}, 683 {"Name", "System Event Log Entry"}, 684 {"Id", logEntryID}, 685 {"Message", std::move(msg)}, 686 {"MessageId", std::move(messageID)}, 687 {"MessageArgs", std::move(messageArgs)}, 688 {"EntryType", "Event"}, 689 {"Severity", std::move(severity)}, 690 {"Created", std::move(timestamp)}}; 691 return 0; 692 } 693 694 class JournalEventLogEntryCollection : public Node 695 { 696 public: 697 template <typename CrowApp> 698 JournalEventLogEntryCollection(CrowApp &app) : 699 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 700 { 701 entityPrivileges = { 702 {boost::beast::http::verb::get, {{"Login"}}}, 703 {boost::beast::http::verb::head, {{"Login"}}}, 704 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 705 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 706 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 707 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 708 } 709 710 private: 711 void doGet(crow::Response &res, const crow::Request &req, 712 const std::vector<std::string> ¶ms) override 713 { 714 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 715 uint64_t skip = 0; 716 uint64_t top = maxEntriesPerPage; // Show max entries by default 717 if (!getSkipParam(asyncResp->res, req, skip)) 718 { 719 return; 720 } 721 if (!getTopParam(asyncResp->res, req, top)) 722 { 723 return; 724 } 725 // Collections don't include the static data added by SubRoute because 726 // it has a duplicate entry for members 727 asyncResp->res.jsonValue["@odata.type"] = 728 "#LogEntryCollection.LogEntryCollection"; 729 asyncResp->res.jsonValue["@odata.id"] = 730 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 731 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 732 asyncResp->res.jsonValue["Description"] = 733 "Collection of System Event Log Entries"; 734 735 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 736 logEntryArray = nlohmann::json::array(); 737 // Go through the log files and create a unique ID for each entry 738 std::vector<std::filesystem::path> redfishLogFiles; 739 getRedfishLogFiles(redfishLogFiles); 740 uint64_t entryCount = 0; 741 std::string logEntry; 742 743 // Oldest logs are in the last file, so start there and loop backwards 744 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 745 it++) 746 { 747 std::ifstream logStream(*it); 748 if (!logStream.is_open()) 749 { 750 continue; 751 } 752 753 // Reset the unique ID on the first entry 754 bool firstEntry = true; 755 while (std::getline(logStream, logEntry)) 756 { 757 entryCount++; 758 // Handle paging using skip (number of entries to skip from the 759 // start) and top (number of entries to display) 760 if (entryCount <= skip || entryCount > skip + top) 761 { 762 continue; 763 } 764 765 std::string idStr; 766 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 767 { 768 continue; 769 } 770 771 if (firstEntry) 772 { 773 firstEntry = false; 774 } 775 776 logEntryArray.push_back({}); 777 nlohmann::json &bmcLogEntry = logEntryArray.back(); 778 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 779 { 780 messages::internalError(asyncResp->res); 781 return; 782 } 783 } 784 } 785 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 786 if (skip + top < entryCount) 787 { 788 asyncResp->res.jsonValue["Members@odata.nextLink"] = 789 "/redfish/v1/Systems/system/LogServices/EventLog/" 790 "Entries?$skip=" + 791 std::to_string(skip + top); 792 } 793 } 794 }; 795 796 class JournalEventLogEntry : public Node 797 { 798 public: 799 JournalEventLogEntry(CrowApp &app) : 800 Node(app, 801 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 802 std::string()) 803 { 804 entityPrivileges = { 805 {boost::beast::http::verb::get, {{"Login"}}}, 806 {boost::beast::http::verb::head, {{"Login"}}}, 807 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 808 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 809 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 810 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 811 } 812 813 private: 814 void doGet(crow::Response &res, const crow::Request &req, 815 const std::vector<std::string> ¶ms) override 816 { 817 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 818 if (params.size() != 1) 819 { 820 messages::internalError(asyncResp->res); 821 return; 822 } 823 const std::string &targetID = params[0]; 824 825 // Go through the log files and check the unique ID for each entry to 826 // find the target entry 827 std::vector<std::filesystem::path> redfishLogFiles; 828 getRedfishLogFiles(redfishLogFiles); 829 std::string logEntry; 830 831 // Oldest logs are in the last file, so start there and loop backwards 832 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 833 it++) 834 { 835 std::ifstream logStream(*it); 836 if (!logStream.is_open()) 837 { 838 continue; 839 } 840 841 // Reset the unique ID on the first entry 842 bool firstEntry = true; 843 while (std::getline(logStream, logEntry)) 844 { 845 std::string idStr; 846 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 847 { 848 continue; 849 } 850 851 if (firstEntry) 852 { 853 firstEntry = false; 854 } 855 856 if (idStr == targetID) 857 { 858 if (fillEventLogEntryJson(idStr, logEntry, 859 asyncResp->res.jsonValue) != 0) 860 { 861 messages::internalError(asyncResp->res); 862 return; 863 } 864 return; 865 } 866 } 867 } 868 // Requested ID was not found 869 messages::resourceMissingAtURI(asyncResp->res, targetID); 870 } 871 }; 872 873 class DBusEventLogEntryCollection : public Node 874 { 875 public: 876 template <typename CrowApp> 877 DBusEventLogEntryCollection(CrowApp &app) : 878 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 879 { 880 entityPrivileges = { 881 {boost::beast::http::verb::get, {{"Login"}}}, 882 {boost::beast::http::verb::head, {{"Login"}}}, 883 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 884 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 885 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 886 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 887 } 888 889 private: 890 void doGet(crow::Response &res, const crow::Request &req, 891 const std::vector<std::string> ¶ms) override 892 { 893 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 894 895 // Collections don't include the static data added by SubRoute because 896 // it has a duplicate entry for members 897 asyncResp->res.jsonValue["@odata.type"] = 898 "#LogEntryCollection.LogEntryCollection"; 899 asyncResp->res.jsonValue["@odata.id"] = 900 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 901 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 902 asyncResp->res.jsonValue["Description"] = 903 "Collection of System Event Log Entries"; 904 905 // DBus implementation of EventLog/Entries 906 // Make call to Logging Service to find all log entry objects 907 crow::connections::systemBus->async_method_call( 908 [asyncResp](const boost::system::error_code ec, 909 GetManagedObjectsType &resp) { 910 if (ec) 911 { 912 // TODO Handle for specific error code 913 BMCWEB_LOG_ERROR 914 << "getLogEntriesIfaceData resp_handler got error " 915 << ec; 916 messages::internalError(asyncResp->res); 917 return; 918 } 919 nlohmann::json &entriesArray = 920 asyncResp->res.jsonValue["Members"]; 921 entriesArray = nlohmann::json::array(); 922 for (auto &objectPath : resp) 923 { 924 for (auto &interfaceMap : objectPath.second) 925 { 926 if (interfaceMap.first != 927 "xyz.openbmc_project.Logging.Entry") 928 { 929 BMCWEB_LOG_DEBUG << "Bailing early on " 930 << interfaceMap.first; 931 continue; 932 } 933 entriesArray.push_back({}); 934 nlohmann::json &thisEntry = entriesArray.back(); 935 uint32_t *id = nullptr; 936 std::time_t timestamp{}; 937 std::string *severity = nullptr; 938 std::string *message = nullptr; 939 for (auto &propertyMap : interfaceMap.second) 940 { 941 if (propertyMap.first == "Id") 942 { 943 id = sdbusplus::message::variant_ns::get_if< 944 uint32_t>(&propertyMap.second); 945 if (id == nullptr) 946 { 947 messages::propertyMissing(asyncResp->res, 948 "Id"); 949 } 950 } 951 else if (propertyMap.first == "Timestamp") 952 { 953 const uint64_t *millisTimeStamp = 954 std::get_if<uint64_t>(&propertyMap.second); 955 if (millisTimeStamp == nullptr) 956 { 957 messages::propertyMissing(asyncResp->res, 958 "Timestamp"); 959 continue; 960 } 961 // Retrieve Created property with format: 962 // yyyy-mm-ddThh:mm:ss 963 std::chrono::milliseconds chronoTimeStamp( 964 *millisTimeStamp); 965 timestamp = std::chrono::duration_cast< 966 std::chrono::duration<int>>( 967 chronoTimeStamp) 968 .count(); 969 } 970 else if (propertyMap.first == "Severity") 971 { 972 severity = std::get_if<std::string>( 973 &propertyMap.second); 974 if (severity == nullptr) 975 { 976 messages::propertyMissing(asyncResp->res, 977 "Severity"); 978 } 979 } 980 else if (propertyMap.first == "Message") 981 { 982 message = std::get_if<std::string>( 983 &propertyMap.second); 984 if (message == nullptr) 985 { 986 messages::propertyMissing(asyncResp->res, 987 "Message"); 988 } 989 } 990 } 991 thisEntry = { 992 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 993 {"@odata.id", 994 "/redfish/v1/Systems/system/LogServices/EventLog/" 995 "Entries/" + 996 std::to_string(*id)}, 997 {"Name", "System Event Log Entry"}, 998 {"Id", std::to_string(*id)}, 999 {"Message", *message}, 1000 {"EntryType", "Event"}, 1001 {"Severity", 1002 translateSeverityDbusToRedfish(*severity)}, 1003 {"Created", crow::utility::getDateTime(timestamp)}}; 1004 } 1005 } 1006 std::sort(entriesArray.begin(), entriesArray.end(), 1007 [](const nlohmann::json &left, 1008 const nlohmann::json &right) { 1009 return (left["Id"] <= right["Id"]); 1010 }); 1011 asyncResp->res.jsonValue["Members@odata.count"] = 1012 entriesArray.size(); 1013 }, 1014 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 1015 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1016 } 1017 }; 1018 1019 class DBusEventLogEntry : public Node 1020 { 1021 public: 1022 DBusEventLogEntry(CrowApp &app) : 1023 Node(app, 1024 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 1025 std::string()) 1026 { 1027 entityPrivileges = { 1028 {boost::beast::http::verb::get, {{"Login"}}}, 1029 {boost::beast::http::verb::head, {{"Login"}}}, 1030 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1031 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1032 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1033 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1034 } 1035 1036 private: 1037 void doGet(crow::Response &res, const crow::Request &req, 1038 const std::vector<std::string> ¶ms) override 1039 { 1040 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1041 if (params.size() != 1) 1042 { 1043 messages::internalError(asyncResp->res); 1044 return; 1045 } 1046 const std::string &entryID = params[0]; 1047 1048 // DBus implementation of EventLog/Entries 1049 // Make call to Logging Service to find all log entry objects 1050 crow::connections::systemBus->async_method_call( 1051 [asyncResp, entryID](const boost::system::error_code ec, 1052 GetManagedPropertyType &resp) { 1053 if (ec) 1054 { 1055 BMCWEB_LOG_ERROR 1056 << "EventLogEntry (DBus) resp_handler got error " << ec; 1057 messages::internalError(asyncResp->res); 1058 return; 1059 } 1060 uint32_t *id = nullptr; 1061 std::time_t timestamp{}; 1062 std::string *severity = nullptr; 1063 std::string *message = nullptr; 1064 for (auto &propertyMap : resp) 1065 { 1066 if (propertyMap.first == "Id") 1067 { 1068 id = std::get_if<uint32_t>(&propertyMap.second); 1069 if (id == nullptr) 1070 { 1071 messages::propertyMissing(asyncResp->res, "Id"); 1072 } 1073 } 1074 else if (propertyMap.first == "Timestamp") 1075 { 1076 const uint64_t *millisTimeStamp = 1077 std::get_if<uint64_t>(&propertyMap.second); 1078 if (millisTimeStamp == nullptr) 1079 { 1080 messages::propertyMissing(asyncResp->res, 1081 "Timestamp"); 1082 continue; 1083 } 1084 // Retrieve Created property with format: 1085 // yyyy-mm-ddThh:mm:ss 1086 std::chrono::milliseconds chronoTimeStamp( 1087 *millisTimeStamp); 1088 timestamp = 1089 std::chrono::duration_cast< 1090 std::chrono::duration<int>>(chronoTimeStamp) 1091 .count(); 1092 } 1093 else if (propertyMap.first == "Severity") 1094 { 1095 severity = 1096 std::get_if<std::string>(&propertyMap.second); 1097 if (severity == nullptr) 1098 { 1099 messages::propertyMissing(asyncResp->res, 1100 "Severity"); 1101 } 1102 } 1103 else if (propertyMap.first == "Message") 1104 { 1105 message = std::get_if<std::string>(&propertyMap.second); 1106 if (message == nullptr) 1107 { 1108 messages::propertyMissing(asyncResp->res, 1109 "Message"); 1110 } 1111 } 1112 } 1113 if (id == nullptr || message == nullptr || severity == nullptr) 1114 { 1115 return; 1116 } 1117 asyncResp->res.jsonValue = { 1118 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1119 {"@odata.id", 1120 "/redfish/v1/Systems/system/LogServices/EventLog/" 1121 "Entries/" + 1122 std::to_string(*id)}, 1123 {"Name", "System Event Log Entry"}, 1124 {"Id", std::to_string(*id)}, 1125 {"Message", *message}, 1126 {"EntryType", "Event"}, 1127 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1128 {"Created", crow::utility::getDateTime(timestamp)}}; 1129 }, 1130 "xyz.openbmc_project.Logging", 1131 "/xyz/openbmc_project/logging/entry/" + entryID, 1132 "org.freedesktop.DBus.Properties", "GetAll", 1133 "xyz.openbmc_project.Logging.Entry"); 1134 } 1135 1136 void doDelete(crow::Response &res, const crow::Request &req, 1137 const std::vector<std::string> ¶ms) override 1138 { 1139 1140 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1141 1142 auto asyncResp = std::make_shared<AsyncResp>(res); 1143 1144 if (params.size() != 1) 1145 { 1146 messages::internalError(asyncResp->res); 1147 return; 1148 } 1149 std::string entryID = params[0]; 1150 1151 dbus::utility::escapePathForDbus(entryID); 1152 1153 // Process response from Logging service. 1154 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1155 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1156 if (ec) 1157 { 1158 // TODO Handle for specific error code 1159 BMCWEB_LOG_ERROR 1160 << "EventLogEntry (DBus) doDelete respHandler got error " 1161 << ec; 1162 asyncResp->res.result( 1163 boost::beast::http::status::internal_server_error); 1164 return; 1165 } 1166 1167 asyncResp->res.result(boost::beast::http::status::ok); 1168 }; 1169 1170 // Make call to Logging service to request Delete Log 1171 crow::connections::systemBus->async_method_call( 1172 respHandler, "xyz.openbmc_project.Logging", 1173 "/xyz/openbmc_project/logging/entry/" + entryID, 1174 "xyz.openbmc_project.Object.Delete", "Delete"); 1175 } 1176 }; 1177 1178 class BMCLogServiceCollection : public Node 1179 { 1180 public: 1181 template <typename CrowApp> 1182 BMCLogServiceCollection(CrowApp &app) : 1183 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1184 { 1185 entityPrivileges = { 1186 {boost::beast::http::verb::get, {{"Login"}}}, 1187 {boost::beast::http::verb::head, {{"Login"}}}, 1188 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1189 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1190 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1191 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1192 } 1193 1194 private: 1195 /** 1196 * Functions triggers appropriate requests on DBus 1197 */ 1198 void doGet(crow::Response &res, const crow::Request &req, 1199 const std::vector<std::string> ¶ms) override 1200 { 1201 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1202 // Collections don't include the static data added by SubRoute because 1203 // it has a duplicate entry for members 1204 asyncResp->res.jsonValue["@odata.type"] = 1205 "#LogServiceCollection.LogServiceCollection"; 1206 asyncResp->res.jsonValue["@odata.id"] = 1207 "/redfish/v1/Managers/bmc/LogServices"; 1208 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1209 asyncResp->res.jsonValue["Description"] = 1210 "Collection of LogServices for this Manager"; 1211 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 1212 logServiceArray = nlohmann::json::array(); 1213 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1214 logServiceArray.push_back( 1215 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1216 #endif 1217 asyncResp->res.jsonValue["Members@odata.count"] = 1218 logServiceArray.size(); 1219 } 1220 }; 1221 1222 class BMCJournalLogService : public Node 1223 { 1224 public: 1225 template <typename CrowApp> 1226 BMCJournalLogService(CrowApp &app) : 1227 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1228 { 1229 entityPrivileges = { 1230 {boost::beast::http::verb::get, {{"Login"}}}, 1231 {boost::beast::http::verb::head, {{"Login"}}}, 1232 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1233 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1234 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1235 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1236 } 1237 1238 private: 1239 void doGet(crow::Response &res, const crow::Request &req, 1240 const std::vector<std::string> ¶ms) override 1241 { 1242 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1243 asyncResp->res.jsonValue["@odata.type"] = 1244 "#LogService.v1_1_0.LogService"; 1245 asyncResp->res.jsonValue["@odata.id"] = 1246 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1247 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1248 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1249 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1250 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1251 asyncResp->res.jsonValue["Entries"] = { 1252 {"@odata.id", 1253 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1254 } 1255 }; 1256 1257 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 1258 sd_journal *journal, 1259 nlohmann::json &bmcJournalLogEntryJson) 1260 { 1261 // Get the Log Entry contents 1262 int ret = 0; 1263 1264 std::string_view msg; 1265 ret = getJournalMetadata(journal, "MESSAGE", msg); 1266 if (ret < 0) 1267 { 1268 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1269 return 1; 1270 } 1271 1272 // Get the severity from the PRIORITY field 1273 long int severity = 8; // Default to an invalid priority 1274 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1275 if (ret < 0) 1276 { 1277 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1278 } 1279 1280 // Get the Created time from the timestamp 1281 std::string entryTimeStr; 1282 if (!getEntryTimestamp(journal, entryTimeStr)) 1283 { 1284 return 1; 1285 } 1286 1287 // Fill in the log entry with the gathered data 1288 bmcJournalLogEntryJson = { 1289 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1290 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1291 bmcJournalLogEntryID}, 1292 {"Name", "BMC Journal Entry"}, 1293 {"Id", bmcJournalLogEntryID}, 1294 {"Message", msg}, 1295 {"EntryType", "Oem"}, 1296 {"Severity", 1297 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1298 {"OemRecordFormat", "BMC Journal Entry"}, 1299 {"Created", std::move(entryTimeStr)}}; 1300 return 0; 1301 } 1302 1303 class BMCJournalLogEntryCollection : public Node 1304 { 1305 public: 1306 template <typename CrowApp> 1307 BMCJournalLogEntryCollection(CrowApp &app) : 1308 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1309 { 1310 entityPrivileges = { 1311 {boost::beast::http::verb::get, {{"Login"}}}, 1312 {boost::beast::http::verb::head, {{"Login"}}}, 1313 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1314 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1315 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1316 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1317 } 1318 1319 private: 1320 void doGet(crow::Response &res, const crow::Request &req, 1321 const std::vector<std::string> ¶ms) override 1322 { 1323 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1324 static constexpr const long maxEntriesPerPage = 1000; 1325 uint64_t skip = 0; 1326 uint64_t top = maxEntriesPerPage; // Show max entries by default 1327 if (!getSkipParam(asyncResp->res, req, skip)) 1328 { 1329 return; 1330 } 1331 if (!getTopParam(asyncResp->res, req, top)) 1332 { 1333 return; 1334 } 1335 // Collections don't include the static data added by SubRoute because 1336 // it has a duplicate entry for members 1337 asyncResp->res.jsonValue["@odata.type"] = 1338 "#LogEntryCollection.LogEntryCollection"; 1339 asyncResp->res.jsonValue["@odata.id"] = 1340 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1341 asyncResp->res.jsonValue["@odata.id"] = 1342 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1343 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1344 asyncResp->res.jsonValue["Description"] = 1345 "Collection of BMC Journal Entries"; 1346 asyncResp->res.jsonValue["@odata.id"] = 1347 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1348 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1349 logEntryArray = nlohmann::json::array(); 1350 1351 // Go through the journal and use the timestamp to create a unique ID 1352 // for each entry 1353 sd_journal *journalTmp = nullptr; 1354 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1355 if (ret < 0) 1356 { 1357 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1358 messages::internalError(asyncResp->res); 1359 return; 1360 } 1361 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1362 journalTmp, sd_journal_close); 1363 journalTmp = nullptr; 1364 uint64_t entryCount = 0; 1365 // Reset the unique ID on the first entry 1366 bool firstEntry = true; 1367 SD_JOURNAL_FOREACH(journal.get()) 1368 { 1369 entryCount++; 1370 // Handle paging using skip (number of entries to skip from the 1371 // start) and top (number of entries to display) 1372 if (entryCount <= skip || entryCount > skip + top) 1373 { 1374 continue; 1375 } 1376 1377 std::string idStr; 1378 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1379 { 1380 continue; 1381 } 1382 1383 if (firstEntry) 1384 { 1385 firstEntry = false; 1386 } 1387 1388 logEntryArray.push_back({}); 1389 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1390 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1391 bmcJournalLogEntry) != 0) 1392 { 1393 messages::internalError(asyncResp->res); 1394 return; 1395 } 1396 } 1397 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1398 if (skip + top < entryCount) 1399 { 1400 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1401 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1402 std::to_string(skip + top); 1403 } 1404 } 1405 }; 1406 1407 class BMCJournalLogEntry : public Node 1408 { 1409 public: 1410 BMCJournalLogEntry(CrowApp &app) : 1411 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1412 std::string()) 1413 { 1414 entityPrivileges = { 1415 {boost::beast::http::verb::get, {{"Login"}}}, 1416 {boost::beast::http::verb::head, {{"Login"}}}, 1417 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1418 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1419 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1420 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1421 } 1422 1423 private: 1424 void doGet(crow::Response &res, const crow::Request &req, 1425 const std::vector<std::string> ¶ms) override 1426 { 1427 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1428 if (params.size() != 1) 1429 { 1430 messages::internalError(asyncResp->res); 1431 return; 1432 } 1433 const std::string &entryID = params[0]; 1434 // Convert the unique ID back to a timestamp to find the entry 1435 uint64_t ts = 0; 1436 uint64_t index = 0; 1437 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1438 { 1439 return; 1440 } 1441 1442 sd_journal *journalTmp = nullptr; 1443 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1444 if (ret < 0) 1445 { 1446 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1447 messages::internalError(asyncResp->res); 1448 return; 1449 } 1450 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1451 journalTmp, sd_journal_close); 1452 journalTmp = nullptr; 1453 // Go to the timestamp in the log and move to the entry at the index 1454 // tracking the unique ID 1455 std::string idStr; 1456 bool firstEntry = true; 1457 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1458 for (uint64_t i = 0; i <= index; i++) 1459 { 1460 sd_journal_next(journal.get()); 1461 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1462 { 1463 messages::internalError(asyncResp->res); 1464 return; 1465 } 1466 if (firstEntry) 1467 { 1468 firstEntry = false; 1469 } 1470 } 1471 // Confirm that the entry ID matches what was requested 1472 if (idStr != entryID) 1473 { 1474 messages::resourceMissingAtURI(asyncResp->res, entryID); 1475 return; 1476 } 1477 1478 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1479 asyncResp->res.jsonValue) != 0) 1480 { 1481 messages::internalError(asyncResp->res); 1482 return; 1483 } 1484 } 1485 }; 1486 1487 class CrashdumpService : public Node 1488 { 1489 public: 1490 template <typename CrowApp> 1491 CrashdumpService(CrowApp &app) : 1492 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1493 { 1494 entityPrivileges = { 1495 {boost::beast::http::verb::get, {{"Login"}}}, 1496 {boost::beast::http::verb::head, {{"Login"}}}, 1497 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1498 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1499 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1500 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1501 } 1502 1503 private: 1504 /** 1505 * Functions triggers appropriate requests on DBus 1506 */ 1507 void doGet(crow::Response &res, const crow::Request &req, 1508 const std::vector<std::string> ¶ms) override 1509 { 1510 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1511 // Copy over the static data to include the entries added by SubRoute 1512 asyncResp->res.jsonValue["@odata.id"] = 1513 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1514 asyncResp->res.jsonValue["@odata.type"] = 1515 "#LogService.v1_1_0.LogService"; 1516 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 1517 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 1518 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 1519 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1520 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1521 asyncResp->res.jsonValue["Entries"] = { 1522 {"@odata.id", 1523 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1524 asyncResp->res.jsonValue["Actions"] = { 1525 {"#LogService.ClearLog", 1526 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1527 "Actions/LogService.ClearLog"}}}, 1528 {"Oem", 1529 {{"#Crashdump.OnDemand", 1530 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1531 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1532 1533 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1534 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1535 {"#Crashdump.SendRawPeci", 1536 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1537 "Actions/Oem/Crashdump.SendRawPeci"}}}); 1538 #endif 1539 } 1540 }; 1541 1542 class CrashdumpClear : public Node 1543 { 1544 public: 1545 CrashdumpClear(CrowApp &app) : 1546 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 1547 "LogService.ClearLog/") 1548 { 1549 entityPrivileges = { 1550 {boost::beast::http::verb::get, {{"Login"}}}, 1551 {boost::beast::http::verb::head, {{"Login"}}}, 1552 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1553 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1554 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1555 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1556 } 1557 1558 private: 1559 void doPost(crow::Response &res, const crow::Request &req, 1560 const std::vector<std::string> ¶ms) override 1561 { 1562 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1563 1564 crow::connections::systemBus->async_method_call( 1565 [asyncResp](const boost::system::error_code ec, 1566 const std::string &resp) { 1567 if (ec) 1568 { 1569 messages::internalError(asyncResp->res); 1570 return; 1571 } 1572 messages::success(asyncResp->res); 1573 }, 1574 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 1575 } 1576 }; 1577 1578 std::string getLogCreatedTime(const std::string &crashdump) 1579 { 1580 nlohmann::json crashdumpJson = 1581 nlohmann::json::parse(crashdump, nullptr, false); 1582 if (crashdumpJson.is_discarded()) 1583 { 1584 return std::string(); 1585 } 1586 1587 nlohmann::json::const_iterator cdIt = crashdumpJson.find("crash_data"); 1588 if (cdIt == crashdumpJson.end()) 1589 { 1590 return std::string(); 1591 } 1592 1593 nlohmann::json::const_iterator siIt = cdIt->find("METADATA"); 1594 if (siIt == cdIt->end()) 1595 { 1596 return std::string(); 1597 } 1598 1599 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1600 if (tsIt == siIt->end()) 1601 { 1602 return std::string(); 1603 } 1604 1605 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 1606 if (logTime == nullptr) 1607 { 1608 return std::string(); 1609 } 1610 1611 std::string redfishDateTime = *logTime; 1612 if (redfishDateTime.length() > 2) 1613 { 1614 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1615 } 1616 1617 return redfishDateTime; 1618 } 1619 1620 std::string getLogFileName(const std::string &logTime) 1621 { 1622 // Set the crashdump file name to "crashdump_<logTime>.json" using the 1623 // created time without the timezone info 1624 std::string fileTime = logTime; 1625 size_t plusPos = fileTime.rfind('+'); 1626 if (plusPos != std::string::npos) 1627 { 1628 fileTime.erase(plusPos); 1629 } 1630 return "crashdump_" + fileTime + ".json"; 1631 } 1632 1633 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp, 1634 const std::string &logID, 1635 nlohmann::json &logEntryJson) 1636 { 1637 auto getStoredLogCallback = [asyncResp, logID, &logEntryJson]( 1638 const boost::system::error_code ec, 1639 const std::variant<std::string> &resp) { 1640 if (ec) 1641 { 1642 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1643 if (ec.value() == 1644 boost::system::linux_error::bad_request_descriptor) 1645 { 1646 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 1647 } 1648 else 1649 { 1650 messages::internalError(asyncResp->res); 1651 } 1652 return; 1653 } 1654 const std::string *log = std::get_if<std::string>(&resp); 1655 if (log == nullptr) 1656 { 1657 messages::internalError(asyncResp->res); 1658 return; 1659 } 1660 std::string logTime = getLogCreatedTime(*log); 1661 std::string fileName = getLogFileName(logTime); 1662 1663 logEntryJson = { 1664 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1665 {"@odata.id", 1666 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1667 logID}, 1668 {"Name", "CPU Crashdump"}, 1669 {"Id", logID}, 1670 {"EntryType", "Oem"}, 1671 {"OemRecordFormat", "Crashdump URI"}, 1672 {"Message", 1673 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1674 logID + "/" + fileName}, 1675 {"Created", std::move(logTime)}}; 1676 }; 1677 crow::connections::systemBus->async_method_call( 1678 std::move(getStoredLogCallback), crashdumpObject, 1679 crashdumpPath + std::string("/") + logID, 1680 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface, "Log"); 1681 } 1682 1683 class CrashdumpEntryCollection : public Node 1684 { 1685 public: 1686 template <typename CrowApp> 1687 CrashdumpEntryCollection(CrowApp &app) : 1688 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 1689 { 1690 entityPrivileges = { 1691 {boost::beast::http::verb::get, {{"Login"}}}, 1692 {boost::beast::http::verb::head, {{"Login"}}}, 1693 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1694 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1695 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1696 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1697 } 1698 1699 private: 1700 /** 1701 * Functions triggers appropriate requests on DBus 1702 */ 1703 void doGet(crow::Response &res, const crow::Request &req, 1704 const std::vector<std::string> ¶ms) override 1705 { 1706 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1707 // Collections don't include the static data added by SubRoute because 1708 // it has a duplicate entry for members 1709 auto getLogEntriesCallback = [asyncResp]( 1710 const boost::system::error_code ec, 1711 const std::vector<std::string> &resp) { 1712 if (ec) 1713 { 1714 if (ec.value() != 1715 boost::system::errc::no_such_file_or_directory) 1716 { 1717 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1718 << ec.message(); 1719 messages::internalError(asyncResp->res); 1720 return; 1721 } 1722 } 1723 asyncResp->res.jsonValue["@odata.type"] = 1724 "#LogEntryCollection.LogEntryCollection"; 1725 asyncResp->res.jsonValue["@odata.id"] = 1726 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 1727 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 1728 asyncResp->res.jsonValue["Description"] = 1729 "Collection of Crashdump Entries"; 1730 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1731 logEntryArray = nlohmann::json::array(); 1732 std::vector<std::string> logIDs; 1733 // Get the list of log entries and build up an empty array big 1734 // enough to hold them 1735 for (const std::string &objpath : resp) 1736 { 1737 // Get the log ID 1738 std::size_t lastPos = objpath.rfind("/"); 1739 if (lastPos == std::string::npos) 1740 { 1741 continue; 1742 } 1743 logIDs.emplace_back(objpath.substr(lastPos + 1)); 1744 1745 // Add a space for the log entry to the array 1746 logEntryArray.push_back({}); 1747 } 1748 // Now go through and set up async calls to fill in the entries 1749 size_t index = 0; 1750 for (const std::string &logID : logIDs) 1751 { 1752 // Add the log entry to the array 1753 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 1754 } 1755 asyncResp->res.jsonValue["Members@odata.count"] = 1756 logEntryArray.size(); 1757 }; 1758 crow::connections::systemBus->async_method_call( 1759 std::move(getLogEntriesCallback), 1760 "xyz.openbmc_project.ObjectMapper", 1761 "/xyz/openbmc_project/object_mapper", 1762 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1763 std::array<const char *, 1>{crashdumpInterface}); 1764 } 1765 }; 1766 1767 class CrashdumpEntry : public Node 1768 { 1769 public: 1770 CrashdumpEntry(CrowApp &app) : 1771 Node(app, 1772 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 1773 std::string()) 1774 { 1775 entityPrivileges = { 1776 {boost::beast::http::verb::get, {{"Login"}}}, 1777 {boost::beast::http::verb::head, {{"Login"}}}, 1778 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1779 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1780 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1781 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1782 } 1783 1784 private: 1785 void doGet(crow::Response &res, const crow::Request &req, 1786 const std::vector<std::string> ¶ms) override 1787 { 1788 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1789 if (params.size() != 1) 1790 { 1791 messages::internalError(asyncResp->res); 1792 return; 1793 } 1794 const std::string &logID = params[0]; 1795 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 1796 } 1797 }; 1798 1799 class CrashdumpFile : public Node 1800 { 1801 public: 1802 CrashdumpFile(CrowApp &app) : 1803 Node(app, 1804 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/" 1805 "<str>/", 1806 std::string(), std::string()) 1807 { 1808 entityPrivileges = { 1809 {boost::beast::http::verb::get, {{"Login"}}}, 1810 {boost::beast::http::verb::head, {{"Login"}}}, 1811 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1812 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1813 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1814 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1815 } 1816 1817 private: 1818 void doGet(crow::Response &res, const crow::Request &req, 1819 const std::vector<std::string> ¶ms) override 1820 { 1821 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1822 if (params.size() != 2) 1823 { 1824 messages::internalError(asyncResp->res); 1825 return; 1826 } 1827 const std::string &logID = params[0]; 1828 const std::string &fileName = params[1]; 1829 1830 auto getStoredLogCallback = [asyncResp, logID, fileName]( 1831 const boost::system::error_code ec, 1832 const std::variant<std::string> &resp) { 1833 if (ec) 1834 { 1835 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1836 messages::internalError(asyncResp->res); 1837 return; 1838 } 1839 const std::string *log = std::get_if<std::string>(&resp); 1840 if (log == nullptr) 1841 { 1842 messages::internalError(asyncResp->res); 1843 return; 1844 } 1845 1846 // Verify the file name parameter is correct 1847 if (fileName != getLogFileName(getLogCreatedTime(*log))) 1848 { 1849 messages::resourceMissingAtURI(asyncResp->res, fileName); 1850 return; 1851 } 1852 1853 // Configure this to be a file download when accessed from a browser 1854 asyncResp->res.addHeader("Content-Disposition", "attachment"); 1855 asyncResp->res.body() = *log; 1856 }; 1857 crow::connections::systemBus->async_method_call( 1858 std::move(getStoredLogCallback), crashdumpObject, 1859 crashdumpPath + std::string("/") + logID, 1860 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface, 1861 "Log"); 1862 } 1863 }; 1864 1865 class OnDemandCrashdump : public Node 1866 { 1867 public: 1868 OnDemandCrashdump(CrowApp &app) : 1869 Node(app, 1870 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1871 "Crashdump.OnDemand/") 1872 { 1873 entityPrivileges = { 1874 {boost::beast::http::verb::get, {{"Login"}}}, 1875 {boost::beast::http::verb::head, {{"Login"}}}, 1876 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1877 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1878 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1879 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1880 } 1881 1882 private: 1883 void doPost(crow::Response &res, const crow::Request &req, 1884 const std::vector<std::string> ¶ms) override 1885 { 1886 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1887 1888 auto generateonDemandLogCallback = [asyncResp, 1889 req](const boost::system::error_code 1890 ec, 1891 const std::string &resp) { 1892 if (ec) 1893 { 1894 if (ec.value() == boost::system::errc::operation_not_supported) 1895 { 1896 messages::resourceInStandby(asyncResp->res); 1897 } 1898 else if (ec.value() == 1899 boost::system::errc::device_or_resource_busy) 1900 { 1901 messages::serviceTemporarilyUnavailable(asyncResp->res, 1902 "60"); 1903 } 1904 else 1905 { 1906 messages::internalError(asyncResp->res); 1907 } 1908 return; 1909 } 1910 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 1911 [](boost::system::error_code err, sdbusplus::message::message &, 1912 const std::shared_ptr<task::TaskData> &taskData) { 1913 if (!err) 1914 { 1915 taskData->messages.emplace_back(messages::success()); 1916 taskData->state = "Completed"; 1917 } 1918 return task::completed; 1919 }, 1920 "type='signal',interface='org.freedesktop.DBus.Properties'," 1921 "member='PropertiesChanged',arg0namespace='com.intel." 1922 "crashdump'"); 1923 task->startTimer(std::chrono::minutes(5)); 1924 task->populateResp(asyncResp->res); 1925 task->payload.emplace(req); 1926 }; 1927 crow::connections::systemBus->async_method_call( 1928 std::move(generateonDemandLogCallback), crashdumpObject, 1929 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog"); 1930 } 1931 }; 1932 1933 class SendRawPECI : public Node 1934 { 1935 public: 1936 SendRawPECI(CrowApp &app) : 1937 Node(app, 1938 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1939 "Crashdump.SendRawPeci/") 1940 { 1941 entityPrivileges = { 1942 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1943 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1944 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1945 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1946 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1947 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1948 } 1949 1950 private: 1951 void doPost(crow::Response &res, const crow::Request &req, 1952 const std::vector<std::string> ¶ms) override 1953 { 1954 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1955 std::vector<std::vector<uint8_t>> peciCommands; 1956 1957 nlohmann::json reqJson = 1958 nlohmann::json::parse(req.body, nullptr, false); 1959 if (reqJson.find("PECICommands") != reqJson.end()) 1960 { 1961 if (!json_util::readJson(req, res, "PECICommands", peciCommands)) 1962 { 1963 return; 1964 } 1965 uint32_t idx = 0; 1966 for (auto const &cmd : peciCommands) 1967 { 1968 if (cmd.size() < 3) 1969 { 1970 std::string s("["); 1971 for (auto const &val : cmd) 1972 { 1973 if (val != *cmd.begin()) 1974 { 1975 s += ","; 1976 } 1977 s += std::to_string(val); 1978 } 1979 s += "]"; 1980 messages::actionParameterValueFormatError( 1981 res, s, "PECICommands[" + std::to_string(idx) + "]", 1982 "SendRawPeci"); 1983 return; 1984 } 1985 idx++; 1986 } 1987 } 1988 else 1989 { 1990 /* This interface is deprecated */ 1991 uint8_t clientAddress = 0; 1992 uint8_t readLength = 0; 1993 std::vector<uint8_t> peciCommand; 1994 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1995 "ReadLength", readLength, "PECICommand", 1996 peciCommand)) 1997 { 1998 return; 1999 } 2000 peciCommands.push_back({clientAddress, 0, readLength}); 2001 peciCommands[0].insert(peciCommands[0].end(), peciCommand.begin(), 2002 peciCommand.end()); 2003 } 2004 // Callback to return the Raw PECI response 2005 auto sendRawPECICallback = 2006 [asyncResp](const boost::system::error_code ec, 2007 const std::vector<std::vector<uint8_t>> &resp) { 2008 if (ec) 2009 { 2010 BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: " 2011 << ec.message(); 2012 messages::internalError(asyncResp->res); 2013 return; 2014 } 2015 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 2016 {"PECIResponse", resp}}; 2017 }; 2018 // Call the SendRawPECI command with the provided data 2019 crow::connections::systemBus->async_method_call( 2020 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath, 2021 crashdumpRawPECIInterface, "SendRawPeci", peciCommands); 2022 } 2023 }; 2024 2025 /** 2026 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2027 */ 2028 class DBusLogServiceActionsClear : public Node 2029 { 2030 public: 2031 DBusLogServiceActionsClear(CrowApp &app) : 2032 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2033 "LogService.ClearLog") 2034 { 2035 entityPrivileges = { 2036 {boost::beast::http::verb::get, {{"Login"}}}, 2037 {boost::beast::http::verb::head, {{"Login"}}}, 2038 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2039 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2040 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2041 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2042 } 2043 2044 private: 2045 /** 2046 * Function handles POST method request. 2047 * The Clear Log actions does not require any parameter.The action deletes 2048 * all entries found in the Entries collection for this Log Service. 2049 */ 2050 void doPost(crow::Response &res, const crow::Request &req, 2051 const std::vector<std::string> ¶ms) override 2052 { 2053 BMCWEB_LOG_DEBUG << "Do delete all entries."; 2054 2055 auto asyncResp = std::make_shared<AsyncResp>(res); 2056 // Process response from Logging service. 2057 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 2058 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 2059 if (ec) 2060 { 2061 // TODO Handle for specific error code 2062 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 2063 asyncResp->res.result( 2064 boost::beast::http::status::internal_server_error); 2065 return; 2066 } 2067 2068 asyncResp->res.result(boost::beast::http::status::no_content); 2069 }; 2070 2071 // Make call to Logging service to request Clear Log 2072 crow::connections::systemBus->async_method_call( 2073 resp_handler, "xyz.openbmc_project.Logging", 2074 "/xyz/openbmc_project/logging", 2075 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2076 } 2077 }; 2078 2079 /**************************************************** 2080 * Redfish PostCode interfaces 2081 * using DBUS interface: getPostCodesTS 2082 ******************************************************/ 2083 class PostCodesLogService : public Node 2084 { 2085 public: 2086 PostCodesLogService(CrowApp &app) : 2087 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 2088 { 2089 entityPrivileges = { 2090 {boost::beast::http::verb::get, {{"Login"}}}, 2091 {boost::beast::http::verb::head, {{"Login"}}}, 2092 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2093 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2094 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2095 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2096 } 2097 2098 private: 2099 void doGet(crow::Response &res, const crow::Request &req, 2100 const std::vector<std::string> ¶ms) override 2101 { 2102 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2103 2104 asyncResp->res.jsonValue = { 2105 {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"}, 2106 {"@odata.type", "#LogService.v1_1_0.LogService"}, 2107 {"@odata.context", "/redfish/v1/$metadata#LogService.LogService"}, 2108 {"Name", "POST Code Log Service"}, 2109 {"Description", "POST Code Log Service"}, 2110 {"Id", "BIOS POST Code Log"}, 2111 {"OverWritePolicy", "WrapsWhenFull"}, 2112 {"Entries", 2113 {{"@odata.id", 2114 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 2115 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 2116 {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/" 2117 "Actions/LogService.ClearLog"}}; 2118 } 2119 }; 2120 2121 class PostCodesClear : public Node 2122 { 2123 public: 2124 PostCodesClear(CrowApp &app) : 2125 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/" 2126 "LogService.ClearLog/") 2127 { 2128 entityPrivileges = { 2129 {boost::beast::http::verb::get, {{"Login"}}}, 2130 {boost::beast::http::verb::head, {{"Login"}}}, 2131 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2132 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2133 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2134 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2135 } 2136 2137 private: 2138 void doPost(crow::Response &res, const crow::Request &req, 2139 const std::vector<std::string> ¶ms) override 2140 { 2141 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 2142 2143 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2144 // Make call to post-code service to request clear all 2145 crow::connections::systemBus->async_method_call( 2146 [asyncResp](const boost::system::error_code ec) { 2147 if (ec) 2148 { 2149 // TODO Handle for specific error code 2150 BMCWEB_LOG_ERROR 2151 << "doClearPostCodes resp_handler got error " << ec; 2152 asyncResp->res.result( 2153 boost::beast::http::status::internal_server_error); 2154 messages::internalError(asyncResp->res); 2155 return; 2156 } 2157 }, 2158 "xyz.openbmc_project.State.Boot.PostCode", 2159 "/xyz/openbmc_project/State/Boot/PostCode", 2160 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2161 } 2162 }; 2163 2164 static void fillPostCodeEntry( 2165 std::shared_ptr<AsyncResp> aResp, 2166 const boost::container::flat_map<uint64_t, uint64_t> &postcode, 2167 const uint16_t bootIndex, const uint64_t codeIndex = 0, 2168 const uint64_t skip = 0, const uint64_t top = 0) 2169 { 2170 // Get the Message from the MessageRegistry 2171 const message_registries::Message *message = 2172 message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode"); 2173 std::string severity; 2174 if (message != nullptr) 2175 { 2176 severity = message->severity; 2177 } 2178 2179 uint64_t currentCodeIndex = 0; 2180 nlohmann::json &logEntryArray = aResp->res.jsonValue["Members"]; 2181 2182 uint64_t firstCodeTimeUs = 0; 2183 for (const std::pair<uint64_t, uint64_t> &code : postcode) 2184 { 2185 currentCodeIndex++; 2186 std::string postcodeEntryID = 2187 "B" + std::to_string(bootIndex) + "-" + 2188 std::to_string(currentCodeIndex); // 1 based index in EntryID string 2189 2190 uint64_t usecSinceEpoch = code.first; 2191 uint64_t usTimeOffset = 0; 2192 2193 if (1 == currentCodeIndex) 2194 { // already incremented 2195 firstCodeTimeUs = code.first; 2196 } 2197 else 2198 { 2199 usTimeOffset = code.first - firstCodeTimeUs; 2200 } 2201 2202 // skip if no specific codeIndex is specified and currentCodeIndex does 2203 // not fall between top and skip 2204 if ((codeIndex == 0) && 2205 (currentCodeIndex <= skip || currentCodeIndex > top)) 2206 { 2207 continue; 2208 } 2209 2210 // skip if a sepcific codeIndex is specified and does not match the 2211 // currentIndex 2212 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 2213 { 2214 // This is done for simplicity. 1st entry is needed to calculate 2215 // time offset. To improve efficiency, one can get to the entry 2216 // directly (possibly with flatmap's nth method) 2217 continue; 2218 } 2219 2220 // currentCodeIndex is within top and skip or equal to specified code 2221 // index 2222 2223 // Get the Created time from the timestamp 2224 std::string entryTimeStr; 2225 if (!getTimestampStr(usecSinceEpoch, entryTimeStr)) 2226 { 2227 continue; 2228 } 2229 2230 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 2231 std::ostringstream hexCode; 2232 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 2233 << code.second; 2234 std::ostringstream timeOffsetStr; 2235 // Set Fixed -Point Notation 2236 timeOffsetStr << std::fixed; 2237 // Set precision to 4 digits 2238 timeOffsetStr << std::setprecision(4); 2239 // Add double to stream 2240 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 2241 std::vector<std::string> messageArgs = { 2242 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 2243 2244 // Get MessageArgs template from message registry 2245 std::string msg; 2246 if (message != nullptr) 2247 { 2248 msg = message->message; 2249 2250 // fill in this post code value 2251 int i = 0; 2252 for (const std::string &messageArg : messageArgs) 2253 { 2254 std::string argStr = "%" + std::to_string(++i); 2255 size_t argPos = msg.find(argStr); 2256 if (argPos != std::string::npos) 2257 { 2258 msg.replace(argPos, argStr.length(), messageArg); 2259 } 2260 } 2261 } 2262 2263 // add to AsyncResp 2264 logEntryArray.push_back({}); 2265 nlohmann::json &bmcLogEntry = logEntryArray.back(); 2266 bmcLogEntry = { 2267 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2268 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 2269 {"@odata.id", "/redfish/v1/Systems/system/LogServices/" 2270 "PostCodes/Entries/" + 2271 postcodeEntryID}, 2272 {"Name", "POST Code Log Entry"}, 2273 {"Id", postcodeEntryID}, 2274 {"Message", std::move(msg)}, 2275 {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"}, 2276 {"MessageArgs", std::move(messageArgs)}, 2277 {"EntryType", "Event"}, 2278 {"Severity", std::move(severity)}, 2279 {"Created", std::move(entryTimeStr)}}; 2280 } 2281 } 2282 2283 static void getPostCodeForEntry(std::shared_ptr<AsyncResp> aResp, 2284 const uint16_t bootIndex, 2285 const uint64_t codeIndex) 2286 { 2287 crow::connections::systemBus->async_method_call( 2288 [aResp, bootIndex, codeIndex]( 2289 const boost::system::error_code ec, 2290 const boost::container::flat_map<uint64_t, uint64_t> &postcode) { 2291 if (ec) 2292 { 2293 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2294 messages::internalError(aResp->res); 2295 return; 2296 } 2297 2298 // skip the empty postcode boots 2299 if (postcode.empty()) 2300 { 2301 return; 2302 } 2303 2304 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 2305 2306 aResp->res.jsonValue["Members@odata.count"] = 2307 aResp->res.jsonValue["Members"].size(); 2308 }, 2309 "xyz.openbmc_project.State.Boot.PostCode", 2310 "/xyz/openbmc_project/State/Boot/PostCode", 2311 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2312 bootIndex); 2313 } 2314 2315 static void getPostCodeForBoot(std::shared_ptr<AsyncResp> aResp, 2316 const uint16_t bootIndex, 2317 const uint16_t bootCount, 2318 const uint64_t entryCount, const uint64_t skip, 2319 const uint64_t top) 2320 { 2321 crow::connections::systemBus->async_method_call( 2322 [aResp, bootIndex, bootCount, entryCount, skip, 2323 top](const boost::system::error_code ec, 2324 const boost::container::flat_map<uint64_t, uint64_t> &postcode) { 2325 if (ec) 2326 { 2327 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2328 messages::internalError(aResp->res); 2329 return; 2330 } 2331 2332 uint64_t endCount = entryCount; 2333 if (!postcode.empty()) 2334 { 2335 endCount = entryCount + postcode.size(); 2336 2337 if ((skip < endCount) && ((top + skip) > entryCount)) 2338 { 2339 uint64_t thisBootSkip = 2340 std::max(skip, entryCount) - entryCount; 2341 uint64_t thisBootTop = 2342 std::min(top + skip, endCount) - entryCount; 2343 2344 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 2345 thisBootSkip, thisBootTop); 2346 } 2347 aResp->res.jsonValue["Members@odata.count"] = endCount; 2348 } 2349 2350 // continue to previous bootIndex 2351 if (bootIndex < bootCount) 2352 { 2353 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 2354 bootCount, endCount, skip, top); 2355 } 2356 else 2357 { 2358 aResp->res.jsonValue["Members@odata.nextLink"] = 2359 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2360 "Entries?$skip=" + 2361 std::to_string(skip + top); 2362 } 2363 }, 2364 "xyz.openbmc_project.State.Boot.PostCode", 2365 "/xyz/openbmc_project/State/Boot/PostCode", 2366 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2367 bootIndex); 2368 } 2369 2370 static void getCurrentBootNumber(std::shared_ptr<AsyncResp> aResp, 2371 const uint64_t skip, const uint64_t top) 2372 { 2373 uint64_t entryCount = 0; 2374 crow::connections::systemBus->async_method_call( 2375 [aResp, entryCount, skip, 2376 top](const boost::system::error_code ec, 2377 const std::variant<uint16_t> &bootCount) { 2378 if (ec) 2379 { 2380 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 2381 messages::internalError(aResp->res); 2382 return; 2383 } 2384 auto pVal = std::get_if<uint16_t>(&bootCount); 2385 if (pVal) 2386 { 2387 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top); 2388 } 2389 else 2390 { 2391 BMCWEB_LOG_DEBUG << "Post code boot index failed."; 2392 } 2393 }, 2394 "xyz.openbmc_project.State.Boot.PostCode", 2395 "/xyz/openbmc_project/State/Boot/PostCode", 2396 "org.freedesktop.DBus.Properties", "Get", 2397 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount"); 2398 } 2399 2400 class PostCodesEntryCollection : public Node 2401 { 2402 public: 2403 template <typename CrowApp> 2404 PostCodesEntryCollection(CrowApp &app) : 2405 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 2406 { 2407 entityPrivileges = { 2408 {boost::beast::http::verb::get, {{"Login"}}}, 2409 {boost::beast::http::verb::head, {{"Login"}}}, 2410 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2411 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2412 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2413 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2414 } 2415 2416 private: 2417 void doGet(crow::Response &res, const crow::Request &req, 2418 const std::vector<std::string> ¶ms) override 2419 { 2420 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2421 2422 asyncResp->res.jsonValue["@odata.type"] = 2423 "#LogEntryCollection.LogEntryCollection"; 2424 asyncResp->res.jsonValue["@odata.context"] = 2425 "/redfish/v1/" 2426 "$metadata#LogEntryCollection.LogEntryCollection"; 2427 asyncResp->res.jsonValue["@odata.id"] = 2428 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 2429 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 2430 asyncResp->res.jsonValue["Description"] = 2431 "Collection of POST Code Log Entries"; 2432 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 2433 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2434 2435 uint64_t skip = 0; 2436 uint64_t top = maxEntriesPerPage; // Show max entries by default 2437 if (!getSkipParam(asyncResp->res, req, skip)) 2438 { 2439 return; 2440 } 2441 if (!getTopParam(asyncResp->res, req, top)) 2442 { 2443 return; 2444 } 2445 getCurrentBootNumber(asyncResp, skip, top); 2446 } 2447 }; 2448 2449 class PostCodesEntry : public Node 2450 { 2451 public: 2452 PostCodesEntry(CrowApp &app) : 2453 Node(app, 2454 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/", 2455 std::string()) 2456 { 2457 entityPrivileges = { 2458 {boost::beast::http::verb::get, {{"Login"}}}, 2459 {boost::beast::http::verb::head, {{"Login"}}}, 2460 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2461 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2462 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2463 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2464 } 2465 2466 private: 2467 void doGet(crow::Response &res, const crow::Request &req, 2468 const std::vector<std::string> ¶ms) override 2469 { 2470 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2471 if (params.size() != 1) 2472 { 2473 messages::internalError(asyncResp->res); 2474 return; 2475 } 2476 2477 const std::string &targetID = params[0]; 2478 2479 size_t bootPos = targetID.find('B'); 2480 if (bootPos == std::string::npos) 2481 { 2482 // Requested ID was not found 2483 messages::resourceMissingAtURI(asyncResp->res, targetID); 2484 return; 2485 } 2486 std::string_view bootIndexStr(targetID); 2487 bootIndexStr.remove_prefix(bootPos + 1); 2488 uint16_t bootIndex = 0; 2489 uint64_t codeIndex = 0; 2490 size_t dashPos = bootIndexStr.find('-'); 2491 2492 if (dashPos == std::string::npos) 2493 { 2494 return; 2495 } 2496 std::string_view codeIndexStr(bootIndexStr); 2497 bootIndexStr.remove_suffix(dashPos); 2498 codeIndexStr.remove_prefix(dashPos + 1); 2499 2500 bootIndex = static_cast<uint16_t>( 2501 strtoul(std::string(bootIndexStr).c_str(), NULL, 0)); 2502 codeIndex = strtoul(std::string(codeIndexStr).c_str(), NULL, 0); 2503 if (bootIndex == 0 || codeIndex == 0) 2504 { 2505 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 2506 << params[0]; 2507 } 2508 2509 asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry"; 2510 asyncResp->res.jsonValue["@odata.context"] = 2511 "/redfish/v1/$metadata#LogEntry.LogEntry"; 2512 asyncResp->res.jsonValue["@odata.id"] = 2513 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2514 "Entries"; 2515 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 2516 asyncResp->res.jsonValue["Description"] = 2517 "Collection of POST Code Log Entries"; 2518 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 2519 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2520 2521 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 2522 } 2523 }; 2524 2525 } // namespace redfish 2526