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