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