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 "filesystem.hpp" 19 #include "node.hpp" 20 21 #include <systemd/sd-journal.h> 22 23 #include <boost/container/flat_map.hpp> 24 #include <error_messages.hpp> 25 #include <variant> 26 27 namespace redfish 28 { 29 30 constexpr char const *cpuLogObject = "com.intel.CpuDebugLog"; 31 constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog"; 32 constexpr char const *cpuLogOnDemandPath = "/com/intel/CpuDebugLog/OnDemand"; 33 constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog"; 34 constexpr char const *cpuLogOnDemandInterface = 35 "com.intel.CpuDebugLog.OnDemand"; 36 constexpr char const *cpuLogRawPECIInterface = 37 "com.intel.CpuDebugLog.SendRawPeci"; 38 39 namespace fs = std::filesystem; 40 41 using GetManagedPropertyType = boost::container::flat_map< 42 std::string, 43 sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t, 44 int32_t, uint32_t, int64_t, uint64_t, double>>; 45 46 using GetManagedObjectsType = boost::container::flat_map< 47 sdbusplus::message::object_path, 48 boost::container::flat_map<std::string, GetManagedPropertyType>>; 49 50 inline std::string translateSeverityDbusToRedfish(const std::string &s) 51 { 52 if (s == "xyz.openbmc_project.Logging.Entry.Level.Alert") 53 { 54 return "Critical"; 55 } 56 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") 57 { 58 return "Critical"; 59 } 60 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Debug") 61 { 62 return "OK"; 63 } 64 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") 65 { 66 return "Critical"; 67 } 68 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Error") 69 { 70 return "Critical"; 71 } 72 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") 73 { 74 return "OK"; 75 } 76 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Notice") 77 { 78 return "OK"; 79 } 80 else if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning") 81 { 82 return "Warning"; 83 } 84 return ""; 85 } 86 87 static int getJournalMetadata(sd_journal *journal, 88 const std::string_view &field, 89 std::string_view &contents) 90 { 91 const char *data = nullptr; 92 size_t length = 0; 93 int ret = 0; 94 // Get the metadata from the requested field of the journal entry 95 ret = sd_journal_get_data(journal, field.data(), (const void **)&data, 96 &length); 97 if (ret < 0) 98 { 99 return ret; 100 } 101 contents = std::string_view(data, length); 102 // Only use the content after the "=" character. 103 contents.remove_prefix(std::min(contents.find("=") + 1, contents.size())); 104 return ret; 105 } 106 107 static int getJournalMetadata(sd_journal *journal, 108 const std::string_view &field, const int &base, 109 int &contents) 110 { 111 int ret = 0; 112 std::string_view metadata; 113 // Get the metadata from the requested field of the journal entry 114 ret = getJournalMetadata(journal, field, metadata); 115 if (ret < 0) 116 { 117 return ret; 118 } 119 contents = strtol(metadata.data(), nullptr, base); 120 return ret; 121 } 122 123 static bool getEntryTimestamp(sd_journal *journal, std::string &entryTimestamp) 124 { 125 int ret = 0; 126 uint64_t timestamp = 0; 127 ret = sd_journal_get_realtime_usec(journal, ×tamp); 128 if (ret < 0) 129 { 130 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 131 << strerror(-ret); 132 return false; 133 } 134 time_t t = 135 static_cast<time_t>(timestamp / 1000 / 1000); // Convert from us to s 136 struct tm *loctime = localtime(&t); 137 char entryTime[64] = {}; 138 if (NULL != loctime) 139 { 140 strftime(entryTime, sizeof(entryTime), "%FT%T%z", loctime); 141 } 142 // Insert the ':' into the timezone 143 std::string_view t1(entryTime); 144 std::string_view t2(entryTime); 145 if (t1.size() > 2 && t2.size() > 2) 146 { 147 t1.remove_suffix(2); 148 t2.remove_prefix(t2.size() - 2); 149 } 150 entryTimestamp = std::string(t1) + ":" + std::string(t2); 151 return true; 152 } 153 154 static bool getSkipParam(crow::Response &res, const crow::Request &req, 155 long &skip) 156 { 157 char *skipParam = req.urlParams.get("$skip"); 158 if (skipParam != nullptr) 159 { 160 char *ptr = nullptr; 161 skip = std::strtol(skipParam, &ptr, 10); 162 if (*skipParam == '\0' || *ptr != '\0') 163 { 164 165 messages::queryParameterValueTypeError(res, std::string(skipParam), 166 "$skip"); 167 return false; 168 } 169 if (skip < 0) 170 { 171 172 messages::queryParameterOutOfRange(res, std::to_string(skip), 173 "$skip", "greater than 0"); 174 return false; 175 } 176 } 177 return true; 178 } 179 180 static constexpr const long maxEntriesPerPage = 1000; 181 static bool getTopParam(crow::Response &res, const crow::Request &req, 182 long &top) 183 { 184 char *topParam = req.urlParams.get("$top"); 185 if (topParam != nullptr) 186 { 187 char *ptr = nullptr; 188 top = std::strtol(topParam, &ptr, 10); 189 if (*topParam == '\0' || *ptr != '\0') 190 { 191 messages::queryParameterValueTypeError(res, std::string(topParam), 192 "$top"); 193 return false; 194 } 195 if (top < 1 || top > maxEntriesPerPage) 196 { 197 198 messages::queryParameterOutOfRange( 199 res, std::to_string(top), "$top", 200 "1-" + std::to_string(maxEntriesPerPage)); 201 return false; 202 } 203 } 204 return true; 205 } 206 207 static bool getUniqueEntryID(sd_journal *journal, std::string &entryID) 208 { 209 int ret = 0; 210 static uint64_t prevTs = 0; 211 static int index = 0; 212 // Get the entry timestamp 213 uint64_t curTs = 0; 214 ret = sd_journal_get_realtime_usec(journal, &curTs); 215 if (ret < 0) 216 { 217 BMCWEB_LOG_ERROR << "Failed to read entry timestamp: " 218 << strerror(-ret); 219 return false; 220 } 221 // If the timestamp isn't unique, increment the index 222 if (curTs == prevTs) 223 { 224 index++; 225 } 226 else 227 { 228 // Otherwise, reset it 229 index = 0; 230 } 231 // Save the timestamp 232 prevTs = curTs; 233 234 entryID = std::to_string(curTs); 235 if (index > 0) 236 { 237 entryID += "_" + std::to_string(index); 238 } 239 return true; 240 } 241 242 static bool getTimestampFromID(crow::Response &res, const std::string &entryID, 243 uint64_t ×tamp, uint16_t &index) 244 { 245 if (entryID.empty()) 246 { 247 return false; 248 } 249 // Convert the unique ID back to a timestamp to find the entry 250 std::string_view tsStr(entryID); 251 252 auto underscorePos = tsStr.find("_"); 253 if (underscorePos != tsStr.npos) 254 { 255 // Timestamp has an index 256 tsStr.remove_suffix(tsStr.size() - underscorePos); 257 std::string_view indexStr(entryID); 258 indexStr.remove_prefix(underscorePos + 1); 259 std::size_t pos; 260 try 261 { 262 index = std::stoul(std::string(indexStr), &pos); 263 } 264 catch (std::invalid_argument) 265 { 266 messages::resourceMissingAtURI(res, entryID); 267 return false; 268 } 269 catch (std::out_of_range) 270 { 271 messages::resourceMissingAtURI(res, entryID); 272 return false; 273 } 274 if (pos != indexStr.size()) 275 { 276 messages::resourceMissingAtURI(res, entryID); 277 return false; 278 } 279 } 280 // Timestamp has no index 281 std::size_t pos; 282 try 283 { 284 timestamp = std::stoull(std::string(tsStr), &pos); 285 } 286 catch (std::invalid_argument) 287 { 288 messages::resourceMissingAtURI(res, entryID); 289 return false; 290 } 291 catch (std::out_of_range) 292 { 293 messages::resourceMissingAtURI(res, entryID); 294 return false; 295 } 296 if (pos != tsStr.size()) 297 { 298 messages::resourceMissingAtURI(res, entryID); 299 return false; 300 } 301 return true; 302 } 303 304 class SystemLogServiceCollection : public Node 305 { 306 public: 307 template <typename CrowApp> 308 SystemLogServiceCollection(CrowApp &app) : 309 Node(app, "/redfish/v1/Systems/system/LogServices/") 310 { 311 entityPrivileges = { 312 {boost::beast::http::verb::get, {{"Login"}}}, 313 {boost::beast::http::verb::head, {{"Login"}}}, 314 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 315 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 316 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 317 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 318 } 319 320 private: 321 /** 322 * Functions triggers appropriate requests on DBus 323 */ 324 void doGet(crow::Response &res, const crow::Request &req, 325 const std::vector<std::string> ¶ms) override 326 { 327 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 328 // Collections don't include the static data added by SubRoute because 329 // it has a duplicate entry for members 330 asyncResp->res.jsonValue["@odata.type"] = 331 "#LogServiceCollection.LogServiceCollection"; 332 asyncResp->res.jsonValue["@odata.context"] = 333 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 334 asyncResp->res.jsonValue["@odata.id"] = 335 "/redfish/v1/Systems/system/LogServices"; 336 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 337 asyncResp->res.jsonValue["Description"] = 338 "Collection of LogServices for this Computer System"; 339 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 340 logServiceArray = nlohmann::json::array(); 341 logServiceArray.push_back( 342 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 343 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 344 logServiceArray.push_back( 345 {{ "@odata.id", 346 "/redfish/v1/Systems/system/LogServices/CpuLog" }}); 347 #endif 348 asyncResp->res.jsonValue["Members@odata.count"] = 349 logServiceArray.size(); 350 } 351 }; 352 353 class EventLogService : public Node 354 { 355 public: 356 template <typename CrowApp> 357 EventLogService(CrowApp &app) : 358 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 359 { 360 entityPrivileges = { 361 {boost::beast::http::verb::get, {{"Login"}}}, 362 {boost::beast::http::verb::head, {{"Login"}}}, 363 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 364 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 365 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 366 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 367 } 368 369 private: 370 void doGet(crow::Response &res, const crow::Request &req, 371 const std::vector<std::string> ¶ms) override 372 { 373 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 374 375 asyncResp->res.jsonValue["@odata.id"] = 376 "/redfish/v1/Systems/system/LogServices/EventLog"; 377 asyncResp->res.jsonValue["@odata.type"] = 378 "#LogService.v1_1_0.LogService"; 379 asyncResp->res.jsonValue["@odata.context"] = 380 "/redfish/v1/$metadata#LogService.LogService"; 381 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 382 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 383 asyncResp->res.jsonValue["Id"] = "Event Log"; 384 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 385 asyncResp->res.jsonValue["Entries"] = { 386 {"@odata.id", 387 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 388 } 389 }; 390 391 static int fillEventLogEntryJson(const std::string &bmcLogEntryID, 392 const std::string_view &messageID, 393 sd_journal *journal, 394 nlohmann::json &bmcLogEntryJson) 395 { 396 // Get the Log Entry contents 397 int ret = 0; 398 399 std::string_view msg; 400 ret = getJournalMetadata(journal, "MESSAGE", msg); 401 if (ret < 0) 402 { 403 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 404 return 1; 405 } 406 407 // Get the severity from the PRIORITY field 408 int severity = 8; // Default to an invalid priority 409 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 410 if (ret < 0) 411 { 412 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 413 return 1; 414 } 415 416 // Get the MessageArgs from the journal entry by finding all of the 417 // REDFISH_MESSAGE_ARG_x fields 418 const void *data; 419 size_t length; 420 std::vector<std::string> messageArgs; 421 SD_JOURNAL_FOREACH_DATA(journal, data, length) 422 { 423 std::string_view field(static_cast<const char *>(data), length); 424 if (boost::starts_with(field, "REDFISH_MESSAGE_ARG_")) 425 { 426 // Get the Arg number from the field name 427 field.remove_prefix(sizeof("REDFISH_MESSAGE_ARG_") - 1); 428 if (field.empty()) 429 { 430 continue; 431 } 432 int argNum = std::strtoul(field.data(), nullptr, 10); 433 if (argNum == 0) 434 { 435 continue; 436 } 437 // Get the Arg value after the "=" character. 438 field.remove_prefix(std::min(field.find("=") + 1, field.size())); 439 // Make sure we have enough space in messageArgs 440 if (argNum > messageArgs.size()) 441 { 442 messageArgs.resize(argNum); 443 } 444 messageArgs[argNum - 1] = std::string(field); 445 } 446 } 447 448 // Get the Created time from the timestamp 449 std::string entryTimeStr; 450 if (!getEntryTimestamp(journal, entryTimeStr)) 451 { 452 return 1; 453 } 454 455 // Fill in the log entry with the gathered data 456 bmcLogEntryJson = { 457 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 458 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 459 {"@odata.id", 460 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 461 bmcLogEntryID}, 462 {"Name", "System Event Log Entry"}, 463 {"Id", bmcLogEntryID}, 464 {"Message", msg}, 465 {"MessageId", messageID}, 466 {"MessageArgs", std::move(messageArgs)}, 467 {"EntryType", "Event"}, 468 {"Severity", 469 severity <= 2 ? "Critical" 470 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 471 {"Created", std::move(entryTimeStr)}}; 472 return 0; 473 } 474 475 class EventLogEntryCollection : public Node 476 { 477 public: 478 template <typename CrowApp> 479 EventLogEntryCollection(CrowApp &app) : 480 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 481 { 482 entityPrivileges = { 483 {boost::beast::http::verb::get, {{"Login"}}}, 484 {boost::beast::http::verb::head, {{"Login"}}}, 485 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 486 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 487 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 488 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 489 } 490 491 private: 492 void doGet(crow::Response &res, const crow::Request &req, 493 const std::vector<std::string> ¶ms) override 494 { 495 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 496 long skip = 0; 497 long top = maxEntriesPerPage; // Show max entries by default 498 if (!getSkipParam(asyncResp->res, req, skip)) 499 { 500 return; 501 } 502 if (!getTopParam(asyncResp->res, req, top)) 503 { 504 return; 505 } 506 // Collections don't include the static data added by SubRoute because 507 // it has a duplicate entry for members 508 asyncResp->res.jsonValue["@odata.type"] = 509 "#LogEntryCollection.LogEntryCollection"; 510 asyncResp->res.jsonValue["@odata.context"] = 511 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 512 asyncResp->res.jsonValue["@odata.id"] = 513 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 514 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 515 asyncResp->res.jsonValue["Description"] = 516 "Collection of System Event Log Entries"; 517 518 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 519 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 520 logEntryArray = nlohmann::json::array(); 521 // Go through the journal and create a unique ID for each entry 522 sd_journal *journalTmp = nullptr; 523 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 524 if (ret < 0) 525 { 526 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 527 messages::internalError(asyncResp->res); 528 return; 529 } 530 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 531 journalTmp, sd_journal_close); 532 journalTmp = nullptr; 533 uint64_t entryCount = 0; 534 SD_JOURNAL_FOREACH(journal.get()) 535 { 536 // Look for only journal entries that contain a REDFISH_MESSAGE_ID 537 // field 538 std::string_view messageID; 539 ret = getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", 540 messageID); 541 if (ret < 0) 542 { 543 continue; 544 } 545 546 entryCount++; 547 // Handle paging using skip (number of entries to skip from the 548 // start) and top (number of entries to display) 549 if (entryCount <= skip || entryCount > skip + top) 550 { 551 continue; 552 } 553 554 std::string idStr; 555 if (!getUniqueEntryID(journal.get(), idStr)) 556 { 557 continue; 558 } 559 560 logEntryArray.push_back({}); 561 nlohmann::json &bmcLogEntry = logEntryArray.back(); 562 if (fillEventLogEntryJson(idStr, messageID, journal.get(), 563 bmcLogEntry) != 0) 564 { 565 messages::internalError(asyncResp->res); 566 return; 567 } 568 } 569 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 570 if (skip + top < entryCount) 571 { 572 asyncResp->res.jsonValue["Members@odata.nextLink"] = 573 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries?$skip=" + 574 std::to_string(skip + top); 575 } 576 #else 577 // DBus implementation of EventLog/Entries 578 // Make call to Logging Service to find all log entry objects 579 crow::connections::systemBus->async_method_call( 580 [asyncResp](const boost::system::error_code ec, 581 GetManagedObjectsType &resp) { 582 if (ec) 583 { 584 // TODO Handle for specific error code 585 BMCWEB_LOG_ERROR 586 << "getLogEntriesIfaceData resp_handler got error " 587 << ec; 588 messages::internalError(asyncResp->res); 589 return; 590 } 591 nlohmann::json &entriesArray = 592 asyncResp->res.jsonValue["Members"]; 593 entriesArray = nlohmann::json::array(); 594 for (auto &objectPath : resp) 595 { 596 for (auto &interfaceMap : objectPath.second) 597 { 598 if (interfaceMap.first != 599 "xyz.openbmc_project.Logging.Entry") 600 { 601 BMCWEB_LOG_DEBUG << "Bailing early on " 602 << interfaceMap.first; 603 continue; 604 } 605 entriesArray.push_back({}); 606 nlohmann::json &thisEntry = entriesArray.back(); 607 uint32_t *id; 608 std::time_t timestamp; 609 std::string *severity, *message; 610 bool *resolved; 611 for (auto &propertyMap : interfaceMap.second) 612 { 613 if (propertyMap.first == "Id") 614 { 615 id = sdbusplus::message::variant_ns::get_if< 616 uint32_t>(&propertyMap.second); 617 if (id == nullptr) 618 { 619 messages::propertyMissing(asyncResp->res, 620 "Id"); 621 } 622 } 623 else if (propertyMap.first == "Timestamp") 624 { 625 const uint64_t *millisTimeStamp = 626 std::get_if<uint64_t>(&propertyMap.second); 627 if (millisTimeStamp == nullptr) 628 { 629 messages::propertyMissing(asyncResp->res, 630 "Timestamp"); 631 } 632 // Retrieve Created property with format: 633 // yyyy-mm-ddThh:mm:ss 634 std::chrono::milliseconds chronoTimeStamp( 635 *millisTimeStamp); 636 timestamp = 637 std::chrono::duration_cast< 638 std::chrono::seconds>(chronoTimeStamp) 639 .count(); 640 } 641 else if (propertyMap.first == "Severity") 642 { 643 severity = std::get_if<std::string>( 644 &propertyMap.second); 645 if (severity == nullptr) 646 { 647 messages::propertyMissing(asyncResp->res, 648 "Severity"); 649 } 650 } 651 else if (propertyMap.first == "Message") 652 { 653 message = std::get_if<std::string>( 654 &propertyMap.second); 655 if (message == nullptr) 656 { 657 messages::propertyMissing(asyncResp->res, 658 "Message"); 659 } 660 } 661 } 662 thisEntry = { 663 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 664 {"@odata.context", "/redfish/v1/" 665 "$metadata#LogEntry.LogEntry"}, 666 {"@odata.id", 667 "/redfish/v1/Systems/system/LogServices/EventLog/" 668 "Entries/" + 669 std::to_string(*id)}, 670 {"Name", "System DBus Event Log Entry"}, 671 {"Id", std::to_string(*id)}, 672 {"Message", *message}, 673 {"EntryType", "Event"}, 674 {"Severity", 675 translateSeverityDbusToRedfish(*severity)}, 676 {"Created", crow::utility::getDateTime(timestamp)}}; 677 } 678 } 679 std::sort(entriesArray.begin(), entriesArray.end(), 680 [](const nlohmann::json &left, 681 const nlohmann::json &right) { 682 return (left["Id"] <= right["Id"]); 683 }); 684 asyncResp->res.jsonValue["Members@odata.count"] = 685 entriesArray.size(); 686 }, 687 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 688 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 689 #endif 690 } 691 }; 692 693 class EventLogEntry : public Node 694 { 695 public: 696 EventLogEntry(CrowApp &app) : 697 Node(app, 698 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 699 std::string()) 700 { 701 entityPrivileges = { 702 {boost::beast::http::verb::get, {{"Login"}}}, 703 {boost::beast::http::verb::head, {{"Login"}}}, 704 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 705 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 706 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 707 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 708 } 709 710 private: 711 void doGet(crow::Response &res, const crow::Request &req, 712 const std::vector<std::string> ¶ms) override 713 { 714 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 715 if (params.size() != 1) 716 { 717 messages::internalError(asyncResp->res); 718 return; 719 } 720 const std::string &entryID = params[0]; 721 722 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 723 // Convert the unique ID back to a timestamp to find the entry 724 uint64_t ts = 0; 725 uint16_t index = 0; 726 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 727 { 728 return; 729 } 730 731 sd_journal *journalTmp = nullptr; 732 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 733 if (ret < 0) 734 { 735 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 736 messages::internalError(asyncResp->res); 737 return; 738 } 739 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 740 journalTmp, sd_journal_close); 741 journalTmp = nullptr; 742 // Go to the timestamp in the log and move to the entry at the index 743 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 744 for (int i = 0; i <= index; i++) 745 { 746 sd_journal_next(journal.get()); 747 } 748 // Confirm that the entry ID matches what was requested 749 std::string idStr; 750 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 751 { 752 messages::resourceMissingAtURI(asyncResp->res, entryID); 753 return; 754 } 755 756 // only use journal entries that contain a REDFISH_MESSAGE_ID field 757 std::string_view messageID; 758 ret = 759 getJournalMetadata(journal.get(), "REDFISH_MESSAGE_ID", messageID); 760 if (ret < 0) 761 { 762 messages::resourceNotFound(asyncResp->res, "LogEntry", "system"); 763 return; 764 } 765 766 if (fillEventLogEntryJson(entryID, messageID, journal.get(), 767 asyncResp->res.jsonValue) != 0) 768 { 769 messages::internalError(asyncResp->res); 770 return; 771 } 772 #else 773 // DBus implementation of EventLog/Entries 774 // Make call to Logging Service to find all log entry objects 775 crow::connections::systemBus->async_method_call( 776 [asyncResp, entryID](const boost::system::error_code ec, 777 GetManagedPropertyType &resp) { 778 if (ec) 779 { 780 BMCWEB_LOG_ERROR 781 << "EventLogEntry (DBus) resp_handler got error " << ec; 782 messages::internalError(asyncResp->res); 783 return; 784 } 785 uint32_t *id; 786 std::time_t timestamp; 787 std::string *severity, *message; 788 bool *resolved; 789 for (auto &propertyMap : resp) 790 { 791 if (propertyMap.first == "Id") 792 { 793 id = std::get_if<uint32_t>(&propertyMap.second); 794 if (id == nullptr) 795 { 796 messages::propertyMissing(asyncResp->res, "Id"); 797 } 798 } 799 else if (propertyMap.first == "Timestamp") 800 { 801 const uint64_t *millisTimeStamp = 802 std::get_if<uint64_t>(&propertyMap.second); 803 if (millisTimeStamp == nullptr) 804 { 805 messages::propertyMissing(asyncResp->res, 806 "Timestamp"); 807 } 808 // Retrieve Created property with format: 809 // yyyy-mm-ddThh:mm:ss 810 std::chrono::milliseconds chronoTimeStamp( 811 *millisTimeStamp); 812 timestamp = 813 std::chrono::duration_cast<std::chrono::seconds>( 814 chronoTimeStamp) 815 .count(); 816 } 817 else if (propertyMap.first == "Severity") 818 { 819 severity = 820 std::get_if<std::string>(&propertyMap.second); 821 if (severity == nullptr) 822 { 823 messages::propertyMissing(asyncResp->res, 824 "Severity"); 825 } 826 } 827 else if (propertyMap.first == "Message") 828 { 829 message = std::get_if<std::string>(&propertyMap.second); 830 if (message == nullptr) 831 { 832 messages::propertyMissing(asyncResp->res, 833 "Message"); 834 } 835 } 836 } 837 asyncResp->res.jsonValue = { 838 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 839 {"@odata.context", "/redfish/v1/" 840 "$metadata#LogEntry.LogEntry"}, 841 {"@odata.id", 842 "/redfish/v1/Systems/system/LogServices/EventLog/" 843 "Entries/" + 844 std::to_string(*id)}, 845 {"Name", "System DBus Event Log Entry"}, 846 {"Id", std::to_string(*id)}, 847 {"Message", *message}, 848 {"EntryType", "Event"}, 849 {"Severity", translateSeverityDbusToRedfish(*severity)}, 850 {"Created", crow::utility::getDateTime(timestamp)}}; 851 }, 852 "xyz.openbmc_project.Logging", 853 "/xyz/openbmc_project/logging/entry/" + entryID, 854 "org.freedesktop.DBus.Properties", "GetAll", 855 "xyz.openbmc_project.Logging.Entry"); 856 #endif 857 } 858 }; 859 860 class BMCLogServiceCollection : public Node 861 { 862 public: 863 template <typename CrowApp> 864 BMCLogServiceCollection(CrowApp &app) : 865 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 866 { 867 entityPrivileges = { 868 {boost::beast::http::verb::get, {{"Login"}}}, 869 {boost::beast::http::verb::head, {{"Login"}}}, 870 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 871 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 872 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 873 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 874 } 875 876 private: 877 /** 878 * Functions triggers appropriate requests on DBus 879 */ 880 void doGet(crow::Response &res, const crow::Request &req, 881 const std::vector<std::string> ¶ms) override 882 { 883 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 884 // Collections don't include the static data added by SubRoute because 885 // it has a duplicate entry for members 886 asyncResp->res.jsonValue["@odata.type"] = 887 "#LogServiceCollection.LogServiceCollection"; 888 asyncResp->res.jsonValue["@odata.context"] = 889 "/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection"; 890 asyncResp->res.jsonValue["@odata.id"] = 891 "/redfish/v1/Managers/bmc/LogServices"; 892 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 893 asyncResp->res.jsonValue["Description"] = 894 "Collection of LogServices for this Manager"; 895 nlohmann::json &logServiceArray = asyncResp->res.jsonValue["Members"]; 896 logServiceArray = nlohmann::json::array(); 897 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 898 logServiceArray.push_back( 899 {{ "@odata.id", 900 "/redfish/v1/Managers/bmc/LogServices/Journal" }}); 901 #endif 902 asyncResp->res.jsonValue["Members@odata.count"] = 903 logServiceArray.size(); 904 } 905 }; 906 907 class BMCJournalLogService : public Node 908 { 909 public: 910 template <typename CrowApp> 911 BMCJournalLogService(CrowApp &app) : 912 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 913 { 914 entityPrivileges = { 915 {boost::beast::http::verb::get, {{"Login"}}}, 916 {boost::beast::http::verb::head, {{"Login"}}}, 917 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 918 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 919 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 920 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 921 } 922 923 private: 924 void doGet(crow::Response &res, const crow::Request &req, 925 const std::vector<std::string> ¶ms) override 926 { 927 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 928 asyncResp->res.jsonValue["@odata.type"] = 929 "#LogService.v1_1_0.LogService"; 930 asyncResp->res.jsonValue["@odata.id"] = 931 "/redfish/v1/Managers/bmc/LogServices/Journal"; 932 asyncResp->res.jsonValue["@odata.context"] = 933 "/redfish/v1/$metadata#LogService.LogService"; 934 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 935 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 936 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 937 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 938 asyncResp->res.jsonValue["Entries"] = { 939 {"@odata.id", 940 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/"}}; 941 } 942 }; 943 944 static int fillBMCJournalLogEntryJson(const std::string &bmcJournalLogEntryID, 945 sd_journal *journal, 946 nlohmann::json &bmcJournalLogEntryJson) 947 { 948 // Get the Log Entry contents 949 int ret = 0; 950 951 std::string_view msg; 952 ret = getJournalMetadata(journal, "MESSAGE", msg); 953 if (ret < 0) 954 { 955 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 956 return 1; 957 } 958 959 // Get the severity from the PRIORITY field 960 int severity = 8; // Default to an invalid priority 961 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 962 if (ret < 0) 963 { 964 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 965 return 1; 966 } 967 968 // Get the Created time from the timestamp 969 std::string entryTimeStr; 970 if (!getEntryTimestamp(journal, entryTimeStr)) 971 { 972 return 1; 973 } 974 975 // Fill in the log entry with the gathered data 976 bmcJournalLogEntryJson = { 977 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 978 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 979 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 980 bmcJournalLogEntryID}, 981 {"Name", "BMC Journal Entry"}, 982 {"Id", bmcJournalLogEntryID}, 983 {"Message", msg}, 984 {"EntryType", "Oem"}, 985 {"Severity", 986 severity <= 2 ? "Critical" 987 : severity <= 4 ? "Warning" : severity <= 7 ? "OK" : ""}, 988 {"OemRecordFormat", "Intel BMC Journal Entry"}, 989 {"Created", std::move(entryTimeStr)}}; 990 return 0; 991 } 992 993 class BMCJournalLogEntryCollection : public Node 994 { 995 public: 996 template <typename CrowApp> 997 BMCJournalLogEntryCollection(CrowApp &app) : 998 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 999 { 1000 entityPrivileges = { 1001 {boost::beast::http::verb::get, {{"Login"}}}, 1002 {boost::beast::http::verb::head, {{"Login"}}}, 1003 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1004 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1005 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1006 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1007 } 1008 1009 private: 1010 void doGet(crow::Response &res, const crow::Request &req, 1011 const std::vector<std::string> ¶ms) override 1012 { 1013 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1014 static constexpr const long maxEntriesPerPage = 1000; 1015 long skip = 0; 1016 long top = maxEntriesPerPage; // Show max entries by default 1017 if (!getSkipParam(asyncResp->res, req, skip)) 1018 { 1019 return; 1020 } 1021 if (!getTopParam(asyncResp->res, req, top)) 1022 { 1023 return; 1024 } 1025 // Collections don't include the static data added by SubRoute because 1026 // it has a duplicate entry for members 1027 asyncResp->res.jsonValue["@odata.type"] = 1028 "#LogEntryCollection.LogEntryCollection"; 1029 asyncResp->res.jsonValue["@odata.id"] = 1030 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1031 asyncResp->res.jsonValue["@odata.context"] = 1032 "/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection"; 1033 asyncResp->res.jsonValue["@odata.id"] = 1034 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1035 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1036 asyncResp->res.jsonValue["Description"] = 1037 "Collection of BMC Journal Entries"; 1038 asyncResp->res.jsonValue["@odata.id"] = 1039 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1040 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1041 logEntryArray = nlohmann::json::array(); 1042 1043 // Go through the journal and use the timestamp to create a unique ID 1044 // for each entry 1045 sd_journal *journalTmp = nullptr; 1046 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1047 if (ret < 0) 1048 { 1049 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1050 messages::internalError(asyncResp->res); 1051 return; 1052 } 1053 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1054 journalTmp, sd_journal_close); 1055 journalTmp = nullptr; 1056 uint64_t entryCount = 0; 1057 SD_JOURNAL_FOREACH(journal.get()) 1058 { 1059 entryCount++; 1060 // Handle paging using skip (number of entries to skip from the 1061 // start) and top (number of entries to display) 1062 if (entryCount <= skip || entryCount > skip + top) 1063 { 1064 continue; 1065 } 1066 1067 std::string idStr; 1068 if (!getUniqueEntryID(journal.get(), idStr)) 1069 { 1070 continue; 1071 } 1072 1073 logEntryArray.push_back({}); 1074 nlohmann::json &bmcJournalLogEntry = logEntryArray.back(); 1075 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1076 bmcJournalLogEntry) != 0) 1077 { 1078 messages::internalError(asyncResp->res); 1079 return; 1080 } 1081 } 1082 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1083 if (skip + top < entryCount) 1084 { 1085 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1086 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1087 std::to_string(skip + top); 1088 } 1089 } 1090 }; 1091 1092 class BMCJournalLogEntry : public Node 1093 { 1094 public: 1095 BMCJournalLogEntry(CrowApp &app) : 1096 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1097 std::string()) 1098 { 1099 entityPrivileges = { 1100 {boost::beast::http::verb::get, {{"Login"}}}, 1101 {boost::beast::http::verb::head, {{"Login"}}}, 1102 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1103 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1104 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1105 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1106 } 1107 1108 private: 1109 void doGet(crow::Response &res, const crow::Request &req, 1110 const std::vector<std::string> ¶ms) override 1111 { 1112 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1113 if (params.size() != 1) 1114 { 1115 messages::internalError(asyncResp->res); 1116 return; 1117 } 1118 const std::string &entryID = params[0]; 1119 // Convert the unique ID back to a timestamp to find the entry 1120 uint64_t ts = 0; 1121 uint16_t index = 0; 1122 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1123 { 1124 return; 1125 } 1126 1127 sd_journal *journalTmp = nullptr; 1128 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1129 if (ret < 0) 1130 { 1131 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1132 messages::internalError(asyncResp->res); 1133 return; 1134 } 1135 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1136 journalTmp, sd_journal_close); 1137 journalTmp = nullptr; 1138 // Go to the timestamp in the log and move to the entry at the index 1139 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1140 for (int i = 0; i <= index; i++) 1141 { 1142 sd_journal_next(journal.get()); 1143 } 1144 // Confirm that the entry ID matches what was requested 1145 std::string idStr; 1146 if (!getUniqueEntryID(journal.get(), idStr) || idStr != entryID) 1147 { 1148 messages::resourceMissingAtURI(asyncResp->res, entryID); 1149 return; 1150 } 1151 1152 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1153 asyncResp->res.jsonValue) != 0) 1154 { 1155 messages::internalError(asyncResp->res); 1156 return; 1157 } 1158 } 1159 }; 1160 1161 class CPULogService : public Node 1162 { 1163 public: 1164 template <typename CrowApp> 1165 CPULogService(CrowApp &app) : 1166 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/") 1167 { 1168 entityPrivileges = { 1169 {boost::beast::http::verb::get, {{"Login"}}}, 1170 {boost::beast::http::verb::head, {{"Login"}}}, 1171 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1172 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1173 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1174 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1175 } 1176 1177 private: 1178 /** 1179 * Functions triggers appropriate requests on DBus 1180 */ 1181 void doGet(crow::Response &res, const crow::Request &req, 1182 const std::vector<std::string> ¶ms) override 1183 { 1184 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1185 // Copy over the static data to include the entries added by SubRoute 1186 asyncResp->res.jsonValue["@odata.id"] = 1187 "/redfish/v1/Systems/system/LogServices/CpuLog"; 1188 asyncResp->res.jsonValue["@odata.type"] = 1189 "#LogService.v1_1_0.LogService"; 1190 asyncResp->res.jsonValue["@odata.context"] = 1191 "/redfish/v1/$metadata#LogService.LogService"; 1192 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Service"; 1193 asyncResp->res.jsonValue["Description"] = "CPU Log Service"; 1194 asyncResp->res.jsonValue["Id"] = "CPU Log"; 1195 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1196 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 1197 asyncResp->res.jsonValue["Entries"] = { 1198 {"@odata.id", 1199 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries"}}; 1200 asyncResp->res.jsonValue["Actions"] = { 1201 {"Oem", 1202 {{"#CpuLog.OnDemand", 1203 {{"target", "/redfish/v1/Systems/system/LogServices/CpuLog/" 1204 "Actions/Oem/CpuLog.OnDemand"}}}}}}; 1205 1206 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 1207 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 1208 {"#CpuLog.SendRawPeci", 1209 { { "target", 1210 "/redfish/v1/Systems/system/LogServices/CpuLog/" 1211 "Actions/Oem/CpuLog.SendRawPeci" } }}); 1212 #endif 1213 } 1214 }; 1215 1216 class CPULogEntryCollection : public Node 1217 { 1218 public: 1219 template <typename CrowApp> 1220 CPULogEntryCollection(CrowApp &app) : 1221 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/") 1222 { 1223 entityPrivileges = { 1224 {boost::beast::http::verb::get, {{"Login"}}}, 1225 {boost::beast::http::verb::head, {{"Login"}}}, 1226 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1227 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1228 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1229 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1230 } 1231 1232 private: 1233 /** 1234 * Functions triggers appropriate requests on DBus 1235 */ 1236 void doGet(crow::Response &res, const crow::Request &req, 1237 const std::vector<std::string> ¶ms) override 1238 { 1239 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1240 // Collections don't include the static data added by SubRoute because 1241 // it has a duplicate entry for members 1242 auto getLogEntriesCallback = [asyncResp]( 1243 const boost::system::error_code ec, 1244 const std::vector<std::string> &resp) { 1245 if (ec) 1246 { 1247 if (ec.value() != 1248 boost::system::errc::no_such_file_or_directory) 1249 { 1250 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 1251 << ec.message(); 1252 messages::internalError(asyncResp->res); 1253 return; 1254 } 1255 } 1256 asyncResp->res.jsonValue["@odata.type"] = 1257 "#LogEntryCollection.LogEntryCollection"; 1258 asyncResp->res.jsonValue["@odata.id"] = 1259 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries"; 1260 asyncResp->res.jsonValue["@odata.context"] = 1261 "/redfish/v1/" 1262 "$metadata#LogEntryCollection.LogEntryCollection"; 1263 asyncResp->res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 1264 asyncResp->res.jsonValue["Description"] = 1265 "Collection of CPU Log Entries"; 1266 nlohmann::json &logEntryArray = asyncResp->res.jsonValue["Members"]; 1267 logEntryArray = nlohmann::json::array(); 1268 for (const std::string &objpath : resp) 1269 { 1270 // Don't list the on-demand log 1271 if (objpath.compare(cpuLogOnDemandPath) == 0) 1272 { 1273 continue; 1274 } 1275 std::size_t lastPos = objpath.rfind("/"); 1276 if (lastPos != std::string::npos) 1277 { 1278 logEntryArray.push_back( 1279 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 1280 "CpuLog/Entries/" + 1281 objpath.substr(lastPos + 1)}}); 1282 } 1283 } 1284 asyncResp->res.jsonValue["Members@odata.count"] = 1285 logEntryArray.size(); 1286 }; 1287 crow::connections::systemBus->async_method_call( 1288 std::move(getLogEntriesCallback), 1289 "xyz.openbmc_project.ObjectMapper", 1290 "/xyz/openbmc_project/object_mapper", 1291 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 1292 std::array<const char *, 1>{cpuLogInterface}); 1293 } 1294 }; 1295 1296 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 1297 { 1298 nlohmann::json::const_iterator cdIt = cpuLog.find("crashlog_data"); 1299 if (cdIt != cpuLog.end()) 1300 { 1301 nlohmann::json::const_iterator siIt = cdIt->find("SYSTEM_INFO"); 1302 if (siIt != cdIt->end()) 1303 { 1304 nlohmann::json::const_iterator tsIt = siIt->find("timestamp"); 1305 if (tsIt != siIt->end()) 1306 { 1307 const std::string *logTime = 1308 tsIt->get_ptr<const std::string *>(); 1309 if (logTime != nullptr) 1310 { 1311 return *logTime; 1312 } 1313 } 1314 } 1315 } 1316 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 1317 1318 return std::string(); 1319 } 1320 1321 class CPULogEntry : public Node 1322 { 1323 public: 1324 CPULogEntry(CrowApp &app) : 1325 Node(app, 1326 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/<str>/", 1327 std::string()) 1328 { 1329 entityPrivileges = { 1330 {boost::beast::http::verb::get, {{"Login"}}}, 1331 {boost::beast::http::verb::head, {{"Login"}}}, 1332 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1333 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1334 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1335 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1336 } 1337 1338 private: 1339 void doGet(crow::Response &res, const crow::Request &req, 1340 const std::vector<std::string> ¶ms) override 1341 { 1342 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1343 if (params.size() != 1) 1344 { 1345 messages::internalError(asyncResp->res); 1346 return; 1347 } 1348 const uint8_t logId = std::atoi(params[0].c_str()); 1349 auto getStoredLogCallback = [asyncResp, logId]( 1350 const boost::system::error_code ec, 1351 const std::variant<std::string> &resp) { 1352 if (ec) 1353 { 1354 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 1355 messages::internalError(asyncResp->res); 1356 return; 1357 } 1358 const std::string *log = std::get_if<std::string>(&resp); 1359 if (log == nullptr) 1360 { 1361 messages::internalError(asyncResp->res); 1362 return; 1363 } 1364 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1365 if (j.is_discarded()) 1366 { 1367 messages::internalError(asyncResp->res); 1368 return; 1369 } 1370 std::string t = getLogCreatedTime(j); 1371 asyncResp->res.jsonValue = { 1372 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1373 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1374 {"@odata.id", 1375 "/redfish/v1/Systems/system/LogServices/CpuLog/Entries/" + 1376 std::to_string(logId)}, 1377 {"Name", "CPU Debug Log"}, 1378 {"Id", logId}, 1379 {"EntryType", "Oem"}, 1380 {"OemRecordFormat", "Intel CPU Log"}, 1381 {"Oem", {{"Intel", std::move(j)}}}, 1382 {"Created", std::move(t)}}; 1383 }; 1384 crow::connections::systemBus->async_method_call( 1385 std::move(getStoredLogCallback), cpuLogObject, 1386 cpuLogPath + std::string("/") + std::to_string(logId), 1387 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 1388 } 1389 }; 1390 1391 class OnDemandCPULog : public Node 1392 { 1393 public: 1394 OnDemandCPULog(CrowApp &app) : 1395 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/" 1396 "CpuLog.OnDemand/") 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 doPost(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 static std::unique_ptr<sdbusplus::bus::match::match> onDemandLogMatcher; 1413 1414 // Only allow one OnDemand Log request at a time 1415 if (onDemandLogMatcher != nullptr) 1416 { 1417 asyncResp->res.addHeader("Retry-After", "30"); 1418 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 1419 return; 1420 } 1421 // Make this static so it survives outside this method 1422 static boost::asio::deadline_timer timeout(*req.ioService); 1423 1424 timeout.expires_from_now(boost::posix_time::seconds(30)); 1425 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 1426 onDemandLogMatcher = nullptr; 1427 if (ec) 1428 { 1429 // operation_aborted is expected if timer is canceled before 1430 // completion. 1431 if (ec != boost::asio::error::operation_aborted) 1432 { 1433 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 1434 } 1435 return; 1436 } 1437 BMCWEB_LOG_ERROR << "Timed out waiting for on-demand log"; 1438 1439 messages::internalError(asyncResp->res); 1440 }); 1441 1442 auto onDemandLogMatcherCallback = [asyncResp]( 1443 sdbusplus::message::message &m) { 1444 BMCWEB_LOG_DEBUG << "OnDemand log available match fired"; 1445 boost::system::error_code ec; 1446 timeout.cancel(ec); 1447 if (ec) 1448 { 1449 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 1450 } 1451 sdbusplus::message::object_path objPath; 1452 boost::container::flat_map< 1453 std::string, boost::container::flat_map< 1454 std::string, std::variant<std::string>>> 1455 interfacesAdded; 1456 m.read(objPath, interfacesAdded); 1457 const std::string *log = std::get_if<std::string>( 1458 &interfacesAdded[cpuLogInterface]["Log"]); 1459 if (log == nullptr) 1460 { 1461 messages::internalError(asyncResp->res); 1462 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1463 // match object inside which this lambda is executing. Once it 1464 // is set to nullptr, the match object will be destroyed and the 1465 // lambda will lose its context, including res, so it needs to 1466 // be the last thing done. 1467 onDemandLogMatcher = nullptr; 1468 return; 1469 } 1470 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 1471 if (j.is_discarded()) 1472 { 1473 messages::internalError(asyncResp->res); 1474 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1475 // match object inside which this lambda is executing. Once it 1476 // is set to nullptr, the match object will be destroyed and the 1477 // lambda will lose its context, including res, so it needs to 1478 // be the last thing done. 1479 onDemandLogMatcher = nullptr; 1480 return; 1481 } 1482 std::string t = getLogCreatedTime(j); 1483 asyncResp->res.jsonValue = { 1484 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1485 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 1486 {"Name", "CPU Debug Log"}, 1487 {"EntryType", "Oem"}, 1488 {"OemRecordFormat", "Intel CPU Log"}, 1489 {"Oem", {{"Intel", std::move(j)}}}, 1490 {"Created", std::move(t)}}; 1491 // Careful with onDemandLogMatcher. It is a unique_ptr to the 1492 // match object inside which this lambda is executing. Once it is 1493 // set to nullptr, the match object will be destroyed and the lambda 1494 // will lose its context, including res, so it needs to be the last 1495 // thing done. 1496 onDemandLogMatcher = nullptr; 1497 }; 1498 onDemandLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 1499 *crow::connections::systemBus, 1500 sdbusplus::bus::match::rules::interfacesAdded() + 1501 sdbusplus::bus::match::rules::argNpath(0, cpuLogOnDemandPath), 1502 std::move(onDemandLogMatcherCallback)); 1503 1504 auto generateonDemandLogCallback = 1505 [asyncResp](const boost::system::error_code ec, 1506 const std::string &resp) { 1507 if (ec) 1508 { 1509 if (ec.value() == 1510 boost::system::errc::operation_not_supported) 1511 { 1512 messages::resourceInStandby(asyncResp->res); 1513 } 1514 else 1515 { 1516 messages::internalError(asyncResp->res); 1517 } 1518 boost::system::error_code timeoutec; 1519 timeout.cancel(timeoutec); 1520 if (timeoutec) 1521 { 1522 BMCWEB_LOG_ERROR << "error canceling timer " 1523 << timeoutec; 1524 } 1525 onDemandLogMatcher = nullptr; 1526 return; 1527 } 1528 }; 1529 crow::connections::systemBus->async_method_call( 1530 std::move(generateonDemandLogCallback), cpuLogObject, cpuLogPath, 1531 cpuLogOnDemandInterface, "GenerateOnDemandLog"); 1532 } 1533 }; 1534 1535 class SendRawPECI : public Node 1536 { 1537 public: 1538 SendRawPECI(CrowApp &app) : 1539 Node(app, "/redfish/v1/Systems/system/LogServices/CpuLog/Actions/Oem/" 1540 "CpuLog.SendRawPeci/") 1541 { 1542 entityPrivileges = { 1543 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 1544 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 1545 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1546 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1547 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1548 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1549 } 1550 1551 private: 1552 void doPost(crow::Response &res, const crow::Request &req, 1553 const std::vector<std::string> ¶ms) override 1554 { 1555 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1556 uint8_t clientAddress = 0; 1557 uint8_t readLength = 0; 1558 std::vector<uint8_t> peciCommand; 1559 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 1560 "ReadLength", readLength, "PECICommand", 1561 peciCommand)) 1562 { 1563 return; 1564 } 1565 1566 // Callback to return the Raw PECI response 1567 auto sendRawPECICallback = 1568 [asyncResp](const boost::system::error_code ec, 1569 const std::vector<uint8_t> &resp) { 1570 if (ec) 1571 { 1572 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 1573 << ec.message(); 1574 messages::internalError(asyncResp->res); 1575 return; 1576 } 1577 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 1578 {"PECIResponse", resp}}; 1579 }; 1580 // Call the SendRawPECI command with the provided data 1581 crow::connections::systemBus->async_method_call( 1582 std::move(sendRawPECICallback), cpuLogObject, cpuLogPath, 1583 cpuLogRawPECIInterface, "SendRawPeci", clientAddress, readLength, 1584 peciCommand); 1585 } 1586 }; 1587 1588 /** 1589 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 1590 */ 1591 class DBusLogServiceActionsClear : public Node 1592 { 1593 public: 1594 DBusLogServiceActionsClear(CrowApp &app) : 1595 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 1596 "LogService.Reset") 1597 { 1598 entityPrivileges = { 1599 {boost::beast::http::verb::get, {{"Login"}}}, 1600 {boost::beast::http::verb::head, {{"Login"}}}, 1601 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1602 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1603 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1604 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1605 } 1606 1607 private: 1608 /** 1609 * Function handles POST method request. 1610 * The Clear Log actions does not require any parameter.The action deletes 1611 * all entries found in the Entries collection for this Log Service. 1612 */ 1613 void doPost(crow::Response &res, const crow::Request &req, 1614 const std::vector<std::string> ¶ms) override 1615 { 1616 BMCWEB_LOG_DEBUG << "Do delete all entries."; 1617 1618 auto asyncResp = std::make_shared<AsyncResp>(res); 1619 // Process response from Logging service. 1620 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 1621 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 1622 if (ec) 1623 { 1624 // TODO Handle for specific error code 1625 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 1626 asyncResp->res.result( 1627 boost::beast::http::status::internal_server_error); 1628 return; 1629 } 1630 1631 asyncResp->res.result(boost::beast::http::status::no_content); 1632 }; 1633 1634 // Make call to Logging service to request Clear Log 1635 crow::connections::systemBus->async_method_call( 1636 resp_handler, "xyz.openbmc_project.Logging", 1637 "/xyz/openbmc_project/logging", 1638 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 1639 } 1640 }; 1641 } // namespace redfish 1642