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