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 if (ret < 0) 1521 { 1522 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 1523 << strerror(-ret); 1524 messages::internalError(asyncResp->res); 1525 return; 1526 } 1527 for (uint64_t i = 0; i <= index; i++) 1528 { 1529 sd_journal_next(journal.get()); 1530 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1531 { 1532 messages::internalError(asyncResp->res); 1533 return; 1534 } 1535 if (firstEntry) 1536 { 1537 firstEntry = false; 1538 } 1539 } 1540 // Confirm that the entry ID matches what was requested 1541 if (idStr != entryID) 1542 { 1543 messages::resourceMissingAtURI(asyncResp->res, entryID); 1544 return; 1545 } 1546 1547 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1548 asyncResp->res.jsonValue) != 0) 1549 { 1550 messages::internalError(asyncResp->res); 1551 return; 1552 } 1553 } 1554 }; 1555 1556 class SystemDumpService : public Node 1557 { 1558 public: 1559 template <typename CrowApp> 1560 SystemDumpService(CrowApp &app) : 1561 Node(app, "/redfish/v1/Systems/system/LogServices/System/") 1562 { 1563 entityPrivileges = { 1564 {boost::beast::http::verb::get, {{"Login"}}}, 1565 {boost::beast::http::verb::head, {{"Login"}}}, 1566 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1567 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1568 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1569 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1570 } 1571 1572 private: 1573 void doGet(crow::Response &res, const crow::Request &req, 1574 const std::vector<std::string> ¶ms) override 1575 { 1576 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1577 1578 asyncResp->res.jsonValue["@odata.id"] = 1579 "/redfish/v1/Systems/system/LogServices/System"; 1580 asyncResp->res.jsonValue["@odata.type"] = 1581 "#LogService.v1_1_0.LogService"; 1582 asyncResp->res.jsonValue["Name"] = "Dump Log Service"; 1583 asyncResp->res.jsonValue["Description"] = "System Dump Log Service"; 1584 asyncResp->res.jsonValue["Id"] = "System"; 1585 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1586 asyncResp->res.jsonValue["LogEntryTypes"] = "Dump"; 1587 asyncResp->res.jsonValue["Oem"]["DumpType"] = "System"; 1588 1589 asyncResp->res.jsonValue["Entries"] = { 1590 {"@odata.id", 1591 "/redfish/v1/Systems/system/LogServices/System/Entries"}}; 1592 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 1593 {"target", "/redfish/v1/Systems/system/LogServices/System/" 1594 "Actions/LogService.ClearLog"}}; 1595 asyncResp->res.jsonValue["Actions"]["#LogService.CreateLog"] = { 1596 {"target", "/redfish/v1/Systems/system/LogServices/System/" 1597 "Actions/LogService.CreateLog"}}; 1598 } 1599 }; 1600 1601 class SystemDumpEntryCollection : public Node 1602 { 1603 public: 1604 template <typename CrowApp> 1605 SystemDumpEntryCollection(CrowApp &app) : 1606 Node(app, "/redfish/v1/Systems/system/LogServices/System/Entries/") 1607 { 1608 entityPrivileges = { 1609 {boost::beast::http::verb::get, {{"Login"}}}, 1610 {boost::beast::http::verb::head, {{"Login"}}}, 1611 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1612 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1613 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1614 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1615 } 1616 1617 private: 1618 /** 1619 * Functions triggers appropriate requests on DBus 1620 */ 1621 void doGet(crow::Response &res, const crow::Request &req, 1622 const std::vector<std::string> ¶ms) override 1623 { 1624 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1625 1626 asyncResp->res.jsonValue["@odata.type"] = 1627 "#LogEntryCollection.LogEntryCollection"; 1628 asyncResp->res.jsonValue["@odata.id"] = 1629 "/redfish/v1/Systems/system/LogServices/System/Entries"; 1630 asyncResp->res.jsonValue["Name"] = "System Dump Entries"; 1631 asyncResp->res.jsonValue["Description"] = 1632 "Collection of System Dump Entries"; 1633 1634 crow::connections::systemBus->async_method_call( 1635 [asyncResp](const boost::system::error_code ec, 1636 const crow::openbmc_mapper::GetSubTreeType &resp) { 1637 if (ec) 1638 { 1639 BMCWEB_LOG_ERROR << " resp_handler got error " << ec; 1640 messages::internalError(asyncResp->res); 1641 return; 1642 } 1643 1644 nlohmann::json &logArray = asyncResp->res.jsonValue["Members"]; 1645 logArray = nlohmann::json::array(); 1646 for (auto &object : resp) 1647 { 1648 const std::string &path = 1649 static_cast<const std::string &>(object.first); 1650 std::size_t lastPos = path.rfind("/"); 1651 if (lastPos == std::string::npos) 1652 { 1653 continue; 1654 } 1655 std::string logID = path.substr(lastPos + 1); 1656 logArray.push_back( 1657 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 1658 "System/Entries/" + 1659 logID}}); 1660 } 1661 asyncResp->res.jsonValue["Members@odata.count"] = 1662 logArray.size(); 1663 }, 1664 "xyz.openbmc_project.ObjectMapper", 1665 "/xyz/openbmc_project/object_mapper", 1666 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1667 "/xyz/openbmc_project/dump", 0, 1668 std::array<const char *, 1>{ 1669 "xyz.openbmc_project.Dump.Entry.System"}); 1670 } 1671 }; 1672 1673 class SystemDumpEntry : public Node 1674 { 1675 public: 1676 SystemDumpEntry(CrowApp &app) : 1677 Node(app, 1678 "/redfish/v1/Systems/system/LogServices/System/Entries/<str>/", 1679 std::string()) 1680 { 1681 entityPrivileges = { 1682 {boost::beast::http::verb::get, {{"Login"}}}, 1683 {boost::beast::http::verb::head, {{"Login"}}}, 1684 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1685 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1686 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1687 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1688 } 1689 1690 private: 1691 void doGet(crow::Response &res, const crow::Request &req, 1692 const std::vector<std::string> ¶ms) override 1693 { 1694 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1695 if (params.size() != 1) 1696 { 1697 messages::internalError(asyncResp->res); 1698 return; 1699 } 1700 const std::string &entryID = params[0]; 1701 crow::connections::systemBus->async_method_call( 1702 [asyncResp, entryID](const boost::system::error_code ec, 1703 GetManagedObjectsType &resp) { 1704 if (ec) 1705 { 1706 BMCWEB_LOG_ERROR 1707 << "SystemDumpEntry resp_handler got error " << ec; 1708 messages::internalError(asyncResp->res); 1709 return; 1710 } 1711 1712 for (auto &objectPath : resp) 1713 { 1714 if (objectPath.first.str.find( 1715 "/xyz/openbmc_project/dump/entry/" + entryID) == 1716 std::string::npos) 1717 { 1718 continue; 1719 } 1720 1721 bool foundSystemDumpEntry = false; 1722 for (auto &interfaceMap : objectPath.second) 1723 { 1724 if (interfaceMap.first == 1725 "xyz.openbmc_project.Dump.Entry.System") 1726 { 1727 foundSystemDumpEntry = true; 1728 break; 1729 } 1730 } 1731 if (foundSystemDumpEntry == false) 1732 { 1733 BMCWEB_LOG_DEBUG << "Can't find System Dump Entry"; 1734 messages::internalError(asyncResp->res); 1735 return; 1736 } 1737 1738 std::string timestamp{}; 1739 uint64_t size = 0; 1740 1741 for (auto &interfaceMap : objectPath.second) 1742 { 1743 if (interfaceMap.first == 1744 "xyz.openbmc_project.Dump.Entry") 1745 { 1746 for (auto &propertyMap : interfaceMap.second) 1747 { 1748 if (propertyMap.first == "Size") 1749 { 1750 auto sizePtr = std::get_if<uint64_t>( 1751 &propertyMap.second); 1752 if (sizePtr == nullptr) 1753 { 1754 messages::propertyMissing( 1755 asyncResp->res, "Size"); 1756 break; 1757 } 1758 size = *sizePtr; 1759 break; 1760 } 1761 } 1762 } 1763 else if (interfaceMap.first == 1764 "xyz.openbmc_project.Time.EpochTime") 1765 { 1766 for (auto &propertyMap : interfaceMap.second) 1767 { 1768 if (propertyMap.first == "Elapsed") 1769 { 1770 const uint64_t *usecsTimeStamp = 1771 std::get_if<uint64_t>( 1772 &propertyMap.second); 1773 if (usecsTimeStamp == nullptr) 1774 { 1775 messages::propertyMissing( 1776 asyncResp->res, "Elapsed"); 1777 break; 1778 } 1779 getTimestampStr(*usecsTimeStamp, timestamp); 1780 break; 1781 } 1782 } 1783 } 1784 } 1785 asyncResp->res.jsonValue = { 1786 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1787 {"@odata.id", 1788 "/redfish/v1/Systems/system/LogServices/System/" 1789 "Entries/" + 1790 entryID}, 1791 {"Name", "System Dump Entry"}, 1792 {"Id", entryID}, 1793 {"SizeInB", size}, 1794 {"EntryType", "Dump"}, 1795 {"EntryCode", "User generated dump"}, 1796 {"Created", timestamp}}; 1797 1798 asyncResp->res 1799 .jsonValue["Actions"]["#LogEntry.DownloadLog"] = { 1800 {"target", 1801 "/redfish/v1/Systems/system/LogServices/System/" 1802 "Entries/" + 1803 entryID + "/Actions/LogEntry.DownloadLog"}}; 1804 } 1805 }, 1806 "xyz.openbmc_project.Dump.Manager", "/xyz/openbmc_project/dump", 1807 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1808 } 1809 1810 void doDelete(crow::Response &res, const crow::Request &req, 1811 const std::vector<std::string> ¶ms) override 1812 { 1813 BMCWEB_LOG_DEBUG << "Do delete single dump entry"; 1814 1815 auto asyncResp = std::make_shared<AsyncResp>(res); 1816 1817 if (params.size() != 1) 1818 { 1819 messages::internalError(asyncResp->res); 1820 return; 1821 } 1822 std::string entryID = params[0]; 1823 1824 crow::connections::systemBus->async_method_call( 1825 [asyncResp, 1826 entryID](const boost::system::error_code ec, 1827 const crow::openbmc_mapper::GetSubTreeType &resp) { 1828 if (ec) 1829 { 1830 BMCWEB_LOG_ERROR << " resp_handler got error " << ec; 1831 messages::internalError(asyncResp->res); 1832 return; 1833 } 1834 1835 for (auto &object : resp) 1836 { 1837 const std::string &path = 1838 static_cast<const std::string &>(object.first); 1839 1840 std::size_t pos = path.rfind( 1841 "/xyz/openbmc_project/dump/entry/" + entryID); 1842 if (pos != std::string::npos) 1843 { 1844 deleteSystemDumpEntry(asyncResp->res, entryID); 1845 return; 1846 } 1847 } 1848 }, 1849 "xyz.openbmc_project.ObjectMapper", 1850 "/xyz/openbmc_project/object_mapper", 1851 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1852 "/xyz/openbmc_project/dump", 0, 1853 std::array<const char *, 1>{ 1854 "xyz.openbmc_project.Dump.Entry.System"}); 1855 } 1856 }; 1857 1858 class SystemDumpEntryDownload : public Node 1859 { 1860 public: 1861 SystemDumpEntryDownload(CrowApp &app) : 1862 Node(app, 1863 "/redfish/v1/Systems/system/LogServices/System/Entries/<str>/" 1864 "Actions/" 1865 "LogEntry.DownloadLog/", 1866 std::string()) 1867 { 1868 entityPrivileges = { 1869 {boost::beast::http::verb::get, {{"Login"}}}, 1870 {boost::beast::http::verb::head, {{"Login"}}}, 1871 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1872 } 1873 1874 private: 1875 void doPost(crow::Response &res, const crow::Request &req, 1876 const std::vector<std::string> ¶ms) override 1877 { 1878 if (params.size() != 1) 1879 { 1880 messages::internalError(res); 1881 return; 1882 } 1883 const std::string &entryID = params[0]; 1884 crow::obmc_dump::handleDumpOffloadUrl(req, res, entryID); 1885 } 1886 }; 1887 1888 class SystemDumpClear : public Node 1889 { 1890 public: 1891 SystemDumpClear(CrowApp &app) : 1892 Node(app, "/redfish/v1/Systems/system/LogServices/System/" 1893 "Actions/" 1894 "LogService.ClearLog/") 1895 { 1896 entityPrivileges = { 1897 {boost::beast::http::verb::get, {{"Login"}}}, 1898 {boost::beast::http::verb::head, {{"Login"}}}, 1899 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1900 } 1901 1902 private: 1903 void doPost(crow::Response &res, const crow::Request &req, 1904 const std::vector<std::string> ¶ms) override 1905 { 1906 1907 auto asyncResp = std::make_shared<AsyncResp>(res); 1908 crow::connections::systemBus->async_method_call( 1909 [asyncResp](const boost::system::error_code ec, 1910 const std::vector<std::string> &dumpList) { 1911 if (ec) 1912 { 1913 messages::internalError(asyncResp->res); 1914 return; 1915 } 1916 1917 for (const std::string &objectPath : dumpList) 1918 { 1919 std::size_t pos = objectPath.rfind("/"); 1920 if (pos != std::string::npos) 1921 { 1922 std::string logID = objectPath.substr(pos + 1); 1923 deleteSystemDumpEntry(asyncResp->res, logID); 1924 } 1925 } 1926 }, 1927 "xyz.openbmc_project.ObjectMapper", 1928 "/xyz/openbmc_project/object_mapper", 1929 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 1930 "/xyz/openbmc_project/dump", 0, 1931 std::array<const char *, 1>{ 1932 "xyz.openbmc_project.Dump.Entry.System"}); 1933 } 1934 }; 1935 1936 class CrashdumpService : public Node 1937 { 1938 public: 1939 template <typename CrowApp> 1940 CrashdumpService(CrowApp &app) : 1941 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1942 { 1943 // Note: Deviated from redfish privilege registry for GET & HEAD 1944 // method for security reasons. 1945 entityPrivileges = { 1946 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1947 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1948 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1949 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1950 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1951 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1952 } 1953 1954 private: 1955 /** 1956 * Functions triggers appropriate requests on DBus 1957 */ 1958 void doGet(crow::Response &res, const crow::Request &req, 1959 const std::vector<std::string> ¶ms) override 1960 { 1961 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1962 // Copy over the static data to include the entries added by SubRoute 1963 asyncResp->res.jsonValue["@odata.id"] = 1964 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1965 asyncResp->res.jsonValue["@odata.type"] = 1966 "#LogService.v1_1_0.LogService"; 1967 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 1968 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 1969 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 1970 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1971 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1972 asyncResp->res.jsonValue["Entries"] = { 1973 {"@odata.id", 1974 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1975 asyncResp->res.jsonValue["Actions"] = { 1976 {"#LogService.ClearLog", 1977 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1978 "Actions/LogService.ClearLog"}}}, 1979 {"Oem", 1980 {{"#Crashdump.OnDemand", 1981 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1982 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1983 1984 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1985 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1986 {"#Crashdump.SendRawPeci", 1987 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1988 "Actions/Oem/Crashdump.SendRawPeci"}}}); 1989 #endif 1990 } 1991 }; 1992 1993 class CrashdumpClear : public Node 1994 { 1995 public: 1996 CrashdumpClear(CrowApp &app) : 1997 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 1998 "LogService.ClearLog/") 1999 { 2000 // Note: Deviated from redfish privilege registry for GET & HEAD 2001 // method for security reasons. 2002 entityPrivileges = { 2003 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2004 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2005 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2006 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2007 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2008 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2009 } 2010 2011 private: 2012 void doPost(crow::Response &res, const crow::Request &req, 2013 const std::vector<std::string> ¶ms) override 2014 { 2015 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2016 2017 crow::connections::systemBus->async_method_call( 2018 [asyncResp](const boost::system::error_code ec, 2019 const std::string &resp) { 2020 if (ec) 2021 { 2022 messages::internalError(asyncResp->res); 2023 return; 2024 } 2025 messages::success(asyncResp->res); 2026 }, 2027 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 2028 } 2029 }; 2030 2031 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp, 2032 const std::string &logID, 2033 nlohmann::json &logEntryJson) 2034 { 2035 auto getStoredLogCallback = 2036 [asyncResp, logID, &logEntryJson]( 2037 const boost::system::error_code ec, 2038 const std::vector<std::pair<std::string, VariantType>> ¶ms) { 2039 if (ec) 2040 { 2041 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 2042 if (ec.value() == 2043 boost::system::linux_error::bad_request_descriptor) 2044 { 2045 messages::resourceNotFound(asyncResp->res, "LogEntry", 2046 logID); 2047 } 2048 else 2049 { 2050 messages::internalError(asyncResp->res); 2051 } 2052 return; 2053 } 2054 2055 std::string timestamp{}; 2056 std::string filename{}; 2057 std::string logfile{}; 2058 ParseCrashdumpParameters(params, filename, timestamp, logfile); 2059 2060 if (filename.empty() || timestamp.empty()) 2061 { 2062 messages::resourceMissingAtURI(asyncResp->res, logID); 2063 return; 2064 } 2065 2066 std::string crashdumpURI = 2067 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 2068 logID + "/" + filename; 2069 logEntryJson = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2070 {"@odata.id", "/redfish/v1/Systems/system/" 2071 "LogServices/Crashdump/Entries/" + 2072 logID}, 2073 {"Name", "CPU Crashdump"}, 2074 {"Id", logID}, 2075 {"EntryType", "Oem"}, 2076 {"OemRecordFormat", "Crashdump URI"}, 2077 {"Message", std::move(crashdumpURI)}, 2078 {"Created", std::move(timestamp)}}; 2079 }; 2080 crow::connections::systemBus->async_method_call( 2081 std::move(getStoredLogCallback), crashdumpObject, 2082 crashdumpPath + std::string("/") + logID, 2083 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2084 } 2085 2086 class CrashdumpEntryCollection : public Node 2087 { 2088 public: 2089 template <typename CrowApp> 2090 CrashdumpEntryCollection(CrowApp &app) : 2091 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 2092 { 2093 // Note: Deviated from redfish privilege registry for GET & HEAD 2094 // method for security reasons. 2095 entityPrivileges = { 2096 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2097 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2098 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2099 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2100 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2101 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2102 } 2103 2104 private: 2105 /** 2106 * Functions triggers appropriate requests on DBus 2107 */ 2108 void doGet(crow::Response &res, const crow::Request &req, 2109 const std::vector<std::string> ¶ms) override 2110 { 2111 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2112 // Collections don't include the static data added by SubRoute because 2113 // it has a duplicate entry for members 2114 auto getLogEntriesCallback = [asyncResp]( 2115 const boost::system::error_code ec, 2116 const std::vector<std::string> &resp) { 2117 if (ec) 2118 { 2119 if (ec.value() != 2120 boost::system::errc::no_such_file_or_directory) 2121 { 2122 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 2123 << ec.message(); 2124 messages::internalError(asyncResp->res); 2125 return; 2126 } 2127 } 2128 asyncResp->res.jsonValue["@odata.type"] = 2129 "#LogEntryCollection.LogEntryCollection"; 2130 asyncResp->res.jsonValue["@odata.id"] = 2131 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 2132 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 2133 asyncResp->res.jsonValue["Description"] = 2134 "Collection of Crashdump Entries"; 2135 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 2136 logEntryArray = nlohmann::json::array(); 2137 std::vector<std::string> logIDs; 2138 // Get the list of log entries and build up an empty array big 2139 // enough to hold them 2140 for (const std::string &objpath : resp) 2141 { 2142 // Get the log ID 2143 std::size_t lastPos = objpath.rfind("/"); 2144 if (lastPos == std::string::npos) 2145 { 2146 continue; 2147 } 2148 logIDs.emplace_back(objpath.substr(lastPos + 1)); 2149 2150 // Add a space for the log entry to the array 2151 logEntryArray.push_back({}); 2152 } 2153 // Now go through and set up async calls to fill in the entries 2154 size_t index = 0; 2155 for (const std::string &logID : logIDs) 2156 { 2157 // Add the log entry to the array 2158 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 2159 } 2160 asyncResp->res.jsonValue["Members@odata.count"] = 2161 logEntryArray.size(); 2162 }; 2163 crow::connections::systemBus->async_method_call( 2164 std::move(getLogEntriesCallback), 2165 "xyz.openbmc_project.ObjectMapper", 2166 "/xyz/openbmc_project/object_mapper", 2167 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 2168 std::array<const char *, 1>{crashdumpInterface}); 2169 } 2170 }; 2171 2172 class CrashdumpEntry : public Node 2173 { 2174 public: 2175 CrashdumpEntry(CrowApp &app) : 2176 Node(app, 2177 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 2178 std::string()) 2179 { 2180 // Note: Deviated from redfish privilege registry for GET & HEAD 2181 // method for security reasons. 2182 entityPrivileges = { 2183 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2184 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2185 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2186 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2187 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2188 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2189 } 2190 2191 private: 2192 void doGet(crow::Response &res, const crow::Request &req, 2193 const std::vector<std::string> ¶ms) override 2194 { 2195 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2196 if (params.size() != 1) 2197 { 2198 messages::internalError(asyncResp->res); 2199 return; 2200 } 2201 const std::string &logID = params[0]; 2202 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 2203 } 2204 }; 2205 2206 class CrashdumpFile : public Node 2207 { 2208 public: 2209 CrashdumpFile(CrowApp &app) : 2210 Node(app, 2211 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/" 2212 "<str>/", 2213 std::string(), std::string()) 2214 { 2215 // Note: Deviated from redfish privilege registry for GET & HEAD 2216 // method for security reasons. 2217 entityPrivileges = { 2218 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2219 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2220 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2221 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2222 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2223 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2224 } 2225 2226 private: 2227 void doGet(crow::Response &res, const crow::Request &req, 2228 const std::vector<std::string> ¶ms) override 2229 { 2230 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2231 if (params.size() != 2) 2232 { 2233 messages::internalError(asyncResp->res); 2234 return; 2235 } 2236 const std::string &logID = params[0]; 2237 const std::string &fileName = params[1]; 2238 2239 auto getStoredLogCallback = 2240 [asyncResp, logID, fileName]( 2241 const boost::system::error_code ec, 2242 const std::vector<std::pair<std::string, VariantType>> &resp) { 2243 if (ec) 2244 { 2245 BMCWEB_LOG_DEBUG << "failed to get log ec: " 2246 << ec.message(); 2247 messages::internalError(asyncResp->res); 2248 return; 2249 } 2250 2251 std::string dbusFilename{}; 2252 std::string dbusTimestamp{}; 2253 std::string dbusFilepath{}; 2254 2255 ParseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 2256 dbusFilepath); 2257 2258 if (dbusFilename.empty() || dbusTimestamp.empty() || 2259 dbusFilepath.empty()) 2260 { 2261 messages::resourceMissingAtURI(asyncResp->res, fileName); 2262 return; 2263 } 2264 2265 // Verify the file name parameter is correct 2266 if (fileName != dbusFilename) 2267 { 2268 messages::resourceMissingAtURI(asyncResp->res, fileName); 2269 return; 2270 } 2271 2272 if (!std::filesystem::exists(dbusFilepath)) 2273 { 2274 messages::resourceMissingAtURI(asyncResp->res, fileName); 2275 return; 2276 } 2277 std::ifstream ifs(dbusFilepath, std::ios::in | 2278 std::ios::binary | 2279 std::ios::ate); 2280 std::ifstream::pos_type fileSize = ifs.tellg(); 2281 if (fileSize < 0) 2282 { 2283 messages::generalError(asyncResp->res); 2284 return; 2285 } 2286 ifs.seekg(0, std::ios::beg); 2287 2288 auto crashData = std::make_unique<char[]>( 2289 static_cast<unsigned int>(fileSize)); 2290 2291 ifs.read(crashData.get(), static_cast<int>(fileSize)); 2292 2293 // The cast to std::string is intentional in order to use the 2294 // assign() that applies move mechanics 2295 asyncResp->res.body().assign( 2296 static_cast<std::string>(crashData.get())); 2297 2298 // Configure this to be a file download when accessed from 2299 // a browser 2300 asyncResp->res.addHeader("Content-Disposition", "attachment"); 2301 }; 2302 crow::connections::systemBus->async_method_call( 2303 std::move(getStoredLogCallback), crashdumpObject, 2304 crashdumpPath + std::string("/") + logID, 2305 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2306 } 2307 }; 2308 2309 class OnDemandCrashdump : public Node 2310 { 2311 public: 2312 OnDemandCrashdump(CrowApp &app) : 2313 Node(app, 2314 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2315 "Crashdump.OnDemand/") 2316 { 2317 // Note: Deviated from redfish privilege registry for GET & HEAD 2318 // method for security reasons. 2319 entityPrivileges = { 2320 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2321 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2322 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2323 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2324 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2325 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2326 } 2327 2328 private: 2329 void doPost(crow::Response &res, const crow::Request &req, 2330 const std::vector<std::string> ¶ms) override 2331 { 2332 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2333 2334 auto generateonDemandLogCallback = [asyncResp, 2335 req](const boost::system::error_code 2336 ec, 2337 const std::string &resp) { 2338 if (ec) 2339 { 2340 if (ec.value() == boost::system::errc::operation_not_supported) 2341 { 2342 messages::resourceInStandby(asyncResp->res); 2343 } 2344 else if (ec.value() == 2345 boost::system::errc::device_or_resource_busy) 2346 { 2347 messages::serviceTemporarilyUnavailable(asyncResp->res, 2348 "60"); 2349 } 2350 else 2351 { 2352 messages::internalError(asyncResp->res); 2353 } 2354 return; 2355 } 2356 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2357 [](boost::system::error_code err, sdbusplus::message::message &, 2358 const std::shared_ptr<task::TaskData> &taskData) { 2359 if (!err) 2360 { 2361 taskData->messages.emplace_back( 2362 messages::taskCompletedOK( 2363 std::to_string(taskData->index))); 2364 taskData->state = "Completed"; 2365 } 2366 return task::completed; 2367 }, 2368 "type='signal',interface='org.freedesktop.DBus.Properties'," 2369 "member='PropertiesChanged',arg0namespace='com.intel." 2370 "crashdump'"); 2371 task->startTimer(std::chrono::minutes(5)); 2372 task->populateResp(asyncResp->res); 2373 task->payload.emplace(req); 2374 }; 2375 crow::connections::systemBus->async_method_call( 2376 std::move(generateonDemandLogCallback), crashdumpObject, 2377 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog"); 2378 } 2379 }; 2380 2381 class SendRawPECI : public Node 2382 { 2383 public: 2384 SendRawPECI(CrowApp &app) : 2385 Node(app, 2386 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2387 "Crashdump.SendRawPeci/") 2388 { 2389 // Note: Deviated from redfish privilege registry for GET & HEAD 2390 // method for security reasons. 2391 entityPrivileges = { 2392 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2393 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2394 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2395 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2396 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2397 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2398 } 2399 2400 private: 2401 void doPost(crow::Response &res, const crow::Request &req, 2402 const std::vector<std::string> ¶ms) override 2403 { 2404 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2405 std::vector<std::vector<uint8_t>> peciCommands; 2406 2407 nlohmann::json reqJson = 2408 nlohmann::json::parse(req.body, nullptr, false); 2409 if (reqJson.find("PECICommands") != reqJson.end()) 2410 { 2411 if (!json_util::readJson(req, res, "PECICommands", peciCommands)) 2412 { 2413 return; 2414 } 2415 uint32_t idx = 0; 2416 for (auto const &cmd : peciCommands) 2417 { 2418 if (cmd.size() < 3) 2419 { 2420 std::string s("["); 2421 for (auto const &val : cmd) 2422 { 2423 if (val != *cmd.begin()) 2424 { 2425 s += ","; 2426 } 2427 s += std::to_string(val); 2428 } 2429 s += "]"; 2430 messages::actionParameterValueFormatError( 2431 res, s, "PECICommands[" + std::to_string(idx) + "]", 2432 "SendRawPeci"); 2433 return; 2434 } 2435 idx++; 2436 } 2437 } 2438 else 2439 { 2440 /* This interface is deprecated */ 2441 uint8_t clientAddress = 0; 2442 uint8_t readLength = 0; 2443 std::vector<uint8_t> peciCommand; 2444 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 2445 "ReadLength", readLength, "PECICommand", 2446 peciCommand)) 2447 { 2448 return; 2449 } 2450 peciCommands.push_back({clientAddress, 0, readLength}); 2451 peciCommands[0].insert(peciCommands[0].end(), peciCommand.begin(), 2452 peciCommand.end()); 2453 } 2454 // Callback to return the Raw PECI response 2455 auto sendRawPECICallback = 2456 [asyncResp](const boost::system::error_code ec, 2457 const std::vector<std::vector<uint8_t>> &resp) { 2458 if (ec) 2459 { 2460 BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: " 2461 << ec.message(); 2462 messages::internalError(asyncResp->res); 2463 return; 2464 } 2465 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 2466 {"PECIResponse", resp}}; 2467 }; 2468 // Call the SendRawPECI command with the provided data 2469 crow::connections::systemBus->async_method_call( 2470 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath, 2471 crashdumpRawPECIInterface, "SendRawPeci", peciCommands); 2472 } 2473 }; 2474 2475 /** 2476 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2477 */ 2478 class DBusLogServiceActionsClear : public Node 2479 { 2480 public: 2481 DBusLogServiceActionsClear(CrowApp &app) : 2482 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2483 "LogService.ClearLog/") 2484 { 2485 entityPrivileges = { 2486 {boost::beast::http::verb::get, {{"Login"}}}, 2487 {boost::beast::http::verb::head, {{"Login"}}}, 2488 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2489 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2490 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2491 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2492 } 2493 2494 private: 2495 /** 2496 * Function handles POST method request. 2497 * The Clear Log actions does not require any parameter.The action deletes 2498 * all entries found in the Entries collection for this Log Service. 2499 */ 2500 void doPost(crow::Response &res, const crow::Request &req, 2501 const std::vector<std::string> ¶ms) override 2502 { 2503 BMCWEB_LOG_DEBUG << "Do delete all entries."; 2504 2505 auto asyncResp = std::make_shared<AsyncResp>(res); 2506 // Process response from Logging service. 2507 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 2508 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 2509 if (ec) 2510 { 2511 // TODO Handle for specific error code 2512 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 2513 asyncResp->res.result( 2514 boost::beast::http::status::internal_server_error); 2515 return; 2516 } 2517 2518 asyncResp->res.result(boost::beast::http::status::no_content); 2519 }; 2520 2521 // Make call to Logging service to request Clear Log 2522 crow::connections::systemBus->async_method_call( 2523 resp_handler, "xyz.openbmc_project.Logging", 2524 "/xyz/openbmc_project/logging", 2525 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2526 } 2527 }; 2528 2529 /**************************************************** 2530 * Redfish PostCode interfaces 2531 * using DBUS interface: getPostCodesTS 2532 ******************************************************/ 2533 class PostCodesLogService : public Node 2534 { 2535 public: 2536 PostCodesLogService(CrowApp &app) : 2537 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 2538 { 2539 entityPrivileges = { 2540 {boost::beast::http::verb::get, {{"Login"}}}, 2541 {boost::beast::http::verb::head, {{"Login"}}}, 2542 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2543 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2544 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2545 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2546 } 2547 2548 private: 2549 void doGet(crow::Response &res, const crow::Request &req, 2550 const std::vector<std::string> ¶ms) override 2551 { 2552 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2553 2554 asyncResp->res.jsonValue = { 2555 {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"}, 2556 {"@odata.type", "#LogService.v1_1_0.LogService"}, 2557 {"@odata.context", "/redfish/v1/$metadata#LogService.LogService"}, 2558 {"Name", "POST Code Log Service"}, 2559 {"Description", "POST Code Log Service"}, 2560 {"Id", "BIOS POST Code Log"}, 2561 {"OverWritePolicy", "WrapsWhenFull"}, 2562 {"Entries", 2563 {{"@odata.id", 2564 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 2565 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 2566 {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/" 2567 "Actions/LogService.ClearLog"}}; 2568 } 2569 }; 2570 2571 class PostCodesClear : public Node 2572 { 2573 public: 2574 PostCodesClear(CrowApp &app) : 2575 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/" 2576 "LogService.ClearLog/") 2577 { 2578 entityPrivileges = { 2579 {boost::beast::http::verb::get, {{"Login"}}}, 2580 {boost::beast::http::verb::head, {{"Login"}}}, 2581 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2582 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2583 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2584 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2585 } 2586 2587 private: 2588 void doPost(crow::Response &res, const crow::Request &req, 2589 const std::vector<std::string> ¶ms) override 2590 { 2591 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 2592 2593 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2594 // Make call to post-code service to request clear all 2595 crow::connections::systemBus->async_method_call( 2596 [asyncResp](const boost::system::error_code ec) { 2597 if (ec) 2598 { 2599 // TODO Handle for specific error code 2600 BMCWEB_LOG_ERROR 2601 << "doClearPostCodes resp_handler got error " << ec; 2602 asyncResp->res.result( 2603 boost::beast::http::status::internal_server_error); 2604 messages::internalError(asyncResp->res); 2605 return; 2606 } 2607 }, 2608 "xyz.openbmc_project.State.Boot.PostCode", 2609 "/xyz/openbmc_project/State/Boot/PostCode", 2610 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2611 } 2612 }; 2613 2614 static void fillPostCodeEntry( 2615 std::shared_ptr<AsyncResp> aResp, 2616 const boost::container::flat_map<uint64_t, uint64_t> &postcode, 2617 const uint16_t bootIndex, const uint64_t codeIndex = 0, 2618 const uint64_t skip = 0, const uint64_t top = 0) 2619 { 2620 // Get the Message from the MessageRegistry 2621 const message_registries::Message *message = 2622 message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode"); 2623 2624 uint64_t currentCodeIndex = 0; 2625 nlohmann::json &logEntryArray = aResp->res.jsonValue["Members"]; 2626 2627 uint64_t firstCodeTimeUs = 0; 2628 for (const std::pair<uint64_t, uint64_t> &code : postcode) 2629 { 2630 currentCodeIndex++; 2631 std::string postcodeEntryID = 2632 "B" + std::to_string(bootIndex) + "-" + 2633 std::to_string(currentCodeIndex); // 1 based index in EntryID string 2634 2635 uint64_t usecSinceEpoch = code.first; 2636 uint64_t usTimeOffset = 0; 2637 2638 if (1 == currentCodeIndex) 2639 { // already incremented 2640 firstCodeTimeUs = code.first; 2641 } 2642 else 2643 { 2644 usTimeOffset = code.first - firstCodeTimeUs; 2645 } 2646 2647 // skip if no specific codeIndex is specified and currentCodeIndex does 2648 // not fall between top and skip 2649 if ((codeIndex == 0) && 2650 (currentCodeIndex <= skip || currentCodeIndex > top)) 2651 { 2652 continue; 2653 } 2654 2655 // skip if a sepcific codeIndex is specified and does not match the 2656 // currentIndex 2657 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 2658 { 2659 // This is done for simplicity. 1st entry is needed to calculate 2660 // time offset. To improve efficiency, one can get to the entry 2661 // directly (possibly with flatmap's nth method) 2662 continue; 2663 } 2664 2665 // currentCodeIndex is within top and skip or equal to specified code 2666 // index 2667 2668 // Get the Created time from the timestamp 2669 std::string entryTimeStr; 2670 if (!getTimestampStr(usecSinceEpoch, entryTimeStr)) 2671 { 2672 continue; 2673 } 2674 2675 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 2676 std::ostringstream hexCode; 2677 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 2678 << code.second; 2679 std::ostringstream timeOffsetStr; 2680 // Set Fixed -Point Notation 2681 timeOffsetStr << std::fixed; 2682 // Set precision to 4 digits 2683 timeOffsetStr << std::setprecision(4); 2684 // Add double to stream 2685 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 2686 std::vector<std::string> messageArgs = { 2687 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 2688 2689 // Get MessageArgs template from message registry 2690 std::string msg; 2691 if (message != nullptr) 2692 { 2693 msg = message->message; 2694 2695 // fill in this post code value 2696 int i = 0; 2697 for (const std::string &messageArg : messageArgs) 2698 { 2699 std::string argStr = "%" + std::to_string(++i); 2700 size_t argPos = msg.find(argStr); 2701 if (argPos != std::string::npos) 2702 { 2703 msg.replace(argPos, argStr.length(), messageArg); 2704 } 2705 } 2706 } 2707 2708 // Get Severity template from message registry 2709 std::string severity; 2710 if (message != nullptr) 2711 { 2712 severity = message->severity; 2713 } 2714 2715 // add to AsyncResp 2716 logEntryArray.push_back({}); 2717 nlohmann::json &bmcLogEntry = logEntryArray.back(); 2718 bmcLogEntry = { 2719 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2720 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 2721 {"@odata.id", "/redfish/v1/Systems/system/LogServices/" 2722 "PostCodes/Entries/" + 2723 postcodeEntryID}, 2724 {"Name", "POST Code Log Entry"}, 2725 {"Id", postcodeEntryID}, 2726 {"Message", std::move(msg)}, 2727 {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"}, 2728 {"MessageArgs", std::move(messageArgs)}, 2729 {"EntryType", "Event"}, 2730 {"Severity", std::move(severity)}, 2731 {"Created", std::move(entryTimeStr)}}; 2732 } 2733 } 2734 2735 static void getPostCodeForEntry(std::shared_ptr<AsyncResp> aResp, 2736 const uint16_t bootIndex, 2737 const uint64_t codeIndex) 2738 { 2739 crow::connections::systemBus->async_method_call( 2740 [aResp, bootIndex, codeIndex]( 2741 const boost::system::error_code ec, 2742 const boost::container::flat_map<uint64_t, uint64_t> &postcode) { 2743 if (ec) 2744 { 2745 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2746 messages::internalError(aResp->res); 2747 return; 2748 } 2749 2750 // skip the empty postcode boots 2751 if (postcode.empty()) 2752 { 2753 return; 2754 } 2755 2756 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 2757 2758 aResp->res.jsonValue["Members@odata.count"] = 2759 aResp->res.jsonValue["Members"].size(); 2760 }, 2761 "xyz.openbmc_project.State.Boot.PostCode", 2762 "/xyz/openbmc_project/State/Boot/PostCode", 2763 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2764 bootIndex); 2765 } 2766 2767 static void getPostCodeForBoot(std::shared_ptr<AsyncResp> aResp, 2768 const uint16_t bootIndex, 2769 const uint16_t bootCount, 2770 const uint64_t entryCount, const uint64_t skip, 2771 const uint64_t top) 2772 { 2773 crow::connections::systemBus->async_method_call( 2774 [aResp, bootIndex, bootCount, entryCount, skip, 2775 top](const boost::system::error_code ec, 2776 const boost::container::flat_map<uint64_t, uint64_t> &postcode) { 2777 if (ec) 2778 { 2779 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2780 messages::internalError(aResp->res); 2781 return; 2782 } 2783 2784 uint64_t endCount = entryCount; 2785 if (!postcode.empty()) 2786 { 2787 endCount = entryCount + postcode.size(); 2788 2789 if ((skip < endCount) && ((top + skip) > entryCount)) 2790 { 2791 uint64_t thisBootSkip = 2792 std::max(skip, entryCount) - entryCount; 2793 uint64_t thisBootTop = 2794 std::min(top + skip, endCount) - entryCount; 2795 2796 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 2797 thisBootSkip, thisBootTop); 2798 } 2799 aResp->res.jsonValue["Members@odata.count"] = endCount; 2800 } 2801 2802 // continue to previous bootIndex 2803 if (bootIndex < bootCount) 2804 { 2805 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 2806 bootCount, endCount, skip, top); 2807 } 2808 else 2809 { 2810 aResp->res.jsonValue["Members@odata.nextLink"] = 2811 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2812 "Entries?$skip=" + 2813 std::to_string(skip + top); 2814 } 2815 }, 2816 "xyz.openbmc_project.State.Boot.PostCode", 2817 "/xyz/openbmc_project/State/Boot/PostCode", 2818 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2819 bootIndex); 2820 } 2821 2822 static void getCurrentBootNumber(std::shared_ptr<AsyncResp> aResp, 2823 const uint64_t skip, const uint64_t top) 2824 { 2825 uint64_t entryCount = 0; 2826 crow::connections::systemBus->async_method_call( 2827 [aResp, entryCount, skip, 2828 top](const boost::system::error_code ec, 2829 const std::variant<uint16_t> &bootCount) { 2830 if (ec) 2831 { 2832 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 2833 messages::internalError(aResp->res); 2834 return; 2835 } 2836 auto pVal = std::get_if<uint16_t>(&bootCount); 2837 if (pVal) 2838 { 2839 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top); 2840 } 2841 else 2842 { 2843 BMCWEB_LOG_DEBUG << "Post code boot index failed."; 2844 } 2845 }, 2846 "xyz.openbmc_project.State.Boot.PostCode", 2847 "/xyz/openbmc_project/State/Boot/PostCode", 2848 "org.freedesktop.DBus.Properties", "Get", 2849 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount"); 2850 } 2851 2852 class PostCodesEntryCollection : public Node 2853 { 2854 public: 2855 template <typename CrowApp> 2856 PostCodesEntryCollection(CrowApp &app) : 2857 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 2858 { 2859 entityPrivileges = { 2860 {boost::beast::http::verb::get, {{"Login"}}}, 2861 {boost::beast::http::verb::head, {{"Login"}}}, 2862 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2863 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2864 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2865 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2866 } 2867 2868 private: 2869 void doGet(crow::Response &res, const crow::Request &req, 2870 const std::vector<std::string> ¶ms) override 2871 { 2872 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2873 2874 asyncResp->res.jsonValue["@odata.type"] = 2875 "#LogEntryCollection.LogEntryCollection"; 2876 asyncResp->res.jsonValue["@odata.context"] = 2877 "/redfish/v1/" 2878 "$metadata#LogEntryCollection.LogEntryCollection"; 2879 asyncResp->res.jsonValue["@odata.id"] = 2880 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 2881 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 2882 asyncResp->res.jsonValue["Description"] = 2883 "Collection of POST Code Log Entries"; 2884 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 2885 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2886 2887 uint64_t skip = 0; 2888 uint64_t top = maxEntriesPerPage; // Show max entries by default 2889 if (!getSkipParam(asyncResp->res, req, skip)) 2890 { 2891 return; 2892 } 2893 if (!getTopParam(asyncResp->res, req, top)) 2894 { 2895 return; 2896 } 2897 getCurrentBootNumber(asyncResp, skip, top); 2898 } 2899 }; 2900 2901 class PostCodesEntry : public Node 2902 { 2903 public: 2904 PostCodesEntry(CrowApp &app) : 2905 Node(app, 2906 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/", 2907 std::string()) 2908 { 2909 entityPrivileges = { 2910 {boost::beast::http::verb::get, {{"Login"}}}, 2911 {boost::beast::http::verb::head, {{"Login"}}}, 2912 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2913 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2914 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2915 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2916 } 2917 2918 private: 2919 void doGet(crow::Response &res, const crow::Request &req, 2920 const std::vector<std::string> ¶ms) override 2921 { 2922 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2923 if (params.size() != 1) 2924 { 2925 messages::internalError(asyncResp->res); 2926 return; 2927 } 2928 2929 const std::string &targetID = params[0]; 2930 2931 size_t bootPos = targetID.find('B'); 2932 if (bootPos == std::string::npos) 2933 { 2934 // Requested ID was not found 2935 messages::resourceMissingAtURI(asyncResp->res, targetID); 2936 return; 2937 } 2938 std::string_view bootIndexStr(targetID); 2939 bootIndexStr.remove_prefix(bootPos + 1); 2940 uint16_t bootIndex = 0; 2941 uint64_t codeIndex = 0; 2942 size_t dashPos = bootIndexStr.find('-'); 2943 2944 if (dashPos == std::string::npos) 2945 { 2946 return; 2947 } 2948 std::string_view codeIndexStr(bootIndexStr); 2949 bootIndexStr.remove_suffix(dashPos); 2950 codeIndexStr.remove_prefix(dashPos + 1); 2951 2952 bootIndex = static_cast<uint16_t>( 2953 strtoul(std::string(bootIndexStr).c_str(), NULL, 0)); 2954 codeIndex = strtoul(std::string(codeIndexStr).c_str(), NULL, 0); 2955 if (bootIndex == 0 || codeIndex == 0) 2956 { 2957 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 2958 << params[0]; 2959 } 2960 2961 asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry"; 2962 asyncResp->res.jsonValue["@odata.context"] = 2963 "/redfish/v1/$metadata#LogEntry.LogEntry"; 2964 asyncResp->res.jsonValue["@odata.id"] = 2965 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2966 "Entries"; 2967 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 2968 asyncResp->res.jsonValue["Description"] = 2969 "Collection of POST Code Log Entries"; 2970 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 2971 asyncResp->res.jsonValue["Members@odata.count"] = 0; 2972 2973 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 2974 } 2975 }; 2976 2977 } // namespace redfish 2978