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