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