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