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