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