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