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