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 23 #include <systemd/sd-journal.h> 24 25 #include <boost/algorithm/string/split.hpp> 26 #include <boost/beast/core/span.hpp> 27 #include <boost/container/flat_map.hpp> 28 #include <boost/system/linux_error.hpp> 29 #include <error_messages.hpp> 30 #include <filesystem> 31 #include <string_view> 32 #include <variant> 33 34 namespace redfish 35 { 36 37 constexpr char const *crashdumpObject = "com.intel.crashdump"; 38 constexpr char const *crashdumpPath = "/com/intel/crashdump"; 39 constexpr char const *crashdumpOnDemandPath = "/com/intel/crashdump/OnDemand"; 40 constexpr char const *crashdumpInterface = "com.intel.crashdump"; 41 constexpr char const *deleteAllInterface = 42 "xyz.openbmc_project.Collection.DeleteAll"; 43 constexpr char const *crashdumpOnDemandInterface = 44 "com.intel.crashdump.OnDemand"; 45 constexpr char const *crashdumpRawPECIInterface = 46 "com.intel.crashdump.SendRawPeci"; 47 48 namespace message_registries 49 { 50 static const Message *getMessageFromRegistry( 51 const std::string &messageKey, 52 const boost::beast::span<const MessageEntry> registry) 53 { 54 boost::beast::span<const MessageEntry>::const_iterator messageIt = 55 std::find_if(registry.cbegin(), registry.cend(), 56 [&messageKey](const MessageEntry &messageEntry) { 57 return !std::strcmp(messageEntry.first, 58 messageKey.c_str()); 59 }); 60 if (messageIt != registry.cend()) 61 { 62 return &messageIt->second; 63 } 64 65 return nullptr; 66 } 67 68 static const Message *getMessage(const std::string_view &messageID) 69 { 70 // Redfish MessageIds are in the form 71 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 72 // the right Message 73 std::vector<std::string> fields; 74 fields.reserve(4); 75 boost::split(fields, messageID, boost::is_any_of(".")); 76 std::string ®istryName = fields[0]; 77 std::string &messageKey = fields[3]; 78 79 // Find the right registry and check it for the MessageKey 80 if (std::string(base::header.registryPrefix) == registryName) 81 { 82 return getMessageFromRegistry( 83 messageKey, boost::beast::span<const MessageEntry>(base::registry)); 84 } 85 if (std::string(openbmc::header.registryPrefix) == registryName) 86 { 87 return getMessageFromRegistry( 88 messageKey, 89 boost::beast::span<const MessageEntry>(openbmc::registry)); 90 } 91 return nullptr; 92 } 93 } // namespace message_registries 94 95 namespace fs = std::filesystem; 96 97 using GetManagedPropertyType = boost::container::flat_map< 98 std::string, 99 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 100 int32_t, uint32_t, int64_t, uint64_t, double>>; 101 102 using GetManagedObjectsType = boost::container::flat_map< 103 sdbusplus::message::object_path, 104 boost::container::flat_map<std::string, GetManagedPropertyType>>; 105 106 inline std::string translateSeverityDbusToRedfish(const std::string &s) 107 { 108 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") 109 { 110 return "Critical"; 111 } 112 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") 113 { 114 return "Critical"; 115 } 116 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") 117 { 118 return "OK"; 119 } 120 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") 121 { 122 return "Critical"; 123 } 124 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") 125 { 126 return "Critical"; 127 } 128 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") 129 { 130 return "OK"; 131 } 132 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") 133 { 134 return "OK"; 135 } 136 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 137 { 138 return "Warning"; 139 } 140 return ""; 141 } 142 143 static int getJournalMetadata(sd_journal *journal, 144 const std::string_view &field, 145 std::string_view &contents) 146 { 147 const char *data = nullptr; 148 size_t length = 0; 149 int ret = 0; 150 // Get the metadata from the requested field of the journal entry 151 ret = sd_journal_get_data(journal, field.data(), 152 reinterpret_cast<const void **>(&data), &length); 153 if (ret < 0) 154 { 155 return ret; 156 } 157 contents = std::string_view(data, length); 158 // Only use the content after the "=" character. 159 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 160 return ret; 161 } 162 163 static int getJournalMetadata(sd_journal *journal, 164 const std::string_view &field, const int &base, 165 long int &contents) 166 { 167 int ret = 0; 168 std::string_view metadata; 169 // Get the metadata from the requested field of the journal entry 170 ret = getJournalMetadata(journal, field, metadata); 171 if (ret < 0) 172 { 173 return ret; 174 } 175 contents = strtol(metadata.data(), nullptr, base); 176 return ret; 177 } 178 179 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 180 { 181 int ret = 0; 182 uint64_t timestamp = 0; 183 ret = sd_journal_get_realtime_usec(journal, ×tamp); 184 if (ret < 0) 185 { 186 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 187 << strerror(-ret); 188 return false; 189 } 190 time_t t = 191 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 192 struct tm *loctime = localtime(&t); 193 char entryTime[64] = {}; 194 if (nullptr != loctime) 195 { 196 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 197 } 198 // Insert the ':' into the timezone 199 std::string_view t1(entryTime); 200 std::string_view t2(entryTime); 201 if (t1.size() > 2 && t2.size() > 2) 202 { 203 t1.remove_suffix(2); 204 t2.remove_prefix(t2.size() - 2); 205 } 206 entryTimestamp = std::string(t1) + ":" + std::string(t2); 207 return true; 208 } 209 210 static bool getSkipParam(crow::Response &res, const crow::Request &req, 211 uint64_t &skip) 212 { 213 char *skipParam = req.urlParams.get("$skip"); 214 if (skipParam != nullptr) 215 { 216 char *ptr = nullptr; 217 skip = std::strtoul(skipParam, &ptr, 10); 218 if (*skipParam == '\0' || *ptr != '\0') 219 { 220 221 messages::queryParameterValueTypeError(res, std::string(skipParam), 222 "$skip"); 223 return false; 224 } 225 } 226 return true; 227 } 228 229 static constexpr const uint64_t maxEntriesPerPage = 1000; 230 static bool getTopParam(crow::Response &res, const crow::Request &req, 231 uint64_t &top) 232 { 233 char *topParam = req.urlParams.get("$top"); 234 if (topParam != nullptr) 235 { 236 char *ptr = nullptr; 237 top = std::strtoul(topParam, &ptr, 10); 238 if (*topParam == '\0' || *ptr != '\0') 239 { 240 messages::queryParameterValueTypeError(res, std::string(topParam), 241 "$top"); 242 return false; 243 } 244 if (top < 1U || top > maxEntriesPerPage) 245 { 246 247 messages::queryParameterOutOfRange( 248 res, std::to_string(top), "$top", 249 "1-" + std::to_string(maxEntriesPerPage)); 250 return false; 251 } 252 } 253 return true; 254 } 255 256 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID, 257 const bool firstEntry = true) 258 { 259 int ret = 0; 260 static uint64_t prevTs = 0; 261 static int index = 0; 262 if (firstEntry) 263 { 264 prevTs = 0; 265 } 266 267 // Get the entry timestamp 268 uint64_t curTs = 0; 269 ret = sd_journal_get_realtime_usec(journal, &curTs); 270 if (ret < 0) 271 { 272 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 273 << strerror(-ret); 274 return false; 275 } 276 // If the timestamp isn't unique, increment the index 277 if (curTs == prevTs) 278 { 279 index++; 280 } 281 else 282 { 283 // Otherwise, reset it 284 index = 0; 285 } 286 // Save the timestamp 287 prevTs = curTs; 288 289 entryID = std::to_string(curTs); 290 if (index > 0) 291 { 292 entryID += "_" + std::to_string(index); 293 } 294 return true; 295 } 296 297 static bool getUniqueEntryID(const std::string &logEntry, std::string &entryID, 298 const bool firstEntry = true) 299 { 300 static time_t prevTs = 0; 301 static int index = 0; 302 if (firstEntry) 303 { 304 prevTs = 0; 305 } 306 307 // Get the entry timestamp 308 std::time_t curTs = 0; 309 std::tm timeStruct = {}; 310 std::istringstream entryStream(logEntry); 311 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 312 { 313 curTs = std::mktime(&timeStruct); 314 } 315 // If the timestamp isn't unique, increment the index 316 if (curTs == prevTs) 317 { 318 index++; 319 } 320 else 321 { 322 // Otherwise, reset it 323 index = 0; 324 } 325 // Save the timestamp 326 prevTs = curTs; 327 328 entryID = std::to_string(curTs); 329 if (index > 0) 330 { 331 entryID += "_" + std::to_string(index); 332 } 333 return true; 334 } 335 336 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 337 uint64_t ×tamp, uint64_t &index) 338 { 339 if (entryID.empty()) 340 { 341 return false; 342 } 343 // Convert the unique ID back to a timestamp to find the entry 344 std::string_view tsStr(entryID); 345 346 auto underscorePos = tsStr.find("_"); 347 if (underscorePos != tsStr.npos) 348 { 349 // Timestamp has an index 350 tsStr.remove_suffix(tsStr.size() - underscorePos); 351 std::string_view indexStr(entryID); 352 indexStr.remove_prefix(underscorePos + 1); 353 std::size_t pos; 354 try 355 { 356 index = std::stoul(std::string(indexStr), &pos); 357 } 358 catch (std::invalid_argument &) 359 { 360 messages::resourceMissingAtURI(res, entryID); 361 return false; 362 } 363 catch (std::out_of_range &) 364 { 365 messages::resourceMissingAtURI(res, entryID); 366 return false; 367 } 368 if (pos != indexStr.size()) 369 { 370 messages::resourceMissingAtURI(res, entryID); 371 return false; 372 } 373 } 374 // Timestamp has no index 375 std::size_t pos; 376 try 377 { 378 timestamp = std::stoull(std::string(tsStr), &pos); 379 } 380 catch (std::invalid_argument &) 381 { 382 messages::resourceMissingAtURI(res, entryID); 383 return false; 384 } 385 catch (std::out_of_range &) 386 { 387 messages::resourceMissingAtURI(res, entryID); 388 return false; 389 } 390 if (pos != tsStr.size()) 391 { 392 messages::resourceMissingAtURI(res, entryID); 393 return false; 394 } 395 return true; 396 } 397 398 static bool 399 getRedfishLogFiles(std::vector<std::filesystem::path> &redfishLogFiles) 400 { 401 static const std::filesystem::path redfishLogDir = "/var/log"; 402 static const std::string redfishLogFilename = "redfish"; 403 404 // Loop through the directory looking for redfish log files 405 for (const std::filesystem::directory_entry &dirEnt : 406 std::filesystem::directory_iterator(redfishLogDir)) 407 { 408 // If we find a redfish log file, save the path 409 std::string filename = dirEnt.path().filename(); 410 if (boost::starts_with(filename, redfishLogFilename)) 411 { 412 redfishLogFiles.emplace_back(redfishLogDir / filename); 413 } 414 } 415 // As the log files rotate, they are appended with a ".#" that is higher for 416 // the older logs. Since we don't expect more than 10 log files, we 417 // can just sort the list to get them in order from newest to oldest 418 std::sort(redfishLogFiles.begin(), redfishLogFiles.end()); 419 420 return !redfishLogFiles.empty(); 421 } 422 423 class SystemLogServiceCollection : public Node 424 { 425 public: 426 template <typename CrowApp> 427 SystemLogServiceCollection(CrowApp &app) : 428 Node(app, "/redfish/v1/Systems/system/LogServices/") 429 { 430 entityPrivileges = { 431 {boost::beast::http::verb::get, {{"Login"}}}, 432 {boost::beast::http::verb::head, {{"Login"}}}, 433 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 434 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 435 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 436 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 437 } 438 439 private: 440 /** 441 * Functions triggers appropriate requests on DBus 442 */ 443 void doGet(crow::Response &res, const crow::Request &req, 444 const std::vector<std::string> ¶ms) override 445 { 446 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 447 // Collections don't include the static data added by SubRoute because 448 // it has a duplicate entry for members 449 asyncResp->res.jsonValue["@odata.type"] = 450 "#LogServiceCollection.LogServiceCollection"; 451 asyncResp->res.jsonValue["@odata.context"] = 452 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 453 asyncResp->res.jsonValue["@odata.id"] = 454 "/redfish/v1/Systems/system/LogServices"; 455 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 456 asyncResp->res.jsonValue["Description"] = 457 "Collection of LogServices for this Computer System"; 458 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 459 logServiceArray = nlohmann::json::array(); 460 logServiceArray.push_back( 461 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 462 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 463 logServiceArray.push_back( 464 {{"@odata.id", 465 "/redfish/v1/Systems/system/LogServices/Crashdump"}}); 466 #endif 467 asyncResp->res.jsonValue["Members@odata.count"] = 468 logServiceArray.size(); 469 } 470 }; 471 472 class EventLogService : public Node 473 { 474 public: 475 template <typename CrowApp> 476 EventLogService(CrowApp &app) : 477 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 478 { 479 entityPrivileges = { 480 {boost::beast::http::verb::get, {{"Login"}}}, 481 {boost::beast::http::verb::head, {{"Login"}}}, 482 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 483 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 484 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 485 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 486 } 487 488 private: 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 494 asyncResp->res.jsonValue["@odata.id"] = 495 "/redfish/v1/Systems/system/LogServices/EventLog"; 496 asyncResp->res.jsonValue["@odata.type"] = 497 "#LogService.v1_1_0.LogService"; 498 asyncResp->res.jsonValue["@odata.context"] = 499 "/redfish/v1/$metadata#LogService.LogService"; 500 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 501 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 502 asyncResp->res.jsonValue["Id"] = "Event Log"; 503 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 504 asyncResp->res.jsonValue["Entries"] = { 505 {"@odata.id", 506 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 507 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 508 509 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 510 "Actions/LogService.ClearLog"}}; 511 } 512 }; 513 514 class JournalEventLogClear : public Node 515 { 516 public: 517 JournalEventLogClear(CrowApp &app) : 518 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 519 "LogService.ClearLog/") 520 { 521 entityPrivileges = { 522 {boost::beast::http::verb::get, {{"Login"}}}, 523 {boost::beast::http::verb::head, {{"Login"}}}, 524 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 525 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 526 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 527 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 528 } 529 530 private: 531 void doPost(crow::Response &res, const crow::Request &req, 532 const std::vector<std::string> ¶ms) override 533 { 534 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 535 536 // Clear the EventLog by deleting the log files 537 std::vector<std::filesystem::path> redfishLogFiles; 538 if (getRedfishLogFiles(redfishLogFiles)) 539 { 540 for (const std::filesystem::path &file : redfishLogFiles) 541 { 542 std::error_code ec; 543 std::filesystem::remove(file, ec); 544 } 545 } 546 547 // Reload rsyslog so it knows to start new log files 548 crow::connections::systemBus->async_method_call( 549 [asyncResp](const boost::system::error_code ec) { 550 if (ec) 551 { 552 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec; 553 messages::internalError(asyncResp->res); 554 return; 555 } 556 557 messages::success(asyncResp->res); 558 }, 559 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 560 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 561 "replace"); 562 } 563 }; 564 565 static int fillEventLogEntryJson(const std::string &logEntryID, 566 const std::string logEntry, 567 nlohmann::json &logEntryJson) 568 { 569 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 570 // First get the Timestamp 571 size_t space = logEntry.find_first_of(" "); 572 if (space == std::string::npos) 573 { 574 return 1; 575 } 576 std::string timestamp = logEntry.substr(0, space); 577 // Then get the log contents 578 size_t entryStart = logEntry.find_first_not_of(" ", space); 579 if (entryStart == std::string::npos) 580 { 581 return 1; 582 } 583 std::string_view entry(logEntry); 584 entry.remove_prefix(entryStart); 585 // Use split to separate the entry into its fields 586 std::vector<std::string> logEntryFields; 587 boost::split(logEntryFields, entry, boost::is_any_of(","), 588 boost::token_compress_on); 589 // We need at least a MessageId to be valid 590 if (logEntryFields.size() < 1) 591 { 592 return 1; 593 } 594 std::string &messageID = logEntryFields[0]; 595 596 // Get the Message from the MessageRegistry 597 const message_registries::Message *message = 598 message_registries::getMessage(messageID); 599 600 std::string msg; 601 std::string severity; 602 if (message != nullptr) 603 { 604 msg = message->message; 605 severity = message->severity; 606 } 607 608 // Get the MessageArgs from the log if there are any 609 boost::beast::span<std::string> messageArgs; 610 if (logEntryFields.size() > 1) 611 { 612 std::string &messageArgsStart = logEntryFields[1]; 613 // If the first string is empty, assume there are no MessageArgs 614 std::size_t messageArgsSize = 0; 615 if (!messageArgsStart.empty()) 616 { 617 messageArgsSize = logEntryFields.size() - 1; 618 } 619 620 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize); 621 622 // Fill the MessageArgs into the Message 623 int i = 0; 624 for (const std::string &messageArg : messageArgs) 625 { 626 std::string argStr = "%" + std::to_string(++i); 627 size_t argPos = msg.find(argStr); 628 if (argPos != std::string::npos) 629 { 630 msg.replace(argPos, argStr.length(), messageArg); 631 } 632 } 633 } 634 635 // Get the Created time from the timestamp. The log timestamp is in RFC3339 636 // format which matches the Redfish format except for the fractional seconds 637 // between the '.' and the '+', so just remove them. 638 std::size_t dot = timestamp.find_first_of("."); 639 std::size_t plus = timestamp.find_first_of("+"); 640 if (dot != std::string::npos && plus != std::string::npos) 641 { 642 timestamp.erase(dot, plus - dot); 643 } 644 645 // Fill in the log entry with the gathered data 646 logEntryJson = { 647 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 648 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 649 {"@odata.id", 650 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 651 logEntryID}, 652 {"Name", "System Event Log Entry"}, 653 {"Id", logEntryID}, 654 {"Message", std::move(msg)}, 655 {"MessageId", std::move(messageID)}, 656 {"MessageArgs", std::move(messageArgs)}, 657 {"EntryType", "Event"}, 658 {"Severity", std::move(severity)}, 659 {"Created", std::move(timestamp)}}; 660 return 0; 661 } 662 663 class JournalEventLogEntryCollection : public Node 664 { 665 public: 666 template <typename CrowApp> 667 JournalEventLogEntryCollection(CrowApp &app) : 668 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 669 { 670 entityPrivileges = { 671 {boost::beast::http::verb::get, {{"Login"}}}, 672 {boost::beast::http::verb::head, {{"Login"}}}, 673 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 674 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 675 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 676 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 677 } 678 679 private: 680 void doGet(crow::Response &res, const crow::Request &req, 681 const std::vector<std::string> ¶ms) override 682 { 683 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 684 uint64_t skip = 0; 685 uint64_t top = maxEntriesPerPage; // Show max entries by default 686 if (!getSkipParam(asyncResp->res, req, skip)) 687 { 688 return; 689 } 690 if (!getTopParam(asyncResp->res, req, top)) 691 { 692 return; 693 } 694 // Collections don't include the static data added by SubRoute because 695 // it has a duplicate entry for members 696 asyncResp->res.jsonValue["@odata.type"] = 697 "#LogEntryCollection.LogEntryCollection"; 698 asyncResp->res.jsonValue["@odata.context"] = 699 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 700 asyncResp->res.jsonValue["@odata.id"] = 701 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 702 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 703 asyncResp->res.jsonValue["Description"] = 704 "Collection of System Event Log Entries"; 705 706 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 707 logEntryArray = nlohmann::json::array(); 708 // Go through the log files and create a unique ID for each entry 709 std::vector<std::filesystem::path> redfishLogFiles; 710 getRedfishLogFiles(redfishLogFiles); 711 uint64_t entryCount = 0; 712 std::string logEntry; 713 714 // Oldest logs are in the last file, so start there and loop backwards 715 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 716 it++) 717 { 718 std::ifstream logStream(*it); 719 if (!logStream.is_open()) 720 { 721 continue; 722 } 723 724 // Reset the unique ID on the first entry 725 bool firstEntry = true; 726 while (std::getline(logStream, logEntry)) 727 { 728 entryCount++; 729 // Handle paging using skip (number of entries to skip from the 730 // start) and top (number of entries to display) 731 if (entryCount <= skip || entryCount > skip + top) 732 { 733 continue; 734 } 735 736 std::string idStr; 737 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 738 { 739 continue; 740 } 741 742 if (firstEntry) 743 { 744 firstEntry = false; 745 } 746 747 logEntryArray.push_back({}); 748 nlohmann::json &bmcLogEntry = logEntryArray.back(); 749 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 750 { 751 messages::internalError(asyncResp->res); 752 return; 753 } 754 } 755 } 756 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 757 if (skip + top < entryCount) 758 { 759 asyncResp->res.jsonValue["Members@odata.nextLink"] = 760 "/redfish/v1/Systems/system/LogServices/EventLog/" 761 "Entries?$skip=" + 762 std::to_string(skip + top); 763 } 764 } 765 }; 766 767 class JournalEventLogEntry : public Node 768 { 769 public: 770 JournalEventLogEntry(CrowApp &app) : 771 Node(app, 772 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 773 std::string()) 774 { 775 entityPrivileges = { 776 {boost::beast::http::verb::get, {{"Login"}}}, 777 {boost::beast::http::verb::head, {{"Login"}}}, 778 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 779 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 780 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 781 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 782 } 783 784 private: 785 void doGet(crow::Response &res, const crow::Request &req, 786 const std::vector<std::string> ¶ms) override 787 { 788 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 789 if (params.size() != 1) 790 { 791 messages::internalError(asyncResp->res); 792 return; 793 } 794 const std::string &targetID = params[0]; 795 796 // Go through the log files and check the unique ID for each entry to 797 // find the target entry 798 std::vector<std::filesystem::path> redfishLogFiles; 799 getRedfishLogFiles(redfishLogFiles); 800 std::string logEntry; 801 802 // Oldest logs are in the last file, so start there and loop backwards 803 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 804 it++) 805 { 806 std::ifstream logStream(*it); 807 if (!logStream.is_open()) 808 { 809 continue; 810 } 811 812 // Reset the unique ID on the first entry 813 bool firstEntry = true; 814 while (std::getline(logStream, logEntry)) 815 { 816 std::string idStr; 817 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 818 { 819 continue; 820 } 821 822 if (firstEntry) 823 { 824 firstEntry = false; 825 } 826 827 if (idStr == targetID) 828 { 829 if (fillEventLogEntryJson(idStr, logEntry, 830 asyncResp->res.jsonValue) != 0) 831 { 832 messages::internalError(asyncResp->res); 833 return; 834 } 835 return; 836 } 837 } 838 } 839 // Requested ID was not found 840 messages::resourceMissingAtURI(asyncResp->res, targetID); 841 } 842 }; 843 844 class DBusEventLogEntryCollection : public Node 845 { 846 public: 847 template <typename CrowApp> 848 DBusEventLogEntryCollection(CrowApp &app) : 849 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 850 { 851 entityPrivileges = { 852 {boost::beast::http::verb::get, {{"Login"}}}, 853 {boost::beast::http::verb::head, {{"Login"}}}, 854 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 855 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 856 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 857 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 858 } 859 860 private: 861 void doGet(crow::Response &res, const crow::Request &req, 862 const std::vector<std::string> ¶ms) override 863 { 864 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 865 866 // Collections don't include the static data added by SubRoute because 867 // it has a duplicate entry for members 868 asyncResp->res.jsonValue["@odata.type"] = 869 "#LogEntryCollection.LogEntryCollection"; 870 asyncResp->res.jsonValue["@odata.context"] = 871 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 872 asyncResp->res.jsonValue["@odata.id"] = 873 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 874 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 875 asyncResp->res.jsonValue["Description"] = 876 "Collection of System Event Log Entries"; 877 878 // DBus implementation of EventLog/Entries 879 // Make call to Logging Service to find all log entry objects 880 crow::connections::systemBus->async_method_call( 881 [asyncResp](const boost::system::error_code ec, 882 GetManagedObjectsType &resp) { 883 if (ec) 884 { 885 // TODO Handle for specific error code 886 BMCWEB_LOG_ERROR 887 << "getLogEntriesIfaceData resp_handler got error " 888 << ec; 889 messages::internalError(asyncResp->res); 890 return; 891 } 892 nlohmann::json &entriesArray = 893 asyncResp->res.jsonValue["Members"]; 894 entriesArray = nlohmann::json::array(); 895 for (auto &objectPath : resp) 896 { 897 for (auto &interfaceMap : objectPath.second) 898 { 899 if (interfaceMap.first != 900 "xyz.openbmc_project.Logging.Entry") 901 { 902 BMCWEB_LOG_DEBUG << "Bailing early on " 903 << interfaceMap.first; 904 continue; 905 } 906 entriesArray.push_back({}); 907 nlohmann::json &thisEntry = entriesArray.back(); 908 uint32_t *id = nullptr; 909 std::time_t timestamp{}; 910 std::string *severity = nullptr; 911 std::string *message = nullptr; 912 for (auto &propertyMap : interfaceMap.second) 913 { 914 if (propertyMap.first == "Id") 915 { 916 id = sdbusplus::message::variant_ns::get_if< 917 uint32_t>(&propertyMap.second); 918 if (id == nullptr) 919 { 920 messages::propertyMissing(asyncResp->res, 921 "Id"); 922 } 923 } 924 else if (propertyMap.first == "Timestamp") 925 { 926 const uint64_t *millisTimeStamp = 927 std::get_if<uint64_t>(&propertyMap.second); 928 if (millisTimeStamp == nullptr) 929 { 930 messages::propertyMissing(asyncResp->res, 931 "Timestamp"); 932 continue; 933 } 934 // Retrieve Created property with format: 935 // yyyy-mm-ddThh:mm:ss 936 std::chrono::milliseconds chronoTimeStamp( 937 *millisTimeStamp); 938 timestamp = std::chrono::duration_cast< 939 std::chrono::duration<int>>( 940 chronoTimeStamp) 941 .count(); 942 } 943 else if (propertyMap.first == "Severity") 944 { 945 severity = std::get_if<std::string>( 946 &propertyMap.second); 947 if (severity == nullptr) 948 { 949 messages::propertyMissing(asyncResp->res, 950 "Severity"); 951 } 952 } 953 else if (propertyMap.first == "Message") 954 { 955 message = std::get_if<std::string>( 956 &propertyMap.second); 957 if (message == nullptr) 958 { 959 messages::propertyMissing(asyncResp->res, 960 "Message"); 961 } 962 } 963 } 964 thisEntry = { 965 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 966 {"@odata.context", "/redfish/v1/" 967 "$metadata#LogEntry.LogEntry"}, 968 {"@odata.id", 969 "/redfish/v1/Systems/system/LogServices/EventLog/" 970 "Entries/" + 971 std::to_string(*id)}, 972 {"Name", "System Event Log Entry"}, 973 {"Id", std::to_string(*id)}, 974 {"Message", *message}, 975 {"EntryType", "Event"}, 976 {"Severity", 977 translateSeverityDbusToRedfish(*severity)}, 978 {"Created", crow::utility::getDateTime(timestamp)}}; 979 } 980 } 981 std::sort(entriesArray.begin(), entriesArray.end(), 982 [](const nlohmann::json &left, 983 const nlohmann::json &right) { 984 return (left["Id"] <= right["Id"]); 985 }); 986 asyncResp->res.jsonValue["Members@odata.count"] = 987 entriesArray.size(); 988 }, 989 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 990 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 991 } 992 }; 993 994 class DBusEventLogEntry : public Node 995 { 996 public: 997 DBusEventLogEntry(CrowApp &app) : 998 Node(app, 999 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 1000 std::string()) 1001 { 1002 entityPrivileges = { 1003 {boost::beast::http::verb::get, {{"Login"}}}, 1004 {boost::beast::http::verb::head, {{"Login"}}}, 1005 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1006 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1007 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1008 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1009 } 1010 1011 private: 1012 void doGet(crow::Response &res, const crow::Request &req, 1013 const std::vector<std::string> ¶ms) override 1014 { 1015 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1016 if (params.size() != 1) 1017 { 1018 messages::internalError(asyncResp->res); 1019 return; 1020 } 1021 const std::string &entryID = params[0]; 1022 1023 // DBus implementation of EventLog/Entries 1024 // Make call to Logging Service to find all log entry objects 1025 crow::connections::systemBus->async_method_call( 1026 [asyncResp, entryID](const boost::system::error_code ec, 1027 GetManagedPropertyType &resp) { 1028 if (ec) 1029 { 1030 BMCWEB_LOG_ERROR 1031 << "EventLogEntry (DBus) resp_handler got error " << ec; 1032 messages::internalError(asyncResp->res); 1033 return; 1034 } 1035 uint32_t *id = nullptr; 1036 std::time_t timestamp{}; 1037 std::string *severity = nullptr; 1038 std::string *message = nullptr; 1039 for (auto &propertyMap : resp) 1040 { 1041 if (propertyMap.first == "Id") 1042 { 1043 id = std::get_if<uint32_t>(&propertyMap.second); 1044 if (id == nullptr) 1045 { 1046 messages::propertyMissing(asyncResp->res, "Id"); 1047 } 1048 } 1049 else if (propertyMap.first == "Timestamp") 1050 { 1051 const uint64_t *millisTimeStamp = 1052 std::get_if<uint64_t>(&propertyMap.second); 1053 if (millisTimeStamp == nullptr) 1054 { 1055 messages::propertyMissing(asyncResp->res, 1056 "Timestamp"); 1057 continue; 1058 } 1059 // Retrieve Created property with format: 1060 // yyyy-mm-ddThh:mm:ss 1061 std::chrono::milliseconds chronoTimeStamp( 1062 *millisTimeStamp); 1063 timestamp = 1064 std::chrono::duration_cast< 1065 std::chrono::duration<int>>(chronoTimeStamp) 1066 .count(); 1067 } 1068 else if (propertyMap.first == "Severity") 1069 { 1070 severity = 1071 std::get_if<std::string>(&propertyMap.second); 1072 if (severity == nullptr) 1073 { 1074 messages::propertyMissing(asyncResp->res, 1075 "Severity"); 1076 } 1077 } 1078 else if (propertyMap.first == "Message") 1079 { 1080 message = std::get_if<std::string>(&propertyMap.second); 1081 if (message == nullptr) 1082 { 1083 messages::propertyMissing(asyncResp->res, 1084 "Message"); 1085 } 1086 } 1087 } 1088 if (id == nullptr || message == nullptr || severity == nullptr) 1089 { 1090 return; 1091 } 1092 asyncResp->res.jsonValue = { 1093 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1094 {"@odata.context", "/redfish/v1/" 1095 "$metadata#LogEntry.LogEntry"}, 1096 {"@odata.id", 1097 "/redfish/v1/Systems/system/LogServices/EventLog/" 1098 "Entries/" + 1099 std::to_string(*id)}, 1100 {"Name", "System Event Log Entry"}, 1101 {"Id", std::to_string(*id)}, 1102 {"Message", *message}, 1103 {"EntryType", "Event"}, 1104 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1105 {"Created", crow::utility::getDateTime(timestamp)}}; 1106 }, 1107 "xyz.openbmc_project.Logging", 1108 "/xyz/openbmc_project/logging/entry/" + entryID, 1109 "org.freedesktop.DBus.Properties", "GetAll", 1110 "xyz.openbmc_project.Logging.Entry"); 1111 } 1112 1113 void doDelete(crow::Response &res, const crow::Request &req, 1114 const std::vector<std::string> ¶ms) override 1115 { 1116 1117 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1118 1119 auto asyncResp = std::make_shared<AsyncResp>(res); 1120 1121 if (params.size() != 1) 1122 { 1123 messages::internalError(asyncResp->res); 1124 return; 1125 } 1126 std::string entryID = params[0]; 1127 1128 dbus::utility::escapePathForDbus(entryID); 1129 1130 // Process response from Logging service. 1131 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1132 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1133 if (ec) 1134 { 1135 // TODO Handle for specific error code 1136 BMCWEB_LOG_ERROR 1137 << "EventLogEntry (DBus) doDelete respHandler got error " 1138 << ec; 1139 asyncResp->res.result( 1140 boost::beast::http::status::internal_server_error); 1141 return; 1142 } 1143 1144 asyncResp->res.result(boost::beast::http::status::ok); 1145 }; 1146 1147 // Make call to Logging service to request Delete Log 1148 crow::connections::systemBus->async_method_call( 1149 respHandler, "xyz.openbmc_project.Logging", 1150 "/xyz/openbmc_project/logging/entry/" + entryID, 1151 "xyz.openbmc_project.Object.Delete", "Delete"); 1152 } 1153 }; 1154 1155 class BMCLogServiceCollection : public Node 1156 { 1157 public: 1158 template <typename CrowApp> 1159 BMCLogServiceCollection(CrowApp &app) : 1160 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1161 { 1162 entityPrivileges = { 1163 {boost::beast::http::verb::get, {{"Login"}}}, 1164 {boost::beast::http::verb::head, {{"Login"}}}, 1165 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1166 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1167 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1168 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1169 } 1170 1171 private: 1172 /** 1173 * Functions triggers appropriate requests on DBus 1174 */ 1175 void doGet(crow::Response &res, const crow::Request &req, 1176 const std::vector<std::string> ¶ms) override 1177 { 1178 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1179 // Collections don't include the static data added by SubRoute because 1180 // it has a duplicate entry for members 1181 asyncResp->res.jsonValue["@odata.type"] = 1182 "#LogServiceCollection.LogServiceCollection"; 1183 asyncResp->res.jsonValue["@odata.context"] = 1184 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 1185 asyncResp->res.jsonValue["@odata.id"] = 1186 "/redfish/v1/Managers/bmc/LogServices"; 1187 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1188 asyncResp->res.jsonValue["Description"] = 1189 "Collection of LogServices for this Manager"; 1190 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 1191 logServiceArray = nlohmann::json::array(); 1192 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1193 logServiceArray.push_back( 1194 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1195 #endif 1196 asyncResp->res.jsonValue["Members@odata.count"] = 1197 logServiceArray.size(); 1198 } 1199 }; 1200 1201 class BMCJournalLogService : public Node 1202 { 1203 public: 1204 template <typename CrowApp> 1205 BMCJournalLogService(CrowApp &app) : 1206 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1207 { 1208 entityPrivileges = { 1209 {boost::beast::http::verb::get, {{"Login"}}}, 1210 {boost::beast::http::verb::head, {{"Login"}}}, 1211 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1212 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1213 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1214 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1215 } 1216 1217 private: 1218 void doGet(crow::Response &res, const crow::Request &req, 1219 const std::vector<std::string> ¶ms) override 1220 { 1221 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1222 asyncResp->res.jsonValue["@odata.type"] = 1223 "#LogService.v1_1_0.LogService"; 1224 asyncResp->res.jsonValue["@odata.id"] = 1225 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1226 asyncResp->res.jsonValue["@odata.context"] = 1227 "/redfish/v1/$metadata#LogService.LogService"; 1228 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1229 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1230 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1231 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1232 asyncResp->res.jsonValue["Entries"] = { 1233 {"@odata.id", 1234 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1235 } 1236 }; 1237 1238 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 1239 sd_journal *journal, 1240 nlohmann::json &bmcJournalLogEntryJson) 1241 { 1242 // Get the Log Entry contents 1243 int ret = 0; 1244 1245 std::string_view msg; 1246 ret = getJournalMetadata(journal, "MESSAGE", msg); 1247 if (ret < 0) 1248 { 1249 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1250 return 1; 1251 } 1252 1253 // Get the severity from the PRIORITY field 1254 long int severity = 8; // Default to an invalid priority 1255 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1256 if (ret < 0) 1257 { 1258 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1259 } 1260 1261 // Get the Created time from the timestamp 1262 std::string entryTimeStr; 1263 if (!getEntryTimestamp(journal, entryTimeStr)) 1264 { 1265 return 1; 1266 } 1267 1268 // Fill in the log entry with the gathered data 1269 bmcJournalLogEntryJson = { 1270 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1271 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1272 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1273 bmcJournalLogEntryID}, 1274 {"Name", "BMC Journal Entry"}, 1275 {"Id", bmcJournalLogEntryID}, 1276 {"Message", msg}, 1277 {"EntryType", "Oem"}, 1278 {"Severity", 1279 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1280 {"OemRecordFormat", "BMC Journal Entry"}, 1281 {"Created", std::move(entryTimeStr)}}; 1282 return 0; 1283 } 1284 1285 class BMCJournalLogEntryCollection : public Node 1286 { 1287 public: 1288 template <typename CrowApp> 1289 BMCJournalLogEntryCollection(CrowApp &app) : 1290 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1291 { 1292 entityPrivileges = { 1293 {boost::beast::http::verb::get, {{"Login"}}}, 1294 {boost::beast::http::verb::head, {{"Login"}}}, 1295 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1296 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1297 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1298 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1299 } 1300 1301 private: 1302 void doGet(crow::Response &res, const crow::Request &req, 1303 const std::vector<std::string> ¶ms) override 1304 { 1305 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1306 static constexpr const long maxEntriesPerPage = 1000; 1307 uint64_t skip = 0; 1308 uint64_t top = maxEntriesPerPage; // Show max entries by default 1309 if (!getSkipParam(asyncResp->res, req, skip)) 1310 { 1311 return; 1312 } 1313 if (!getTopParam(asyncResp->res, req, top)) 1314 { 1315 return; 1316 } 1317 // Collections don't include the static data added by SubRoute because 1318 // it has a duplicate entry for members 1319 asyncResp->res.jsonValue["@odata.type"] = 1320 "#LogEntryCollection.LogEntryCollection"; 1321 asyncResp->res.jsonValue["@odata.id"] = 1322 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1323 asyncResp->res.jsonValue["@odata.context"] = 1324 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1325 asyncResp->res.jsonValue["@odata.id"] = 1326 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1327 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1328 asyncResp->res.jsonValue["Description"] = 1329 "Collection of BMC Journal Entries"; 1330 asyncResp->res.jsonValue["@odata.id"] = 1331 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1332 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1333 logEntryArray = nlohmann::json::array(); 1334 1335 // Go through the journal and use the timestamp to create a unique ID 1336 // for each entry 1337 sd_journal *journalTmp = nullptr; 1338 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1339 if (ret < 0) 1340 { 1341 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1342 messages::internalError(asyncResp->res); 1343 return; 1344 } 1345 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1346 journalTmp, sd_journal_close); 1347 journalTmp = nullptr; 1348 uint64_t entryCount = 0; 1349 // Reset the unique ID on the first entry 1350 bool firstEntry = true; 1351 SD_JOURNAL_FOREACH(journal.get()) 1352 { 1353 entryCount++; 1354 // Handle paging using skip (number of entries to skip from the 1355 // start) and top (number of entries to display) 1356 if (entryCount <= skip || entryCount > skip + top) 1357 { 1358 continue; 1359 } 1360 1361 std::string idStr; 1362 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1363 { 1364 continue; 1365 } 1366 1367 if (firstEntry) 1368 { 1369 firstEntry = false; 1370 } 1371 1372 logEntryArray.push_back({}); 1373 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1374 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1375 bmcJournalLogEntry) != 0) 1376 { 1377 messages::internalError(asyncResp->res); 1378 return; 1379 } 1380 } 1381 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1382 if (skip + top < entryCount) 1383 { 1384 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1385 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1386 std::to_string(skip + top); 1387 } 1388 } 1389 }; 1390 1391 class BMCJournalLogEntry : public Node 1392 { 1393 public: 1394 BMCJournalLogEntry(CrowApp &app) : 1395 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1396 std::string()) 1397 { 1398 entityPrivileges = { 1399 {boost::beast::http::verb::get, {{"Login"}}}, 1400 {boost::beast::http::verb::head, {{"Login"}}}, 1401 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1402 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1403 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1404 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1405 } 1406 1407 private: 1408 void doGet(crow::Response &res, const crow::Request &req, 1409 const std::vector<std::string> ¶ms) override 1410 { 1411 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1412 if (params.size() != 1) 1413 { 1414 messages::internalError(asyncResp->res); 1415 return; 1416 } 1417 const std::string &entryID = params[0]; 1418 // Convert the unique ID back to a timestamp to find the entry 1419 uint64_t ts = 0; 1420 uint64_t index = 0; 1421 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1422 { 1423 return; 1424 } 1425 1426 sd_journal *journalTmp = nullptr; 1427 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1428 if (ret < 0) 1429 { 1430 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1431 messages::internalError(asyncResp->res); 1432 return; 1433 } 1434 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1435 journalTmp, sd_journal_close); 1436 journalTmp = nullptr; 1437 // Go to the timestamp in the log and move to the entry at the index 1438 // tracking the unique ID 1439 std::string idStr; 1440 bool firstEntry = true; 1441 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1442 for (uint64_t i = 0; i <= index; i++) 1443 { 1444 sd_journal_next(journal.get()); 1445 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1446 { 1447 messages::internalError(asyncResp->res); 1448 return; 1449 } 1450 if (firstEntry) 1451 { 1452 firstEntry = false; 1453 } 1454 } 1455 // Confirm that the entry ID matches what was requested 1456 if (idStr != entryID) 1457 { 1458 messages::resourceMissingAtURI(asyncResp->res, entryID); 1459 return; 1460 } 1461 1462 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1463 asyncResp->res.jsonValue) != 0) 1464 { 1465 messages::internalError(asyncResp->res); 1466 return; 1467 } 1468 } 1469 }; 1470 1471 class CrashdumpService : public Node 1472 { 1473 public: 1474 template <typename CrowApp> 1475 CrashdumpService(CrowApp &app) : 1476 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 1477 { 1478 entityPrivileges = { 1479 {boost::beast::http::verb::get, {{"Login"}}}, 1480 {boost::beast::http::verb::head, {{"Login"}}}, 1481 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1482 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1483 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1484 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1485 } 1486 1487 private: 1488 /** 1489 * Functions triggers appropriate requests on DBus 1490 */ 1491 void doGet(crow::Response &res, const crow::Request &req, 1492 const std::vector<std::string> ¶ms) override 1493 { 1494 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1495 // Copy over the static data to include the entries added by SubRoute 1496 asyncResp->res.jsonValue["@odata.id"] = 1497 "/redfish/v1/Systems/system/LogServices/Crashdump"; 1498 asyncResp->res.jsonValue["@odata.type"] = 1499 "#LogService.v1_1_0.LogService"; 1500 asyncResp->res.jsonValue["@odata.context"] = 1501 "/redfish/v1/$metadata#LogService.LogService"; 1502 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 1503 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 1504 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 1505 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1506 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1507 asyncResp->res.jsonValue["Entries"] = { 1508 {"@odata.id", 1509 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 1510 asyncResp->res.jsonValue["Actions"] = { 1511 {"#LogService.ClearLog", 1512 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1513 "Actions/LogService.ClearLog"}}}, 1514 {"Oem", 1515 {{"#Crashdump.OnDemand", 1516 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1517 "Actions/Oem/Crashdump.OnDemand"}}}}}}; 1518 1519 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1520 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1521 {"#Crashdump.SendRawPeci", 1522 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 1523 "Actions/Oem/Crashdump.SendRawPeci"}}}); 1524 #endif 1525 } 1526 }; 1527 1528 class CrashdumpClear : public Node 1529 { 1530 public: 1531 CrashdumpClear(CrowApp &app) : 1532 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 1533 "LogService.ClearLog/") 1534 { 1535 entityPrivileges = { 1536 {boost::beast::http::verb::get, {{"Login"}}}, 1537 {boost::beast::http::verb::head, {{"Login"}}}, 1538 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1539 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1540 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1541 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1542 } 1543 1544 private: 1545 void doPost(crow::Response &res, const crow::Request &req, 1546 const std::vector<std::string> ¶ms) override 1547 { 1548 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1549 1550 crow::connections::systemBus->async_method_call( 1551 [asyncResp](const boost::system::error_code ec, 1552 const std::string &resp) { 1553 if (ec) 1554 { 1555 messages::internalError(asyncResp->res); 1556 return; 1557 } 1558 messages::success(asyncResp->res); 1559 }, 1560 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 1561 } 1562 }; 1563 1564 std::string getLogCreatedTime(const std::string &crashdump) 1565 { 1566 nlohmann::json crashdumpJson = 1567 nlohmann::json::parse(crashdump, nullptr, false); 1568 if (crashdumpJson.is_discarded()) 1569 { 1570 return std::string(); 1571 } 1572 1573 nlohmann::json::const_iterator cdIt = crashdumpJson.find("crash_data"); 1574 if (cdIt == crashdumpJson.end()) 1575 { 1576 return std::string(); 1577 } 1578 1579 nlohmann::json::const_iterator siIt = cdIt->find("METADATA"); 1580 if (siIt == cdIt->end()) 1581 { 1582 return std::string(); 1583 } 1584 1585 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1586 if (tsIt == siIt->end()) 1587 { 1588 return std::string(); 1589 } 1590 1591 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 1592 if (logTime == nullptr) 1593 { 1594 return std::string(); 1595 } 1596 1597 std::string redfishDateTime = *logTime; 1598 if (redfishDateTime.length() > 2) 1599 { 1600 redfishDateTime.insert(redfishDateTime.end() - 2, ':'); 1601 } 1602 1603 return redfishDateTime; 1604 } 1605 1606 std::string getLogFileName(const std::string &logTime) 1607 { 1608 // Set the crashdump file name to "crashdump_<logTime>.json" using the 1609 // created time without the timezone info 1610 std::string fileTime = logTime; 1611 size_t plusPos = fileTime.rfind('+'); 1612 if (plusPos != std::string::npos) 1613 { 1614 fileTime.erase(plusPos); 1615 } 1616 return "crashdump_" + fileTime + ".json"; 1617 } 1618 1619 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp, 1620 const std::string &logID, 1621 nlohmann::json &logEntryJson) 1622 { 1623 auto getStoredLogCallback = [asyncResp, logID, &logEntryJson]( 1624 const boost::system::error_code ec, 1625 const std::variant<std::string> &resp) { 1626 if (ec) 1627 { 1628 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1629 if (ec.value() == 1630 boost::system::linux_error::bad_request_descriptor) 1631 { 1632 messages::resourceNotFound(asyncResp->res, "LogEntry", logID); 1633 } 1634 else 1635 { 1636 messages::internalError(asyncResp->res); 1637 } 1638 return; 1639 } 1640 const std::string *log = std::get_if<std::string>(&resp); 1641 if (log == nullptr) 1642 { 1643 messages::internalError(asyncResp->res); 1644 return; 1645 } 1646 std::string logTime = getLogCreatedTime(*log); 1647 std::string fileName = getLogFileName(logTime); 1648 1649 logEntryJson = { 1650 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1651 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1652 {"@odata.id", 1653 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1654 logID}, 1655 {"Name", "CPU Crashdump"}, 1656 {"Id", logID}, 1657 {"EntryType", "Oem"}, 1658 {"OemRecordFormat", "Crashdump URI"}, 1659 {"Message", 1660 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 1661 logID + "/" + fileName}, 1662 {"Created", std::move(logTime)}}; 1663 }; 1664 crow::connections::systemBus->async_method_call( 1665 std::move(getStoredLogCallback), crashdumpObject, 1666 crashdumpPath + std::string("/") + logID, 1667 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface, "Log"); 1668 } 1669 1670 class CrashdumpEntryCollection : public Node 1671 { 1672 public: 1673 template <typename CrowApp> 1674 CrashdumpEntryCollection(CrowApp &app) : 1675 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 1676 { 1677 entityPrivileges = { 1678 {boost::beast::http::verb::get, {{"Login"}}}, 1679 {boost::beast::http::verb::head, {{"Login"}}}, 1680 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1681 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1682 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1683 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1684 } 1685 1686 private: 1687 /** 1688 * Functions triggers appropriate requests on DBus 1689 */ 1690 void doGet(crow::Response &res, const crow::Request &req, 1691 const std::vector<std::string> ¶ms) override 1692 { 1693 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1694 // Collections don't include the static data added by SubRoute because 1695 // it has a duplicate entry for members 1696 auto getLogEntriesCallback = [asyncResp]( 1697 const boost::system::error_code ec, 1698 const std::vector<std::string> &resp) { 1699 if (ec) 1700 { 1701 if (ec.value() != 1702 boost::system::errc::no_such_file_or_directory) 1703 { 1704 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1705 << ec.message(); 1706 messages::internalError(asyncResp->res); 1707 return; 1708 } 1709 } 1710 asyncResp->res.jsonValue["@odata.type"] = 1711 "#LogEntryCollection.LogEntryCollection"; 1712 asyncResp->res.jsonValue["@odata.id"] = 1713 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 1714 asyncResp->res.jsonValue["@odata.context"] = 1715 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 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 = [asyncResp, logID, fileName]( 1820 const boost::system::error_code ec, 1821 const std::variant<std::string> &resp) { 1822 if (ec) 1823 { 1824 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1825 messages::internalError(asyncResp->res); 1826 return; 1827 } 1828 const std::string *log = std::get_if<std::string>(&resp); 1829 if (log == nullptr) 1830 { 1831 messages::internalError(asyncResp->res); 1832 return; 1833 } 1834 1835 // Verify the file name parameter is correct 1836 if (fileName != getLogFileName(getLogCreatedTime(*log))) 1837 { 1838 messages::resourceMissingAtURI(asyncResp->res, fileName); 1839 return; 1840 } 1841 1842 // Configure this to be a file download when accessed from a browser 1843 asyncResp->res.addHeader("Content-Disposition", "attachment"); 1844 asyncResp->res.body() = *log; 1845 }; 1846 crow::connections::systemBus->async_method_call( 1847 std::move(getStoredLogCallback), crashdumpObject, 1848 crashdumpPath + std::string("/") + logID, 1849 "org.freedesktop.DBus.Properties", "Get", crashdumpInterface, 1850 "Log"); 1851 } 1852 }; 1853 1854 class OnDemandCrashdump : public Node 1855 { 1856 public: 1857 OnDemandCrashdump(CrowApp &app) : 1858 Node(app, 1859 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1860 "Crashdump.OnDemand/") 1861 { 1862 entityPrivileges = { 1863 {boost::beast::http::verb::get, {{"Login"}}}, 1864 {boost::beast::http::verb::head, {{"Login"}}}, 1865 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1866 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1867 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1868 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1869 } 1870 1871 private: 1872 void doPost(crow::Response &res, const crow::Request &req, 1873 const std::vector<std::string> ¶ms) override 1874 { 1875 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1876 1877 auto generateonDemandLogCallback = 1878 [asyncResp](const boost::system::error_code ec, 1879 const std::string &resp) { 1880 if (ec) 1881 { 1882 if (ec.value() == 1883 boost::system::errc::operation_not_supported) 1884 { 1885 messages::resourceInStandby(asyncResp->res); 1886 } 1887 else if (ec.value() == 1888 boost::system::errc::device_or_resource_busy) 1889 { 1890 messages::serviceTemporarilyUnavailable(asyncResp->res, 1891 "60"); 1892 } 1893 else 1894 { 1895 messages::internalError(asyncResp->res); 1896 } 1897 return; 1898 } 1899 asyncResp->res.result(boost::beast::http::status::no_content); 1900 }; 1901 crow::connections::systemBus->async_method_call( 1902 std::move(generateonDemandLogCallback), crashdumpObject, 1903 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog"); 1904 } 1905 }; 1906 1907 class SendRawPECI : public Node 1908 { 1909 public: 1910 SendRawPECI(CrowApp &app) : 1911 Node(app, 1912 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 1913 "Crashdump.SendRawPeci/") 1914 { 1915 entityPrivileges = { 1916 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1917 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1918 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1919 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1920 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1921 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1922 } 1923 1924 private: 1925 void doPost(crow::Response &res, const crow::Request &req, 1926 const std::vector<std::string> ¶ms) override 1927 { 1928 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1929 std::vector<std::vector<uint8_t>> peciCommands; 1930 1931 nlohmann::json reqJson = 1932 nlohmann::json::parse(req.body, nullptr, false); 1933 if (reqJson.find("PECICommands") != reqJson.end()) 1934 { 1935 if (!json_util::readJson(req, res, "PECICommands", peciCommands)) 1936 { 1937 return; 1938 } 1939 uint32_t idx = 0; 1940 for (auto const &cmd : peciCommands) 1941 { 1942 if (cmd.size() < 3) 1943 { 1944 std::string s("["); 1945 for (auto const &val : cmd) 1946 { 1947 if (val != *cmd.begin()) 1948 { 1949 s += ","; 1950 } 1951 s += std::to_string(val); 1952 } 1953 s += "]"; 1954 messages::actionParameterValueFormatError( 1955 res, s, "PECICommands[" + std::to_string(idx) + "]", 1956 "SendRawPeci"); 1957 return; 1958 } 1959 idx++; 1960 } 1961 } 1962 else 1963 { 1964 /* This interface is deprecated */ 1965 uint8_t clientAddress = 0; 1966 uint8_t readLength = 0; 1967 std::vector<uint8_t> peciCommand; 1968 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1969 "ReadLength", readLength, "PECICommand", 1970 peciCommand)) 1971 { 1972 return; 1973 } 1974 peciCommands.push_back({clientAddress, 0, readLength}); 1975 peciCommands[0].insert(peciCommands[0].end(), peciCommand.begin(), 1976 peciCommand.end()); 1977 } 1978 // Callback to return the Raw PECI response 1979 auto sendRawPECICallback = 1980 [asyncResp](const boost::system::error_code ec, 1981 const std::vector<std::vector<uint8_t>> &resp) { 1982 if (ec) 1983 { 1984 BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: " 1985 << ec.message(); 1986 messages::internalError(asyncResp->res); 1987 return; 1988 } 1989 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1990 {"PECIResponse", resp}}; 1991 }; 1992 // Call the SendRawPECI command with the provided data 1993 crow::connections::systemBus->async_method_call( 1994 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath, 1995 crashdumpRawPECIInterface, "SendRawPeci", peciCommands); 1996 } 1997 }; 1998 1999 /** 2000 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2001 */ 2002 class DBusLogServiceActionsClear : public Node 2003 { 2004 public: 2005 DBusLogServiceActionsClear(CrowApp &app) : 2006 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2007 "LogService.ClearLog") 2008 { 2009 entityPrivileges = { 2010 {boost::beast::http::verb::get, {{"Login"}}}, 2011 {boost::beast::http::verb::head, {{"Login"}}}, 2012 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2013 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2014 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2015 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2016 } 2017 2018 private: 2019 /** 2020 * Function handles POST method request. 2021 * The Clear Log actions does not require any parameter.The action deletes 2022 * all entries found in the Entries collection for this Log Service. 2023 */ 2024 void doPost(crow::Response &res, const crow::Request &req, 2025 const std::vector<std::string> ¶ms) override 2026 { 2027 BMCWEB_LOG_DEBUG << "Do delete all entries."; 2028 2029 auto asyncResp = std::make_shared<AsyncResp>(res); 2030 // Process response from Logging service. 2031 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 2032 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 2033 if (ec) 2034 { 2035 // TODO Handle for specific error code 2036 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 2037 asyncResp->res.result( 2038 boost::beast::http::status::internal_server_error); 2039 return; 2040 } 2041 2042 asyncResp->res.result(boost::beast::http::status::no_content); 2043 }; 2044 2045 // Make call to Logging service to request Clear Log 2046 crow::connections::systemBus->async_method_call( 2047 resp_handler, "xyz.openbmc_project.Logging", 2048 "/xyz/openbmc_project/logging", 2049 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2050 } 2051 }; 2052 } // namespace redfish 2053