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 static void ParseCrashdumpParameters( 880 const std::vector<std::pair<std::string, VariantType>>& params, 881 std::string& filename, std::string& timestamp, std::string& logfile) 882 { 883 for (auto property : params) 884 { 885 if (property.first == "Timestamp") 886 { 887 const std::string* value = 888 std::get_if<std::string>(&property.second); 889 if (value != nullptr) 890 { 891 timestamp = *value; 892 } 893 } 894 else if (property.first == "Filename") 895 { 896 const std::string* value = 897 std::get_if<std::string>(&property.second); 898 if (value != nullptr) 899 { 900 filename = *value; 901 } 902 } 903 else if (property.first == "Log") 904 { 905 const std::string* value = 906 std::get_if<std::string>(&property.second); 907 if (value != nullptr) 908 { 909 logfile = *value; 910 } 911 } 912 } 913 } 914 915 constexpr char const* postCodeIface = "xyz.openbmc_project.State.Boot.PostCode"; 916 class SystemLogServiceCollection : public Node 917 { 918 public: 919 template <typename CrowApp> 920 SystemLogServiceCollection(CrowApp& app) : 921 Node(app, "/redfish/v1/Systems/system/LogServices/") 922 { 923 entityPrivileges = { 924 {boost::beast::http::verb::get, {{"Login"}}}, 925 {boost::beast::http::verb::head, {{"Login"}}}, 926 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 927 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 928 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 929 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 930 } 931 932 private: 933 /** 934 * Functions triggers appropriate requests on DBus 935 */ 936 void doGet(crow::Response& res, const crow::Request& req, 937 const std::vector<std::string>& params) override 938 { 939 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 940 // Collections don't include the static data added by SubRoute because 941 // it has a duplicate entry for members 942 asyncResp->res.jsonValue["@odata.type"] = 943 "#LogServiceCollection.LogServiceCollection"; 944 asyncResp->res.jsonValue["@odata.id"] = 945 "/redfish/v1/Systems/system/LogServices"; 946 asyncResp->res.jsonValue["Name"] = "System Log Services Collection"; 947 asyncResp->res.jsonValue["Description"] = 948 "Collection of LogServices for this Computer System"; 949 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; 950 logServiceArray = nlohmann::json::array(); 951 logServiceArray.push_back( 952 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/EventLog"}}); 953 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 954 logServiceArray.push_back( 955 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/Dump"}}); 956 #endif 957 958 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 959 logServiceArray.push_back( 960 {{"@odata.id", 961 "/redfish/v1/Systems/system/LogServices/Crashdump"}}); 962 #endif 963 asyncResp->res.jsonValue["Members@odata.count"] = 964 logServiceArray.size(); 965 966 crow::connections::systemBus->async_method_call( 967 [asyncResp](const boost::system::error_code ec, 968 const std::vector<std::string>& subtreePath) { 969 if (ec) 970 { 971 BMCWEB_LOG_ERROR << ec; 972 return; 973 } 974 975 for (auto& pathStr : subtreePath) 976 { 977 if (pathStr.find("PostCode") != std::string::npos) 978 { 979 nlohmann::json& logServiceArray = 980 asyncResp->res.jsonValue["Members"]; 981 logServiceArray.push_back( 982 {{"@odata.id", "/redfish/v1/Systems/system/" 983 "LogServices/PostCodes"}}); 984 asyncResp->res.jsonValue["Members@odata.count"] = 985 logServiceArray.size(); 986 return; 987 } 988 } 989 }, 990 "xyz.openbmc_project.ObjectMapper", 991 "/xyz/openbmc_project/object_mapper", 992 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 0, 993 std::array<const char*, 1>{postCodeIface}); 994 } 995 }; 996 997 class EventLogService : public Node 998 { 999 public: 1000 template <typename CrowApp> 1001 EventLogService(CrowApp& app) : 1002 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/") 1003 { 1004 entityPrivileges = { 1005 {boost::beast::http::verb::get, {{"Login"}}}, 1006 {boost::beast::http::verb::head, {{"Login"}}}, 1007 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1008 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1009 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1010 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1011 } 1012 1013 private: 1014 void doGet(crow::Response& res, const crow::Request& req, 1015 const std::vector<std::string>& params) override 1016 { 1017 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1018 1019 asyncResp->res.jsonValue["@odata.id"] = 1020 "/redfish/v1/Systems/system/LogServices/EventLog"; 1021 asyncResp->res.jsonValue["@odata.type"] = 1022 "#LogService.v1_1_0.LogService"; 1023 asyncResp->res.jsonValue["Name"] = "Event Log Service"; 1024 asyncResp->res.jsonValue["Description"] = "System Event Log Service"; 1025 asyncResp->res.jsonValue["Id"] = "EventLog"; 1026 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1027 asyncResp->res.jsonValue["Entries"] = { 1028 {"@odata.id", 1029 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 1030 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 1031 1032 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 1033 "Actions/LogService.ClearLog"}}; 1034 } 1035 }; 1036 1037 class JournalEventLogClear : public Node 1038 { 1039 public: 1040 JournalEventLogClear(CrowApp& app) : 1041 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 1042 "LogService.ClearLog/") 1043 { 1044 entityPrivileges = { 1045 {boost::beast::http::verb::get, {{"Login"}}}, 1046 {boost::beast::http::verb::head, {{"Login"}}}, 1047 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1048 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1049 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1050 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1051 } 1052 1053 private: 1054 void doPost(crow::Response& res, const crow::Request& req, 1055 const std::vector<std::string>& params) override 1056 { 1057 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1058 1059 // Clear the EventLog by deleting the log files 1060 std::vector<std::filesystem::path> redfishLogFiles; 1061 if (getRedfishLogFiles(redfishLogFiles)) 1062 { 1063 for (const std::filesystem::path& file : redfishLogFiles) 1064 { 1065 std::error_code ec; 1066 std::filesystem::remove(file, ec); 1067 } 1068 } 1069 1070 // Reload rsyslog so it knows to start new log files 1071 crow::connections::systemBus->async_method_call( 1072 [asyncResp](const boost::system::error_code ec) { 1073 if (ec) 1074 { 1075 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " << ec; 1076 messages::internalError(asyncResp->res); 1077 return; 1078 } 1079 1080 messages::success(asyncResp->res); 1081 }, 1082 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1083 "org.freedesktop.systemd1.Manager", "ReloadUnit", "rsyslog.service", 1084 "replace"); 1085 } 1086 }; 1087 1088 static int fillEventLogEntryJson(const std::string& logEntryID, 1089 const std::string logEntry, 1090 nlohmann::json& logEntryJson) 1091 { 1092 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 1093 // First get the Timestamp 1094 size_t space = logEntry.find_first_of(" "); 1095 if (space == std::string::npos) 1096 { 1097 return 1; 1098 } 1099 std::string timestamp = logEntry.substr(0, space); 1100 // Then get the log contents 1101 size_t entryStart = logEntry.find_first_not_of(" ", space); 1102 if (entryStart == std::string::npos) 1103 { 1104 return 1; 1105 } 1106 std::string_view entry(logEntry); 1107 entry.remove_prefix(entryStart); 1108 // Use split to separate the entry into its fields 1109 std::vector<std::string> logEntryFields; 1110 boost::split(logEntryFields, entry, boost::is_any_of(","), 1111 boost::token_compress_on); 1112 // We need at least a MessageId to be valid 1113 if (logEntryFields.size() < 1) 1114 { 1115 return 1; 1116 } 1117 std::string& messageID = logEntryFields[0]; 1118 1119 // Get the Message from the MessageRegistry 1120 const message_registries::Message* message = 1121 message_registries::getMessage(messageID); 1122 1123 std::string msg; 1124 std::string severity; 1125 if (message != nullptr) 1126 { 1127 msg = message->message; 1128 severity = message->severity; 1129 } 1130 1131 // Get the MessageArgs from the log if there are any 1132 boost::beast::span<std::string> messageArgs; 1133 if (logEntryFields.size() > 1) 1134 { 1135 std::string& messageArgsStart = logEntryFields[1]; 1136 // If the first string is empty, assume there are no MessageArgs 1137 std::size_t messageArgsSize = 0; 1138 if (!messageArgsStart.empty()) 1139 { 1140 messageArgsSize = logEntryFields.size() - 1; 1141 } 1142 1143 messageArgs = boost::beast::span(&messageArgsStart, messageArgsSize); 1144 1145 // Fill the MessageArgs into the Message 1146 int i = 0; 1147 for (const std::string& messageArg : messageArgs) 1148 { 1149 std::string argStr = "%" + std::to_string(++i); 1150 size_t argPos = msg.find(argStr); 1151 if (argPos != std::string::npos) 1152 { 1153 msg.replace(argPos, argStr.length(), messageArg); 1154 } 1155 } 1156 } 1157 1158 // Get the Created time from the timestamp. The log timestamp is in RFC3339 1159 // format which matches the Redfish format except for the fractional seconds 1160 // between the '.' and the '+', so just remove them. 1161 std::size_t dot = timestamp.find_first_of("."); 1162 std::size_t plus = timestamp.find_first_of("+"); 1163 if (dot != std::string::npos && plus != std::string::npos) 1164 { 1165 timestamp.erase(dot, plus - dot); 1166 } 1167 1168 // Fill in the log entry with the gathered data 1169 logEntryJson = { 1170 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1171 {"@odata.id", 1172 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1173 logEntryID}, 1174 {"Name", "System Event Log Entry"}, 1175 {"Id", logEntryID}, 1176 {"Message", std::move(msg)}, 1177 {"MessageId", std::move(messageID)}, 1178 {"MessageArgs", std::move(messageArgs)}, 1179 {"EntryType", "Event"}, 1180 {"Severity", std::move(severity)}, 1181 {"Created", std::move(timestamp)}}; 1182 return 0; 1183 } 1184 1185 class JournalEventLogEntryCollection : public Node 1186 { 1187 public: 1188 template <typename CrowApp> 1189 JournalEventLogEntryCollection(CrowApp& app) : 1190 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 1191 { 1192 entityPrivileges = { 1193 {boost::beast::http::verb::get, {{"Login"}}}, 1194 {boost::beast::http::verb::head, {{"Login"}}}, 1195 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1196 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1197 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1198 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1199 } 1200 1201 private: 1202 void doGet(crow::Response& res, const crow::Request& req, 1203 const std::vector<std::string>& params) override 1204 { 1205 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1206 uint64_t skip = 0; 1207 uint64_t top = maxEntriesPerPage; // Show max entries by default 1208 if (!getSkipParam(asyncResp->res, req, skip)) 1209 { 1210 return; 1211 } 1212 if (!getTopParam(asyncResp->res, req, top)) 1213 { 1214 return; 1215 } 1216 // Collections don't include the static data added by SubRoute because 1217 // it has a duplicate entry for members 1218 asyncResp->res.jsonValue["@odata.type"] = 1219 "#LogEntryCollection.LogEntryCollection"; 1220 asyncResp->res.jsonValue["@odata.id"] = 1221 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 1222 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1223 asyncResp->res.jsonValue["Description"] = 1224 "Collection of System Event Log Entries"; 1225 1226 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 1227 logEntryArray = nlohmann::json::array(); 1228 // Go through the log files and create a unique ID for each entry 1229 std::vector<std::filesystem::path> redfishLogFiles; 1230 getRedfishLogFiles(redfishLogFiles); 1231 uint64_t entryCount = 0; 1232 std::string logEntry; 1233 1234 // Oldest logs are in the last file, so start there and loop backwards 1235 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 1236 it++) 1237 { 1238 std::ifstream logStream(*it); 1239 if (!logStream.is_open()) 1240 { 1241 continue; 1242 } 1243 1244 // Reset the unique ID on the first entry 1245 bool firstEntry = true; 1246 while (std::getline(logStream, logEntry)) 1247 { 1248 entryCount++; 1249 // Handle paging using skip (number of entries to skip from the 1250 // start) and top (number of entries to display) 1251 if (entryCount <= skip || entryCount > skip + top) 1252 { 1253 continue; 1254 } 1255 1256 std::string idStr; 1257 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1258 { 1259 continue; 1260 } 1261 1262 if (firstEntry) 1263 { 1264 firstEntry = false; 1265 } 1266 1267 logEntryArray.push_back({}); 1268 nlohmann::json& bmcLogEntry = logEntryArray.back(); 1269 if (fillEventLogEntryJson(idStr, logEntry, bmcLogEntry) != 0) 1270 { 1271 messages::internalError(asyncResp->res); 1272 return; 1273 } 1274 } 1275 } 1276 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1277 if (skip + top < entryCount) 1278 { 1279 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1280 "/redfish/v1/Systems/system/LogServices/EventLog/" 1281 "Entries?$skip=" + 1282 std::to_string(skip + top); 1283 } 1284 } 1285 }; 1286 1287 class JournalEventLogEntry : public Node 1288 { 1289 public: 1290 JournalEventLogEntry(CrowApp& app) : 1291 Node(app, 1292 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 1293 std::string()) 1294 { 1295 entityPrivileges = { 1296 {boost::beast::http::verb::get, {{"Login"}}}, 1297 {boost::beast::http::verb::head, {{"Login"}}}, 1298 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1299 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1300 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1301 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1302 } 1303 1304 private: 1305 void doGet(crow::Response& res, const crow::Request& req, 1306 const std::vector<std::string>& params) override 1307 { 1308 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1309 if (params.size() != 1) 1310 { 1311 messages::internalError(asyncResp->res); 1312 return; 1313 } 1314 const std::string& targetID = params[0]; 1315 1316 // Go through the log files and check the unique ID for each entry to 1317 // find the target entry 1318 std::vector<std::filesystem::path> redfishLogFiles; 1319 getRedfishLogFiles(redfishLogFiles); 1320 std::string logEntry; 1321 1322 // Oldest logs are in the last file, so start there and loop backwards 1323 for (auto it = redfishLogFiles.rbegin(); it < redfishLogFiles.rend(); 1324 it++) 1325 { 1326 std::ifstream logStream(*it); 1327 if (!logStream.is_open()) 1328 { 1329 continue; 1330 } 1331 1332 // Reset the unique ID on the first entry 1333 bool firstEntry = true; 1334 while (std::getline(logStream, logEntry)) 1335 { 1336 std::string idStr; 1337 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1338 { 1339 continue; 1340 } 1341 1342 if (firstEntry) 1343 { 1344 firstEntry = false; 1345 } 1346 1347 if (idStr == targetID) 1348 { 1349 if (fillEventLogEntryJson(idStr, logEntry, 1350 asyncResp->res.jsonValue) != 0) 1351 { 1352 messages::internalError(asyncResp->res); 1353 return; 1354 } 1355 return; 1356 } 1357 } 1358 } 1359 // Requested ID was not found 1360 messages::resourceMissingAtURI(asyncResp->res, targetID); 1361 } 1362 }; 1363 1364 class DBusEventLogEntryCollection : public Node 1365 { 1366 public: 1367 template <typename CrowApp> 1368 DBusEventLogEntryCollection(CrowApp& app) : 1369 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 1370 { 1371 entityPrivileges = { 1372 {boost::beast::http::verb::get, {{"Login"}}}, 1373 {boost::beast::http::verb::head, {{"Login"}}}, 1374 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1375 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1376 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1377 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1378 } 1379 1380 private: 1381 void doGet(crow::Response& res, const crow::Request& req, 1382 const std::vector<std::string>& params) override 1383 { 1384 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1385 1386 // Collections don't include the static data added by SubRoute because 1387 // it has a duplicate entry for members 1388 asyncResp->res.jsonValue["@odata.type"] = 1389 "#LogEntryCollection.LogEntryCollection"; 1390 asyncResp->res.jsonValue["@odata.id"] = 1391 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 1392 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1393 asyncResp->res.jsonValue["Description"] = 1394 "Collection of System Event Log Entries"; 1395 1396 // DBus implementation of EventLog/Entries 1397 // Make call to Logging Service to find all log entry objects 1398 crow::connections::systemBus->async_method_call( 1399 [asyncResp](const boost::system::error_code ec, 1400 GetManagedObjectsType& resp) { 1401 if (ec) 1402 { 1403 // TODO Handle for specific error code 1404 BMCWEB_LOG_ERROR 1405 << "getLogEntriesIfaceData resp_handler got error " 1406 << ec; 1407 messages::internalError(asyncResp->res); 1408 return; 1409 } 1410 nlohmann::json& entriesArray = 1411 asyncResp->res.jsonValue["Members"]; 1412 entriesArray = nlohmann::json::array(); 1413 for (auto& objectPath : resp) 1414 { 1415 for (auto& interfaceMap : objectPath.second) 1416 { 1417 if (interfaceMap.first != 1418 "xyz.openbmc_project.Logging.Entry") 1419 { 1420 BMCWEB_LOG_DEBUG << "Bailing early on " 1421 << interfaceMap.first; 1422 continue; 1423 } 1424 entriesArray.push_back({}); 1425 nlohmann::json& thisEntry = entriesArray.back(); 1426 uint32_t* id = nullptr; 1427 std::time_t timestamp{}; 1428 std::string* severity = nullptr; 1429 std::string* message = nullptr; 1430 for (auto& propertyMap : interfaceMap.second) 1431 { 1432 if (propertyMap.first == "Id") 1433 { 1434 id = std::get_if<uint32_t>(&propertyMap.second); 1435 if (id == nullptr) 1436 { 1437 messages::propertyMissing(asyncResp->res, 1438 "Id"); 1439 } 1440 } 1441 else if (propertyMap.first == "Timestamp") 1442 { 1443 const uint64_t* millisTimeStamp = 1444 std::get_if<uint64_t>(&propertyMap.second); 1445 if (millisTimeStamp == nullptr) 1446 { 1447 messages::propertyMissing(asyncResp->res, 1448 "Timestamp"); 1449 continue; 1450 } 1451 // Retrieve Created property with format: 1452 // yyyy-mm-ddThh:mm:ss 1453 std::chrono::milliseconds chronoTimeStamp( 1454 *millisTimeStamp); 1455 timestamp = std::chrono::duration_cast< 1456 std::chrono::duration<int>>( 1457 chronoTimeStamp) 1458 .count(); 1459 } 1460 else if (propertyMap.first == "Severity") 1461 { 1462 severity = std::get_if<std::string>( 1463 &propertyMap.second); 1464 if (severity == nullptr) 1465 { 1466 messages::propertyMissing(asyncResp->res, 1467 "Severity"); 1468 } 1469 } 1470 else if (propertyMap.first == "Message") 1471 { 1472 message = std::get_if<std::string>( 1473 &propertyMap.second); 1474 if (message == nullptr) 1475 { 1476 messages::propertyMissing(asyncResp->res, 1477 "Message"); 1478 } 1479 } 1480 } 1481 thisEntry = { 1482 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1483 {"@odata.id", 1484 "/redfish/v1/Systems/system/LogServices/EventLog/" 1485 "Entries/" + 1486 std::to_string(*id)}, 1487 {"Name", "System Event Log Entry"}, 1488 {"Id", std::to_string(*id)}, 1489 {"Message", *message}, 1490 {"EntryType", "Event"}, 1491 {"Severity", 1492 translateSeverityDbusToRedfish(*severity)}, 1493 {"Created", crow::utility::getDateTime(timestamp)}}; 1494 } 1495 } 1496 std::sort(entriesArray.begin(), entriesArray.end(), 1497 [](const nlohmann::json& left, 1498 const nlohmann::json& right) { 1499 return (left["Id"] <= right["Id"]); 1500 }); 1501 asyncResp->res.jsonValue["Members@odata.count"] = 1502 entriesArray.size(); 1503 }, 1504 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 1505 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1506 } 1507 }; 1508 1509 class DBusEventLogEntry : public Node 1510 { 1511 public: 1512 DBusEventLogEntry(CrowApp& app) : 1513 Node(app, 1514 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 1515 std::string()) 1516 { 1517 entityPrivileges = { 1518 {boost::beast::http::verb::get, {{"Login"}}}, 1519 {boost::beast::http::verb::head, {{"Login"}}}, 1520 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1521 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1522 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1523 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1524 } 1525 1526 private: 1527 void doGet(crow::Response& res, const crow::Request& req, 1528 const std::vector<std::string>& params) override 1529 { 1530 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1531 if (params.size() != 1) 1532 { 1533 messages::internalError(asyncResp->res); 1534 return; 1535 } 1536 const std::string& entryID = params[0]; 1537 1538 // DBus implementation of EventLog/Entries 1539 // Make call to Logging Service to find all log entry objects 1540 crow::connections::systemBus->async_method_call( 1541 [asyncResp, entryID](const boost::system::error_code ec, 1542 GetManagedPropertyType& resp) { 1543 if (ec) 1544 { 1545 BMCWEB_LOG_ERROR 1546 << "EventLogEntry (DBus) resp_handler got error " << ec; 1547 messages::internalError(asyncResp->res); 1548 return; 1549 } 1550 uint32_t* id = nullptr; 1551 std::time_t timestamp{}; 1552 std::string* severity = nullptr; 1553 std::string* message = nullptr; 1554 for (auto& propertyMap : resp) 1555 { 1556 if (propertyMap.first == "Id") 1557 { 1558 id = std::get_if<uint32_t>(&propertyMap.second); 1559 if (id == nullptr) 1560 { 1561 messages::propertyMissing(asyncResp->res, "Id"); 1562 } 1563 } 1564 else if (propertyMap.first == "Timestamp") 1565 { 1566 const uint64_t* millisTimeStamp = 1567 std::get_if<uint64_t>(&propertyMap.second); 1568 if (millisTimeStamp == nullptr) 1569 { 1570 messages::propertyMissing(asyncResp->res, 1571 "Timestamp"); 1572 continue; 1573 } 1574 // Retrieve Created property with format: 1575 // yyyy-mm-ddThh:mm:ss 1576 std::chrono::milliseconds chronoTimeStamp( 1577 *millisTimeStamp); 1578 timestamp = 1579 std::chrono::duration_cast< 1580 std::chrono::duration<int>>(chronoTimeStamp) 1581 .count(); 1582 } 1583 else if (propertyMap.first == "Severity") 1584 { 1585 severity = 1586 std::get_if<std::string>(&propertyMap.second); 1587 if (severity == nullptr) 1588 { 1589 messages::propertyMissing(asyncResp->res, 1590 "Severity"); 1591 } 1592 } 1593 else if (propertyMap.first == "Message") 1594 { 1595 message = std::get_if<std::string>(&propertyMap.second); 1596 if (message == nullptr) 1597 { 1598 messages::propertyMissing(asyncResp->res, 1599 "Message"); 1600 } 1601 } 1602 } 1603 if (id == nullptr || message == nullptr || severity == nullptr) 1604 { 1605 return; 1606 } 1607 asyncResp->res.jsonValue = { 1608 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1609 {"@odata.id", 1610 "/redfish/v1/Systems/system/LogServices/EventLog/" 1611 "Entries/" + 1612 std::to_string(*id)}, 1613 {"Name", "System Event Log Entry"}, 1614 {"Id", std::to_string(*id)}, 1615 {"Message", *message}, 1616 {"EntryType", "Event"}, 1617 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1618 {"Created", crow::utility::getDateTime(timestamp)}}; 1619 }, 1620 "xyz.openbmc_project.Logging", 1621 "/xyz/openbmc_project/logging/entry/" + entryID, 1622 "org.freedesktop.DBus.Properties", "GetAll", 1623 "xyz.openbmc_project.Logging.Entry"); 1624 } 1625 1626 void doDelete(crow::Response& res, const crow::Request& req, 1627 const std::vector<std::string>& params) override 1628 { 1629 1630 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1631 1632 auto asyncResp = std::make_shared<AsyncResp>(res); 1633 1634 if (params.size() != 1) 1635 { 1636 messages::internalError(asyncResp->res); 1637 return; 1638 } 1639 std::string entryID = params[0]; 1640 1641 dbus::utility::escapePathForDbus(entryID); 1642 1643 // Process response from Logging service. 1644 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1645 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1646 if (ec) 1647 { 1648 // TODO Handle for specific error code 1649 BMCWEB_LOG_ERROR 1650 << "EventLogEntry (DBus) doDelete respHandler got error " 1651 << ec; 1652 asyncResp->res.result( 1653 boost::beast::http::status::internal_server_error); 1654 return; 1655 } 1656 1657 asyncResp->res.result(boost::beast::http::status::ok); 1658 }; 1659 1660 // Make call to Logging service to request Delete Log 1661 crow::connections::systemBus->async_method_call( 1662 respHandler, "xyz.openbmc_project.Logging", 1663 "/xyz/openbmc_project/logging/entry/" + entryID, 1664 "xyz.openbmc_project.Object.Delete", "Delete"); 1665 } 1666 }; 1667 1668 class BMCLogServiceCollection : public Node 1669 { 1670 public: 1671 template <typename CrowApp> 1672 BMCLogServiceCollection(CrowApp& app) : 1673 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1674 { 1675 entityPrivileges = { 1676 {boost::beast::http::verb::get, {{"Login"}}}, 1677 {boost::beast::http::verb::head, {{"Login"}}}, 1678 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1679 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1680 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1681 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1682 } 1683 1684 private: 1685 /** 1686 * Functions triggers appropriate requests on DBus 1687 */ 1688 void doGet(crow::Response& res, const crow::Request& req, 1689 const std::vector<std::string>& params) override 1690 { 1691 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1692 // Collections don't include the static data added by SubRoute because 1693 // it has a duplicate entry for members 1694 asyncResp->res.jsonValue["@odata.type"] = 1695 "#LogServiceCollection.LogServiceCollection"; 1696 asyncResp->res.jsonValue["@odata.id"] = 1697 "/redfish/v1/Managers/bmc/LogServices"; 1698 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1699 asyncResp->res.jsonValue["Description"] = 1700 "Collection of LogServices for this Manager"; 1701 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; 1702 logServiceArray = nlohmann::json::array(); 1703 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 1704 logServiceArray.push_back( 1705 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump"}}); 1706 #endif 1707 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1708 logServiceArray.push_back( 1709 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1710 #endif 1711 asyncResp->res.jsonValue["Members@odata.count"] = 1712 logServiceArray.size(); 1713 } 1714 }; 1715 1716 class BMCJournalLogService : public Node 1717 { 1718 public: 1719 template <typename CrowApp> 1720 BMCJournalLogService(CrowApp& app) : 1721 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1722 { 1723 entityPrivileges = { 1724 {boost::beast::http::verb::get, {{"Login"}}}, 1725 {boost::beast::http::verb::head, {{"Login"}}}, 1726 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1727 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1728 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1729 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1730 } 1731 1732 private: 1733 void doGet(crow::Response& res, const crow::Request& req, 1734 const std::vector<std::string>& params) override 1735 { 1736 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1737 asyncResp->res.jsonValue["@odata.type"] = 1738 "#LogService.v1_1_0.LogService"; 1739 asyncResp->res.jsonValue["@odata.id"] = 1740 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1741 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1742 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1743 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1744 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1745 asyncResp->res.jsonValue["Entries"] = { 1746 {"@odata.id", 1747 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1748 } 1749 }; 1750 1751 static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 1752 sd_journal* journal, 1753 nlohmann::json& bmcJournalLogEntryJson) 1754 { 1755 // Get the Log Entry contents 1756 int ret = 0; 1757 1758 std::string_view msg; 1759 ret = getJournalMetadata(journal, "MESSAGE", msg); 1760 if (ret < 0) 1761 { 1762 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1763 return 1; 1764 } 1765 1766 // Get the severity from the PRIORITY field 1767 long int severity = 8; // Default to an invalid priority 1768 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1769 if (ret < 0) 1770 { 1771 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1772 } 1773 1774 // Get the Created time from the timestamp 1775 std::string entryTimeStr; 1776 if (!getEntryTimestamp(journal, entryTimeStr)) 1777 { 1778 return 1; 1779 } 1780 1781 // Fill in the log entry with the gathered data 1782 bmcJournalLogEntryJson = { 1783 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1784 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1785 bmcJournalLogEntryID}, 1786 {"Name", "BMC Journal Entry"}, 1787 {"Id", bmcJournalLogEntryID}, 1788 {"Message", msg}, 1789 {"EntryType", "Oem"}, 1790 {"Severity", 1791 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1792 {"OemRecordFormat", "BMC Journal Entry"}, 1793 {"Created", std::move(entryTimeStr)}}; 1794 return 0; 1795 } 1796 1797 class BMCJournalLogEntryCollection : public Node 1798 { 1799 public: 1800 template <typename CrowApp> 1801 BMCJournalLogEntryCollection(CrowApp& app) : 1802 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1803 { 1804 entityPrivileges = { 1805 {boost::beast::http::verb::get, {{"Login"}}}, 1806 {boost::beast::http::verb::head, {{"Login"}}}, 1807 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1808 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1809 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1810 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1811 } 1812 1813 private: 1814 void doGet(crow::Response& res, const crow::Request& req, 1815 const std::vector<std::string>& params) override 1816 { 1817 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1818 static constexpr const long maxEntriesPerPage = 1000; 1819 uint64_t skip = 0; 1820 uint64_t top = maxEntriesPerPage; // Show max entries by default 1821 if (!getSkipParam(asyncResp->res, req, skip)) 1822 { 1823 return; 1824 } 1825 if (!getTopParam(asyncResp->res, req, top)) 1826 { 1827 return; 1828 } 1829 // Collections don't include the static data added by SubRoute because 1830 // it has a duplicate entry for members 1831 asyncResp->res.jsonValue["@odata.type"] = 1832 "#LogEntryCollection.LogEntryCollection"; 1833 asyncResp->res.jsonValue["@odata.id"] = 1834 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1835 asyncResp->res.jsonValue["@odata.id"] = 1836 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1837 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1838 asyncResp->res.jsonValue["Description"] = 1839 "Collection of BMC Journal Entries"; 1840 asyncResp->res.jsonValue["@odata.id"] = 1841 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1842 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 1843 logEntryArray = nlohmann::json::array(); 1844 1845 // Go through the journal and use the timestamp to create a unique ID 1846 // for each entry 1847 sd_journal* journalTmp = nullptr; 1848 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1849 if (ret < 0) 1850 { 1851 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1852 messages::internalError(asyncResp->res); 1853 return; 1854 } 1855 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1856 journalTmp, sd_journal_close); 1857 journalTmp = nullptr; 1858 uint64_t entryCount = 0; 1859 // Reset the unique ID on the first entry 1860 bool firstEntry = true; 1861 SD_JOURNAL_FOREACH(journal.get()) 1862 { 1863 entryCount++; 1864 // Handle paging using skip (number of entries to skip from the 1865 // start) and top (number of entries to display) 1866 if (entryCount <= skip || entryCount > skip + top) 1867 { 1868 continue; 1869 } 1870 1871 std::string idStr; 1872 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1873 { 1874 continue; 1875 } 1876 1877 if (firstEntry) 1878 { 1879 firstEntry = false; 1880 } 1881 1882 logEntryArray.push_back({}); 1883 nlohmann::json& bmcJournalLogEntry = logEntryArray.back(); 1884 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1885 bmcJournalLogEntry) != 0) 1886 { 1887 messages::internalError(asyncResp->res); 1888 return; 1889 } 1890 } 1891 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1892 if (skip + top < entryCount) 1893 { 1894 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1895 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1896 std::to_string(skip + top); 1897 } 1898 } 1899 }; 1900 1901 class BMCJournalLogEntry : public Node 1902 { 1903 public: 1904 BMCJournalLogEntry(CrowApp& app) : 1905 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1906 std::string()) 1907 { 1908 entityPrivileges = { 1909 {boost::beast::http::verb::get, {{"Login"}}}, 1910 {boost::beast::http::verb::head, {{"Login"}}}, 1911 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1912 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1913 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1914 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1915 } 1916 1917 private: 1918 void doGet(crow::Response& res, const crow::Request& req, 1919 const std::vector<std::string>& params) override 1920 { 1921 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1922 if (params.size() != 1) 1923 { 1924 messages::internalError(asyncResp->res); 1925 return; 1926 } 1927 const std::string& entryID = params[0]; 1928 // Convert the unique ID back to a timestamp to find the entry 1929 uint64_t ts = 0; 1930 uint64_t index = 0; 1931 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1932 { 1933 return; 1934 } 1935 1936 sd_journal* journalTmp = nullptr; 1937 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1938 if (ret < 0) 1939 { 1940 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1941 messages::internalError(asyncResp->res); 1942 return; 1943 } 1944 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1945 journalTmp, sd_journal_close); 1946 journalTmp = nullptr; 1947 // Go to the timestamp in the log and move to the entry at the index 1948 // tracking the unique ID 1949 std::string idStr; 1950 bool firstEntry = true; 1951 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1952 if (ret < 0) 1953 { 1954 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 1955 << strerror(-ret); 1956 messages::internalError(asyncResp->res); 1957 return; 1958 } 1959 for (uint64_t i = 0; i <= index; i++) 1960 { 1961 sd_journal_next(journal.get()); 1962 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1963 { 1964 messages::internalError(asyncResp->res); 1965 return; 1966 } 1967 if (firstEntry) 1968 { 1969 firstEntry = false; 1970 } 1971 } 1972 // Confirm that the entry ID matches what was requested 1973 if (idStr != entryID) 1974 { 1975 messages::resourceMissingAtURI(asyncResp->res, entryID); 1976 return; 1977 } 1978 1979 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1980 asyncResp->res.jsonValue) != 0) 1981 { 1982 messages::internalError(asyncResp->res); 1983 return; 1984 } 1985 } 1986 }; 1987 1988 class BMCDumpService : public Node 1989 { 1990 public: 1991 template <typename CrowApp> 1992 BMCDumpService(CrowApp& app) : 1993 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 1994 { 1995 entityPrivileges = { 1996 {boost::beast::http::verb::get, {{"Login"}}}, 1997 {boost::beast::http::verb::head, {{"Login"}}}, 1998 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1999 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2000 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2001 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2002 } 2003 2004 private: 2005 void doGet(crow::Response& res, const crow::Request& req, 2006 const std::vector<std::string>& params) override 2007 { 2008 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2009 2010 asyncResp->res.jsonValue["@odata.id"] = 2011 "/redfish/v1/Managers/bmc/LogServices/Dump"; 2012 asyncResp->res.jsonValue["@odata.type"] = 2013 "#LogService.v1_1_0.LogService"; 2014 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2015 asyncResp->res.jsonValue["Description"] = "BMC Dump LogService"; 2016 asyncResp->res.jsonValue["Id"] = "Dump"; 2017 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2018 asyncResp->res.jsonValue["Entries"] = { 2019 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}}; 2020 asyncResp->res.jsonValue["Actions"] = { 2021 {"#LogService.ClearLog", 2022 {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/" 2023 "Actions/LogService.ClearLog"}}}, 2024 {"Oem", 2025 {{"#OemLogService.CollectDiagnosticData", 2026 {{"target", 2027 "/redfish/v1/Managers/bmc/LogServices/Dump/" 2028 "Actions/Oem/OemLogService.CollectDiagnosticData"}}}}}}; 2029 } 2030 }; 2031 2032 class BMCDumpEntryCollection : public Node 2033 { 2034 public: 2035 template <typename CrowApp> 2036 BMCDumpEntryCollection(CrowApp& app) : 2037 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 2038 { 2039 entityPrivileges = { 2040 {boost::beast::http::verb::get, {{"Login"}}}, 2041 {boost::beast::http::verb::head, {{"Login"}}}, 2042 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2043 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2044 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2045 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2046 } 2047 2048 private: 2049 /** 2050 * Functions triggers appropriate requests on DBus 2051 */ 2052 void doGet(crow::Response& res, const crow::Request& req, 2053 const std::vector<std::string>& params) override 2054 { 2055 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2056 2057 asyncResp->res.jsonValue["@odata.type"] = 2058 "#LogEntryCollection.LogEntryCollection"; 2059 asyncResp->res.jsonValue["@odata.id"] = 2060 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; 2061 asyncResp->res.jsonValue["Name"] = "BMC Dump Entries"; 2062 asyncResp->res.jsonValue["Description"] = 2063 "Collection of BMC Dump Entries"; 2064 2065 getDumpEntryCollection(asyncResp, "BMC"); 2066 } 2067 }; 2068 2069 class BMCDumpEntry : public Node 2070 { 2071 public: 2072 BMCDumpEntry(CrowApp& app) : 2073 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/", 2074 std::string()) 2075 { 2076 entityPrivileges = { 2077 {boost::beast::http::verb::get, {{"Login"}}}, 2078 {boost::beast::http::verb::head, {{"Login"}}}, 2079 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2080 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2081 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2082 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2083 } 2084 2085 private: 2086 void doGet(crow::Response& res, const crow::Request& req, 2087 const std::vector<std::string>& params) override 2088 { 2089 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2090 if (params.size() != 1) 2091 { 2092 messages::internalError(asyncResp->res); 2093 return; 2094 } 2095 getDumpEntryById(asyncResp, params[0], "BMC"); 2096 } 2097 2098 void doDelete(crow::Response& res, const crow::Request& req, 2099 const std::vector<std::string>& params) override 2100 { 2101 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2102 if (params.size() != 1) 2103 { 2104 messages::internalError(asyncResp->res); 2105 return; 2106 } 2107 deleteDumpEntry(asyncResp->res, params[0]); 2108 } 2109 }; 2110 2111 class BMCDumpCreate : public Node 2112 { 2113 public: 2114 BMCDumpCreate(CrowApp& app) : 2115 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/" 2116 "Actions/Oem/" 2117 "OemLogService.CollectDiagnosticData/") 2118 { 2119 entityPrivileges = { 2120 {boost::beast::http::verb::get, {{"Login"}}}, 2121 {boost::beast::http::verb::head, {{"Login"}}}, 2122 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2123 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2124 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2125 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2126 } 2127 2128 private: 2129 void doPost(crow::Response& res, const crow::Request& req, 2130 const std::vector<std::string>& params) override 2131 { 2132 createDump(res, req, "BMC"); 2133 } 2134 }; 2135 2136 class SystemDumpService : public Node 2137 { 2138 public: 2139 template <typename CrowApp> 2140 SystemDumpService(CrowApp& app) : 2141 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/") 2142 { 2143 entityPrivileges = { 2144 {boost::beast::http::verb::get, {{"Login"}}}, 2145 {boost::beast::http::verb::head, {{"Login"}}}, 2146 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2147 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2148 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2149 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2150 } 2151 2152 private: 2153 void doGet(crow::Response& res, const crow::Request& req, 2154 const std::vector<std::string>& params) override 2155 { 2156 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2157 2158 asyncResp->res.jsonValue["@odata.id"] = 2159 "/redfish/v1/Systems/system/LogServices/Dump"; 2160 asyncResp->res.jsonValue["@odata.type"] = 2161 "#LogService.v1_1_0.LogService"; 2162 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2163 asyncResp->res.jsonValue["Description"] = "System Dump LogService"; 2164 asyncResp->res.jsonValue["Id"] = "Dump"; 2165 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2166 asyncResp->res.jsonValue["Entries"] = { 2167 {"@odata.id", 2168 "/redfish/v1/Systems/system/LogServices/Dump/Entries"}}; 2169 asyncResp->res.jsonValue["Actions"] = { 2170 {"#LogService.ClearLog", 2171 {{"target", "/redfish/v1/Systems/system/LogServices/Dump/Actions/" 2172 "LogService.ClearLog"}}}, 2173 {"Oem", 2174 {{"#OemLogService.CollectDiagnosticData", 2175 {{"target", 2176 "/redfish/v1/Systems/system/LogServices/Dump/Actions/Oem/" 2177 "OemLogService.CollectDiagnosticData"}}}}}}; 2178 } 2179 }; 2180 2181 class SystemDumpEntryCollection : public Node 2182 { 2183 public: 2184 template <typename CrowApp> 2185 SystemDumpEntryCollection(CrowApp& app) : 2186 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/") 2187 { 2188 entityPrivileges = { 2189 {boost::beast::http::verb::get, {{"Login"}}}, 2190 {boost::beast::http::verb::head, {{"Login"}}}, 2191 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2192 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2193 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2194 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2195 } 2196 2197 private: 2198 /** 2199 * Functions triggers appropriate requests on DBus 2200 */ 2201 void doGet(crow::Response& res, const crow::Request& req, 2202 const std::vector<std::string>& params) override 2203 { 2204 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2205 2206 asyncResp->res.jsonValue["@odata.type"] = 2207 "#LogEntryCollection.LogEntryCollection"; 2208 asyncResp->res.jsonValue["@odata.id"] = 2209 "/redfish/v1/Systems/system/LogServices/Dump/Entries"; 2210 asyncResp->res.jsonValue["Name"] = "System Dump Entries"; 2211 asyncResp->res.jsonValue["Description"] = 2212 "Collection of System Dump Entries"; 2213 2214 getDumpEntryCollection(asyncResp, "System"); 2215 } 2216 }; 2217 2218 class SystemDumpEntry : public Node 2219 { 2220 public: 2221 SystemDumpEntry(CrowApp& app) : 2222 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/", 2223 std::string()) 2224 { 2225 entityPrivileges = { 2226 {boost::beast::http::verb::get, {{"Login"}}}, 2227 {boost::beast::http::verb::head, {{"Login"}}}, 2228 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2229 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2230 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2231 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2232 } 2233 2234 private: 2235 void doGet(crow::Response& res, const crow::Request& req, 2236 const std::vector<std::string>& params) override 2237 { 2238 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2239 if (params.size() != 1) 2240 { 2241 messages::internalError(asyncResp->res); 2242 return; 2243 } 2244 getDumpEntryById(asyncResp, params[0], "System"); 2245 } 2246 2247 void doDelete(crow::Response& res, const crow::Request& req, 2248 const std::vector<std::string>& params) override 2249 { 2250 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2251 if (params.size() != 1) 2252 { 2253 messages::internalError(asyncResp->res); 2254 return; 2255 } 2256 deleteDumpEntry(asyncResp->res, params[0]); 2257 } 2258 }; 2259 2260 class SystemDumpCreate : public Node 2261 { 2262 public: 2263 SystemDumpCreate(CrowApp& app) : 2264 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/" 2265 "Actions/Oem/" 2266 "OemLogService.CollectDiagnosticData/") 2267 { 2268 entityPrivileges = { 2269 {boost::beast::http::verb::get, {{"Login"}}}, 2270 {boost::beast::http::verb::head, {{"Login"}}}, 2271 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2272 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2273 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2274 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2275 } 2276 2277 private: 2278 void doPost(crow::Response& res, const crow::Request& req, 2279 const std::vector<std::string>& params) override 2280 { 2281 createDump(res, req, "System"); 2282 } 2283 }; 2284 2285 class SystemDumpEntryDownload : public Node 2286 { 2287 public: 2288 SystemDumpEntryDownload(CrowApp& app) : 2289 Node(app, 2290 "/redfish/v1/Systems/system/LogServices/System/Entries/<str>/" 2291 "Actions/" 2292 "LogEntry.DownloadLog/", 2293 std::string()) 2294 { 2295 entityPrivileges = { 2296 {boost::beast::http::verb::get, {{"Login"}}}, 2297 {boost::beast::http::verb::head, {{"Login"}}}, 2298 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2299 } 2300 2301 private: 2302 void doPost(crow::Response& res, const crow::Request& req, 2303 const std::vector<std::string>& params) override 2304 { 2305 if (params.size() != 1) 2306 { 2307 messages::internalError(res); 2308 return; 2309 } 2310 const std::string& entryID = params[0]; 2311 crow::obmc_dump::handleDumpOffloadUrl(req, res, entryID); 2312 } 2313 }; 2314 2315 class SystemDumpClear : public Node 2316 { 2317 public: 2318 SystemDumpClear(CrowApp& app) : 2319 Node(app, "/redfish/v1/Systems/system/LogServices/System/" 2320 "Actions/" 2321 "LogService.ClearLog/") 2322 { 2323 entityPrivileges = { 2324 {boost::beast::http::verb::get, {{"Login"}}}, 2325 {boost::beast::http::verb::head, {{"Login"}}}, 2326 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2327 } 2328 2329 private: 2330 void doPost(crow::Response& res, const crow::Request& req, 2331 const std::vector<std::string>& params) override 2332 { 2333 2334 auto asyncResp = std::make_shared<AsyncResp>(res); 2335 crow::connections::systemBus->async_method_call( 2336 [asyncResp](const boost::system::error_code ec, 2337 const std::vector<std::string>& dumpList) { 2338 if (ec) 2339 { 2340 messages::internalError(asyncResp->res); 2341 return; 2342 } 2343 2344 for (const std::string& objectPath : dumpList) 2345 { 2346 std::size_t pos = objectPath.rfind("/"); 2347 if (pos != std::string::npos) 2348 { 2349 std::string logID = objectPath.substr(pos + 1); 2350 deleteDumpEntry(asyncResp->res, logID); 2351 } 2352 } 2353 }, 2354 "xyz.openbmc_project.ObjectMapper", 2355 "/xyz/openbmc_project/object_mapper", 2356 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 2357 "/xyz/openbmc_project/dump", 0, 2358 std::array<const char*, 1>{ 2359 "xyz.openbmc_project.Dump.Entry.System"}); 2360 } 2361 }; 2362 2363 class CrashdumpService : public Node 2364 { 2365 public: 2366 template <typename CrowApp> 2367 CrashdumpService(CrowApp& app) : 2368 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 2369 { 2370 // Note: Deviated from redfish privilege registry for GET & HEAD 2371 // method for security reasons. 2372 entityPrivileges = { 2373 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2374 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2375 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2376 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2377 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2378 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2379 } 2380 2381 private: 2382 /** 2383 * Functions triggers appropriate requests on DBus 2384 */ 2385 void doGet(crow::Response& res, const crow::Request& req, 2386 const std::vector<std::string>& params) override 2387 { 2388 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2389 // Copy over the static data to include the entries added by SubRoute 2390 asyncResp->res.jsonValue["@odata.id"] = 2391 "/redfish/v1/Systems/system/LogServices/Crashdump"; 2392 asyncResp->res.jsonValue["@odata.type"] = 2393 "#LogService.v1_1_0.LogService"; 2394 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 2395 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 2396 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 2397 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2398 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 2399 asyncResp->res.jsonValue["Entries"] = { 2400 {"@odata.id", 2401 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 2402 asyncResp->res.jsonValue["Actions"] = { 2403 {"#LogService.ClearLog", 2404 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2405 "Actions/LogService.ClearLog"}}}, 2406 {"Oem", 2407 {{"#Crashdump.OnDemand", 2408 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2409 "Actions/Oem/Crashdump.OnDemand"}}}, 2410 {"#Crashdump.Telemetry", 2411 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2412 "Actions/Oem/Crashdump.Telemetry"}}}}}}; 2413 2414 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 2415 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 2416 {"#Crashdump.SendRawPeci", 2417 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2418 "Actions/Oem/Crashdump.SendRawPeci"}}}); 2419 #endif 2420 } 2421 }; 2422 2423 class CrashdumpClear : public Node 2424 { 2425 public: 2426 CrashdumpClear(CrowApp& app) : 2427 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 2428 "LogService.ClearLog/") 2429 { 2430 // Note: Deviated from redfish privilege registry for GET & HEAD 2431 // method for security reasons. 2432 entityPrivileges = { 2433 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2434 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2435 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2436 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2437 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2438 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2439 } 2440 2441 private: 2442 void doPost(crow::Response& res, const crow::Request& req, 2443 const std::vector<std::string>& params) override 2444 { 2445 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2446 2447 crow::connections::systemBus->async_method_call( 2448 [asyncResp](const boost::system::error_code ec, 2449 const std::string& resp) { 2450 if (ec) 2451 { 2452 messages::internalError(asyncResp->res); 2453 return; 2454 } 2455 messages::success(asyncResp->res); 2456 }, 2457 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 2458 } 2459 }; 2460 2461 static void logCrashdumpEntry(std::shared_ptr<AsyncResp> asyncResp, 2462 const std::string& logID, 2463 nlohmann::json& logEntryJson) 2464 { 2465 auto getStoredLogCallback = 2466 [asyncResp, logID, &logEntryJson]( 2467 const boost::system::error_code ec, 2468 const std::vector<std::pair<std::string, VariantType>>& params) { 2469 if (ec) 2470 { 2471 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 2472 if (ec.value() == 2473 boost::system::linux_error::bad_request_descriptor) 2474 { 2475 messages::resourceNotFound(asyncResp->res, "LogEntry", 2476 logID); 2477 } 2478 else 2479 { 2480 messages::internalError(asyncResp->res); 2481 } 2482 return; 2483 } 2484 2485 std::string timestamp{}; 2486 std::string filename{}; 2487 std::string logfile{}; 2488 ParseCrashdumpParameters(params, filename, timestamp, logfile); 2489 2490 if (filename.empty() || timestamp.empty()) 2491 { 2492 messages::resourceMissingAtURI(asyncResp->res, logID); 2493 return; 2494 } 2495 2496 std::string crashdumpURI = 2497 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 2498 logID + "/" + filename; 2499 logEntryJson = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2500 {"@odata.id", "/redfish/v1/Systems/system/" 2501 "LogServices/Crashdump/Entries/" + 2502 logID}, 2503 {"Name", "CPU Crashdump"}, 2504 {"Id", logID}, 2505 {"EntryType", "Oem"}, 2506 {"OemRecordFormat", "Crashdump URI"}, 2507 {"Message", std::move(crashdumpURI)}, 2508 {"Created", std::move(timestamp)}}; 2509 }; 2510 crow::connections::systemBus->async_method_call( 2511 std::move(getStoredLogCallback), crashdumpObject, 2512 crashdumpPath + std::string("/") + logID, 2513 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2514 } 2515 2516 class CrashdumpEntryCollection : public Node 2517 { 2518 public: 2519 template <typename CrowApp> 2520 CrashdumpEntryCollection(CrowApp& app) : 2521 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 2522 { 2523 // Note: Deviated from redfish privilege registry for GET & HEAD 2524 // method for security reasons. 2525 entityPrivileges = { 2526 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2527 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2528 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2529 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2530 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2531 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2532 } 2533 2534 private: 2535 /** 2536 * Functions triggers appropriate requests on DBus 2537 */ 2538 void doGet(crow::Response& res, const crow::Request& req, 2539 const std::vector<std::string>& params) override 2540 { 2541 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2542 // Collections don't include the static data added by SubRoute because 2543 // it has a duplicate entry for members 2544 auto getLogEntriesCallback = [asyncResp]( 2545 const boost::system::error_code ec, 2546 const std::vector<std::string>& resp) { 2547 if (ec) 2548 { 2549 if (ec.value() != 2550 boost::system::errc::no_such_file_or_directory) 2551 { 2552 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 2553 << ec.message(); 2554 messages::internalError(asyncResp->res); 2555 return; 2556 } 2557 } 2558 asyncResp->res.jsonValue["@odata.type"] = 2559 "#LogEntryCollection.LogEntryCollection"; 2560 asyncResp->res.jsonValue["@odata.id"] = 2561 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 2562 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 2563 asyncResp->res.jsonValue["Description"] = 2564 "Collection of Crashdump Entries"; 2565 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2566 logEntryArray = nlohmann::json::array(); 2567 std::vector<std::string> logIDs; 2568 // Get the list of log entries and build up an empty array big 2569 // enough to hold them 2570 for (const std::string& objpath : resp) 2571 { 2572 // Get the log ID 2573 std::size_t lastPos = objpath.rfind("/"); 2574 if (lastPos == std::string::npos) 2575 { 2576 continue; 2577 } 2578 logIDs.emplace_back(objpath.substr(lastPos + 1)); 2579 2580 // Add a space for the log entry to the array 2581 logEntryArray.push_back({}); 2582 } 2583 // Now go through and set up async calls to fill in the entries 2584 size_t index = 0; 2585 for (const std::string& logID : logIDs) 2586 { 2587 // Add the log entry to the array 2588 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 2589 } 2590 asyncResp->res.jsonValue["Members@odata.count"] = 2591 logEntryArray.size(); 2592 }; 2593 crow::connections::systemBus->async_method_call( 2594 std::move(getLogEntriesCallback), 2595 "xyz.openbmc_project.ObjectMapper", 2596 "/xyz/openbmc_project/object_mapper", 2597 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 2598 std::array<const char*, 1>{crashdumpInterface}); 2599 } 2600 }; 2601 2602 class CrashdumpEntry : public Node 2603 { 2604 public: 2605 CrashdumpEntry(CrowApp& app) : 2606 Node(app, 2607 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 2608 std::string()) 2609 { 2610 // Note: Deviated from redfish privilege registry for GET & HEAD 2611 // method for security reasons. 2612 entityPrivileges = { 2613 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2614 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2615 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2616 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2617 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2618 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2619 } 2620 2621 private: 2622 void doGet(crow::Response& res, const crow::Request& req, 2623 const std::vector<std::string>& params) override 2624 { 2625 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2626 if (params.size() != 1) 2627 { 2628 messages::internalError(asyncResp->res); 2629 return; 2630 } 2631 const std::string& logID = params[0]; 2632 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 2633 } 2634 }; 2635 2636 class CrashdumpFile : public Node 2637 { 2638 public: 2639 CrashdumpFile(CrowApp& app) : 2640 Node(app, 2641 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/" 2642 "<str>/", 2643 std::string(), std::string()) 2644 { 2645 // Note: Deviated from redfish privilege registry for GET & HEAD 2646 // method for security reasons. 2647 entityPrivileges = { 2648 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2649 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2650 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2651 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2652 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2653 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2654 } 2655 2656 private: 2657 void doGet(crow::Response& res, const crow::Request& req, 2658 const std::vector<std::string>& params) override 2659 { 2660 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2661 if (params.size() != 2) 2662 { 2663 messages::internalError(asyncResp->res); 2664 return; 2665 } 2666 const std::string& logID = params[0]; 2667 const std::string& fileName = params[1]; 2668 2669 auto getStoredLogCallback = 2670 [asyncResp, logID, fileName]( 2671 const boost::system::error_code ec, 2672 const std::vector<std::pair<std::string, VariantType>>& resp) { 2673 if (ec) 2674 { 2675 BMCWEB_LOG_DEBUG << "failed to get log ec: " 2676 << ec.message(); 2677 messages::internalError(asyncResp->res); 2678 return; 2679 } 2680 2681 std::string dbusFilename{}; 2682 std::string dbusTimestamp{}; 2683 std::string dbusFilepath{}; 2684 2685 ParseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 2686 dbusFilepath); 2687 2688 if (dbusFilename.empty() || dbusTimestamp.empty() || 2689 dbusFilepath.empty()) 2690 { 2691 messages::resourceMissingAtURI(asyncResp->res, fileName); 2692 return; 2693 } 2694 2695 // Verify the file name parameter is correct 2696 if (fileName != dbusFilename) 2697 { 2698 messages::resourceMissingAtURI(asyncResp->res, fileName); 2699 return; 2700 } 2701 2702 if (!std::filesystem::exists(dbusFilepath)) 2703 { 2704 messages::resourceMissingAtURI(asyncResp->res, fileName); 2705 return; 2706 } 2707 std::ifstream ifs(dbusFilepath, std::ios::in | 2708 std::ios::binary | 2709 std::ios::ate); 2710 std::ifstream::pos_type fileSize = ifs.tellg(); 2711 if (fileSize < 0) 2712 { 2713 messages::generalError(asyncResp->res); 2714 return; 2715 } 2716 ifs.seekg(0, std::ios::beg); 2717 2718 auto crashData = std::make_unique<char[]>( 2719 static_cast<unsigned int>(fileSize)); 2720 2721 ifs.read(crashData.get(), static_cast<int>(fileSize)); 2722 2723 // The cast to std::string is intentional in order to use the 2724 // assign() that applies move mechanics 2725 asyncResp->res.body().assign( 2726 static_cast<std::string>(crashData.get())); 2727 2728 // Configure this to be a file download when accessed from 2729 // a browser 2730 asyncResp->res.addHeader("Content-Disposition", "attachment"); 2731 }; 2732 crow::connections::systemBus->async_method_call( 2733 std::move(getStoredLogCallback), crashdumpObject, 2734 crashdumpPath + std::string("/") + logID, 2735 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2736 } 2737 }; 2738 2739 class OnDemandCrashdump : public Node 2740 { 2741 public: 2742 OnDemandCrashdump(CrowApp& app) : 2743 Node(app, 2744 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2745 "Crashdump.OnDemand/") 2746 { 2747 // Note: Deviated from redfish privilege registry for GET & HEAD 2748 // method for security reasons. 2749 entityPrivileges = { 2750 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2751 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2752 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2753 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2754 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2755 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2756 } 2757 2758 private: 2759 void doPost(crow::Response& res, const crow::Request& req, 2760 const std::vector<std::string>& params) override 2761 { 2762 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2763 2764 auto generateonDemandLogCallback = [asyncResp, 2765 req](const boost::system::error_code 2766 ec, 2767 const std::string& resp) { 2768 if (ec) 2769 { 2770 if (ec.value() == boost::system::errc::operation_not_supported) 2771 { 2772 messages::resourceInStandby(asyncResp->res); 2773 } 2774 else if (ec.value() == 2775 boost::system::errc::device_or_resource_busy) 2776 { 2777 messages::serviceTemporarilyUnavailable(asyncResp->res, 2778 "60"); 2779 } 2780 else 2781 { 2782 messages::internalError(asyncResp->res); 2783 } 2784 return; 2785 } 2786 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2787 [](boost::system::error_code err, sdbusplus::message::message&, 2788 const std::shared_ptr<task::TaskData>& taskData) { 2789 if (!err) 2790 { 2791 taskData->messages.emplace_back( 2792 messages::taskCompletedOK( 2793 std::to_string(taskData->index))); 2794 taskData->state = "Completed"; 2795 } 2796 return task::completed; 2797 }, 2798 "type='signal',interface='org.freedesktop.DBus.Properties'," 2799 "member='PropertiesChanged',arg0namespace='com.intel." 2800 "crashdump'"); 2801 task->startTimer(std::chrono::minutes(5)); 2802 task->populateResp(asyncResp->res); 2803 task->payload.emplace(req); 2804 }; 2805 crow::connections::systemBus->async_method_call( 2806 std::move(generateonDemandLogCallback), crashdumpObject, 2807 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog"); 2808 } 2809 }; 2810 2811 class TelemetryCrashdump : public Node 2812 { 2813 public: 2814 TelemetryCrashdump(CrowApp& app) : 2815 Node(app, 2816 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2817 "Crashdump.Telemetry/") 2818 { 2819 // Note: Deviated from redfish privilege registry for GET & HEAD 2820 // method for security reasons. 2821 entityPrivileges = { 2822 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2823 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2824 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2825 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2826 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2827 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2828 } 2829 2830 private: 2831 void doPost(crow::Response& res, const crow::Request& req, 2832 const std::vector<std::string>& params) override 2833 { 2834 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2835 2836 auto generateTelemetryLogCallback = [asyncResp, req]( 2837 const boost::system::error_code 2838 ec, 2839 const std::string& resp) { 2840 if (ec) 2841 { 2842 if (ec.value() == boost::system::errc::operation_not_supported) 2843 { 2844 messages::resourceInStandby(asyncResp->res); 2845 } 2846 else if (ec.value() == 2847 boost::system::errc::device_or_resource_busy) 2848 { 2849 messages::serviceTemporarilyUnavailable(asyncResp->res, 2850 "60"); 2851 } 2852 else 2853 { 2854 messages::internalError(asyncResp->res); 2855 } 2856 return; 2857 } 2858 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2859 [](boost::system::error_code err, sdbusplus::message::message&, 2860 const std::shared_ptr<task::TaskData>& taskData) { 2861 if (!err) 2862 { 2863 taskData->messages.emplace_back( 2864 messages::taskCompletedOK( 2865 std::to_string(taskData->index))); 2866 taskData->state = "Completed"; 2867 } 2868 return task::completed; 2869 }, 2870 "type='signal',interface='org.freedesktop.DBus.Properties'," 2871 "member='PropertiesChanged',arg0namespace='com.intel." 2872 "crashdump'"); 2873 task->startTimer(std::chrono::minutes(5)); 2874 task->populateResp(asyncResp->res); 2875 task->payload.emplace(req); 2876 }; 2877 crow::connections::systemBus->async_method_call( 2878 std::move(generateTelemetryLogCallback), crashdumpObject, 2879 crashdumpPath, crashdumpTelemetryInterface, "GenerateTelemetryLog"); 2880 } 2881 }; 2882 2883 class SendRawPECI : public Node 2884 { 2885 public: 2886 SendRawPECI(CrowApp& app) : 2887 Node(app, 2888 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2889 "Crashdump.SendRawPeci/") 2890 { 2891 // Note: Deviated from redfish privilege registry for GET & HEAD 2892 // method for security reasons. 2893 entityPrivileges = { 2894 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2895 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2896 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2897 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2898 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2899 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2900 } 2901 2902 private: 2903 void doPost(crow::Response& res, const crow::Request& req, 2904 const std::vector<std::string>& params) override 2905 { 2906 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2907 std::vector<std::vector<uint8_t>> peciCommands; 2908 2909 nlohmann::json reqJson = 2910 nlohmann::json::parse(req.body, nullptr, false); 2911 if (reqJson.find("PECICommands") != reqJson.end()) 2912 { 2913 if (!json_util::readJson(req, res, "PECICommands", peciCommands)) 2914 { 2915 return; 2916 } 2917 uint32_t idx = 0; 2918 for (auto const& cmd : peciCommands) 2919 { 2920 if (cmd.size() < 3) 2921 { 2922 std::string s("["); 2923 for (auto const& val : cmd) 2924 { 2925 if (val != *cmd.begin()) 2926 { 2927 s += ","; 2928 } 2929 s += std::to_string(val); 2930 } 2931 s += "]"; 2932 messages::actionParameterValueFormatError( 2933 res, s, "PECICommands[" + std::to_string(idx) + "]", 2934 "SendRawPeci"); 2935 return; 2936 } 2937 idx++; 2938 } 2939 } 2940 else 2941 { 2942 /* This interface is deprecated */ 2943 uint8_t clientAddress = 0; 2944 uint8_t readLength = 0; 2945 std::vector<uint8_t> peciCommand; 2946 if (!json_util::readJson(req, res, "ClientAddress", clientAddress, 2947 "ReadLength", readLength, "PECICommand", 2948 peciCommand)) 2949 { 2950 return; 2951 } 2952 peciCommands.push_back({clientAddress, 0, readLength}); 2953 peciCommands[0].insert(peciCommands[0].end(), peciCommand.begin(), 2954 peciCommand.end()); 2955 } 2956 // Callback to return the Raw PECI response 2957 auto sendRawPECICallback = 2958 [asyncResp](const boost::system::error_code ec, 2959 const std::vector<std::vector<uint8_t>>& resp) { 2960 if (ec) 2961 { 2962 BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: " 2963 << ec.message(); 2964 messages::internalError(asyncResp->res); 2965 return; 2966 } 2967 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 2968 {"PECIResponse", resp}}; 2969 }; 2970 // Call the SendRawPECI command with the provided data 2971 crow::connections::systemBus->async_method_call( 2972 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath, 2973 crashdumpRawPECIInterface, "SendRawPeci", peciCommands); 2974 } 2975 }; 2976 2977 /** 2978 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2979 */ 2980 class DBusLogServiceActionsClear : public Node 2981 { 2982 public: 2983 DBusLogServiceActionsClear(CrowApp& app) : 2984 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2985 "LogService.ClearLog/") 2986 { 2987 entityPrivileges = { 2988 {boost::beast::http::verb::get, {{"Login"}}}, 2989 {boost::beast::http::verb::head, {{"Login"}}}, 2990 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2991 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2992 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2993 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2994 } 2995 2996 private: 2997 /** 2998 * Function handles POST method request. 2999 * The Clear Log actions does not require any parameter.The action deletes 3000 * all entries found in the Entries collection for this Log Service. 3001 */ 3002 void doPost(crow::Response& res, const crow::Request& req, 3003 const std::vector<std::string>& params) override 3004 { 3005 BMCWEB_LOG_DEBUG << "Do delete all entries."; 3006 3007 auto asyncResp = std::make_shared<AsyncResp>(res); 3008 // Process response from Logging service. 3009 auto resp_handler = [asyncResp](const boost::system::error_code ec) { 3010 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 3011 if (ec) 3012 { 3013 // TODO Handle for specific error code 3014 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 3015 asyncResp->res.result( 3016 boost::beast::http::status::internal_server_error); 3017 return; 3018 } 3019 3020 asyncResp->res.result(boost::beast::http::status::no_content); 3021 }; 3022 3023 // Make call to Logging service to request Clear Log 3024 crow::connections::systemBus->async_method_call( 3025 resp_handler, "xyz.openbmc_project.Logging", 3026 "/xyz/openbmc_project/logging", 3027 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3028 } 3029 }; 3030 3031 /**************************************************** 3032 * Redfish PostCode interfaces 3033 * using DBUS interface: getPostCodesTS 3034 ******************************************************/ 3035 class PostCodesLogService : public Node 3036 { 3037 public: 3038 PostCodesLogService(CrowApp& app) : 3039 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 3040 { 3041 entityPrivileges = { 3042 {boost::beast::http::verb::get, {{"Login"}}}, 3043 {boost::beast::http::verb::head, {{"Login"}}}, 3044 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 3045 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 3046 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 3047 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 3048 } 3049 3050 private: 3051 void doGet(crow::Response& res, const crow::Request& req, 3052 const std::vector<std::string>& params) override 3053 { 3054 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3055 3056 asyncResp->res.jsonValue = { 3057 {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"}, 3058 {"@odata.type", "#LogService.v1_1_0.LogService"}, 3059 {"@odata.context", "/redfish/v1/$metadata#LogService.LogService"}, 3060 {"Name", "POST Code Log Service"}, 3061 {"Description", "POST Code Log Service"}, 3062 {"Id", "BIOS POST Code Log"}, 3063 {"OverWritePolicy", "WrapsWhenFull"}, 3064 {"Entries", 3065 {{"@odata.id", 3066 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 3067 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 3068 {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/" 3069 "Actions/LogService.ClearLog"}}; 3070 } 3071 }; 3072 3073 class PostCodesClear : public Node 3074 { 3075 public: 3076 PostCodesClear(CrowApp& app) : 3077 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/" 3078 "LogService.ClearLog/") 3079 { 3080 entityPrivileges = { 3081 {boost::beast::http::verb::get, {{"Login"}}}, 3082 {boost::beast::http::verb::head, {{"Login"}}}, 3083 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 3084 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 3085 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 3086 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 3087 } 3088 3089 private: 3090 void doPost(crow::Response& res, const crow::Request& req, 3091 const std::vector<std::string>& params) override 3092 { 3093 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 3094 3095 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3096 // Make call to post-code service to request clear all 3097 crow::connections::systemBus->async_method_call( 3098 [asyncResp](const boost::system::error_code ec) { 3099 if (ec) 3100 { 3101 // TODO Handle for specific error code 3102 BMCWEB_LOG_ERROR 3103 << "doClearPostCodes resp_handler got error " << ec; 3104 asyncResp->res.result( 3105 boost::beast::http::status::internal_server_error); 3106 messages::internalError(asyncResp->res); 3107 return; 3108 } 3109 }, 3110 "xyz.openbmc_project.State.Boot.PostCode", 3111 "/xyz/openbmc_project/State/Boot/PostCode", 3112 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3113 } 3114 }; 3115 3116 static void fillPostCodeEntry( 3117 std::shared_ptr<AsyncResp> aResp, 3118 const boost::container::flat_map<uint64_t, uint64_t>& postcode, 3119 const uint16_t bootIndex, const uint64_t codeIndex = 0, 3120 const uint64_t skip = 0, const uint64_t top = 0) 3121 { 3122 // Get the Message from the MessageRegistry 3123 const message_registries::Message* message = 3124 message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode"); 3125 3126 uint64_t currentCodeIndex = 0; 3127 nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"]; 3128 3129 uint64_t firstCodeTimeUs = 0; 3130 for (const std::pair<uint64_t, uint64_t>& code : postcode) 3131 { 3132 currentCodeIndex++; 3133 std::string postcodeEntryID = 3134 "B" + std::to_string(bootIndex) + "-" + 3135 std::to_string(currentCodeIndex); // 1 based index in EntryID string 3136 3137 uint64_t usecSinceEpoch = code.first; 3138 uint64_t usTimeOffset = 0; 3139 3140 if (1 == currentCodeIndex) 3141 { // already incremented 3142 firstCodeTimeUs = code.first; 3143 } 3144 else 3145 { 3146 usTimeOffset = code.first - firstCodeTimeUs; 3147 } 3148 3149 // skip if no specific codeIndex is specified and currentCodeIndex does 3150 // not fall between top and skip 3151 if ((codeIndex == 0) && 3152 (currentCodeIndex <= skip || currentCodeIndex > top)) 3153 { 3154 continue; 3155 } 3156 3157 // skip if a specific codeIndex is specified and does not match the 3158 // currentIndex 3159 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 3160 { 3161 // This is done for simplicity. 1st entry is needed to calculate 3162 // time offset. To improve efficiency, one can get to the entry 3163 // directly (possibly with flatmap's nth method) 3164 continue; 3165 } 3166 3167 // currentCodeIndex is within top and skip or equal to specified code 3168 // index 3169 3170 // Get the Created time from the timestamp 3171 std::string entryTimeStr; 3172 if (!getTimestampStr(usecSinceEpoch, entryTimeStr)) 3173 { 3174 continue; 3175 } 3176 3177 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 3178 std::ostringstream hexCode; 3179 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 3180 << code.second; 3181 std::ostringstream timeOffsetStr; 3182 // Set Fixed -Point Notation 3183 timeOffsetStr << std::fixed; 3184 // Set precision to 4 digits 3185 timeOffsetStr << std::setprecision(4); 3186 // Add double to stream 3187 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 3188 std::vector<std::string> messageArgs = { 3189 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 3190 3191 // Get MessageArgs template from message registry 3192 std::string msg; 3193 if (message != nullptr) 3194 { 3195 msg = message->message; 3196 3197 // fill in this post code value 3198 int i = 0; 3199 for (const std::string& messageArg : messageArgs) 3200 { 3201 std::string argStr = "%" + std::to_string(++i); 3202 size_t argPos = msg.find(argStr); 3203 if (argPos != std::string::npos) 3204 { 3205 msg.replace(argPos, argStr.length(), messageArg); 3206 } 3207 } 3208 } 3209 3210 // Get Severity template from message registry 3211 std::string severity; 3212 if (message != nullptr) 3213 { 3214 severity = message->severity; 3215 } 3216 3217 // add to AsyncResp 3218 logEntryArray.push_back({}); 3219 nlohmann::json& bmcLogEntry = logEntryArray.back(); 3220 bmcLogEntry = { 3221 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 3222 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 3223 {"@odata.id", "/redfish/v1/Systems/system/LogServices/" 3224 "PostCodes/Entries/" + 3225 postcodeEntryID}, 3226 {"Name", "POST Code Log Entry"}, 3227 {"Id", postcodeEntryID}, 3228 {"Message", std::move(msg)}, 3229 {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"}, 3230 {"MessageArgs", std::move(messageArgs)}, 3231 {"EntryType", "Event"}, 3232 {"Severity", std::move(severity)}, 3233 {"Created", std::move(entryTimeStr)}}; 3234 } 3235 } 3236 3237 static void getPostCodeForEntry(std::shared_ptr<AsyncResp> aResp, 3238 const uint16_t bootIndex, 3239 const uint64_t codeIndex) 3240 { 3241 crow::connections::systemBus->async_method_call( 3242 [aResp, bootIndex, codeIndex]( 3243 const boost::system::error_code ec, 3244 const boost::container::flat_map<uint64_t, uint64_t>& postcode) { 3245 if (ec) 3246 { 3247 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3248 messages::internalError(aResp->res); 3249 return; 3250 } 3251 3252 // skip the empty postcode boots 3253 if (postcode.empty()) 3254 { 3255 return; 3256 } 3257 3258 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 3259 3260 aResp->res.jsonValue["Members@odata.count"] = 3261 aResp->res.jsonValue["Members"].size(); 3262 }, 3263 "xyz.openbmc_project.State.Boot.PostCode", 3264 "/xyz/openbmc_project/State/Boot/PostCode", 3265 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3266 bootIndex); 3267 } 3268 3269 static void getPostCodeForBoot(std::shared_ptr<AsyncResp> aResp, 3270 const uint16_t bootIndex, 3271 const uint16_t bootCount, 3272 const uint64_t entryCount, const uint64_t skip, 3273 const uint64_t top) 3274 { 3275 crow::connections::systemBus->async_method_call( 3276 [aResp, bootIndex, bootCount, entryCount, skip, 3277 top](const boost::system::error_code ec, 3278 const boost::container::flat_map<uint64_t, uint64_t>& postcode) { 3279 if (ec) 3280 { 3281 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3282 messages::internalError(aResp->res); 3283 return; 3284 } 3285 3286 uint64_t endCount = entryCount; 3287 if (!postcode.empty()) 3288 { 3289 endCount = entryCount + postcode.size(); 3290 3291 if ((skip < endCount) && ((top + skip) > entryCount)) 3292 { 3293 uint64_t thisBootSkip = 3294 std::max(skip, entryCount) - entryCount; 3295 uint64_t thisBootTop = 3296 std::min(top + skip, endCount) - entryCount; 3297 3298 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 3299 thisBootSkip, thisBootTop); 3300 } 3301 aResp->res.jsonValue["Members@odata.count"] = endCount; 3302 } 3303 3304 // continue to previous bootIndex 3305 if (bootIndex < bootCount) 3306 { 3307 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 3308 bootCount, endCount, skip, top); 3309 } 3310 else 3311 { 3312 aResp->res.jsonValue["Members@odata.nextLink"] = 3313 "/redfish/v1/Systems/system/LogServices/PostCodes/" 3314 "Entries?$skip=" + 3315 std::to_string(skip + top); 3316 } 3317 }, 3318 "xyz.openbmc_project.State.Boot.PostCode", 3319 "/xyz/openbmc_project/State/Boot/PostCode", 3320 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3321 bootIndex); 3322 } 3323 3324 static void getCurrentBootNumber(std::shared_ptr<AsyncResp> aResp, 3325 const uint64_t skip, const uint64_t top) 3326 { 3327 uint64_t entryCount = 0; 3328 crow::connections::systemBus->async_method_call( 3329 [aResp, entryCount, skip, 3330 top](const boost::system::error_code ec, 3331 const std::variant<uint16_t>& bootCount) { 3332 if (ec) 3333 { 3334 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3335 messages::internalError(aResp->res); 3336 return; 3337 } 3338 auto pVal = std::get_if<uint16_t>(&bootCount); 3339 if (pVal) 3340 { 3341 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top); 3342 } 3343 else 3344 { 3345 BMCWEB_LOG_DEBUG << "Post code boot index failed."; 3346 } 3347 }, 3348 "xyz.openbmc_project.State.Boot.PostCode", 3349 "/xyz/openbmc_project/State/Boot/PostCode", 3350 "org.freedesktop.DBus.Properties", "Get", 3351 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount"); 3352 } 3353 3354 class PostCodesEntryCollection : public Node 3355 { 3356 public: 3357 template <typename CrowApp> 3358 PostCodesEntryCollection(CrowApp& app) : 3359 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 3360 { 3361 entityPrivileges = { 3362 {boost::beast::http::verb::get, {{"Login"}}}, 3363 {boost::beast::http::verb::head, {{"Login"}}}, 3364 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 3365 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 3366 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 3367 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 3368 } 3369 3370 private: 3371 void doGet(crow::Response& res, const crow::Request& req, 3372 const std::vector<std::string>& params) override 3373 { 3374 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3375 3376 asyncResp->res.jsonValue["@odata.type"] = 3377 "#LogEntryCollection.LogEntryCollection"; 3378 asyncResp->res.jsonValue["@odata.context"] = 3379 "/redfish/v1/" 3380 "$metadata#LogEntryCollection.LogEntryCollection"; 3381 asyncResp->res.jsonValue["@odata.id"] = 3382 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3383 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3384 asyncResp->res.jsonValue["Description"] = 3385 "Collection of POST Code Log Entries"; 3386 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3387 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3388 3389 uint64_t skip = 0; 3390 uint64_t top = maxEntriesPerPage; // Show max entries by default 3391 if (!getSkipParam(asyncResp->res, req, skip)) 3392 { 3393 return; 3394 } 3395 if (!getTopParam(asyncResp->res, req, top)) 3396 { 3397 return; 3398 } 3399 getCurrentBootNumber(asyncResp, skip, top); 3400 } 3401 }; 3402 3403 class PostCodesEntry : public Node 3404 { 3405 public: 3406 PostCodesEntry(CrowApp& app) : 3407 Node(app, 3408 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/", 3409 std::string()) 3410 { 3411 entityPrivileges = { 3412 {boost::beast::http::verb::get, {{"Login"}}}, 3413 {boost::beast::http::verb::head, {{"Login"}}}, 3414 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 3415 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 3416 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 3417 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 3418 } 3419 3420 private: 3421 void doGet(crow::Response& res, const crow::Request& req, 3422 const std::vector<std::string>& params) override 3423 { 3424 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3425 if (params.size() != 1) 3426 { 3427 messages::internalError(asyncResp->res); 3428 return; 3429 } 3430 3431 const std::string& targetID = params[0]; 3432 3433 size_t bootPos = targetID.find('B'); 3434 if (bootPos == std::string::npos) 3435 { 3436 // Requested ID was not found 3437 messages::resourceMissingAtURI(asyncResp->res, targetID); 3438 return; 3439 } 3440 std::string_view bootIndexStr(targetID); 3441 bootIndexStr.remove_prefix(bootPos + 1); 3442 uint16_t bootIndex = 0; 3443 uint64_t codeIndex = 0; 3444 size_t dashPos = bootIndexStr.find('-'); 3445 3446 if (dashPos == std::string::npos) 3447 { 3448 return; 3449 } 3450 std::string_view codeIndexStr(bootIndexStr); 3451 bootIndexStr.remove_suffix(dashPos); 3452 codeIndexStr.remove_prefix(dashPos + 1); 3453 3454 bootIndex = static_cast<uint16_t>( 3455 strtoul(std::string(bootIndexStr).c_str(), NULL, 0)); 3456 codeIndex = strtoul(std::string(codeIndexStr).c_str(), NULL, 0); 3457 if (bootIndex == 0 || codeIndex == 0) 3458 { 3459 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 3460 << params[0]; 3461 } 3462 3463 asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry"; 3464 asyncResp->res.jsonValue["@odata.context"] = 3465 "/redfish/v1/$metadata#LogEntry.LogEntry"; 3466 asyncResp->res.jsonValue["@odata.id"] = 3467 "/redfish/v1/Systems/system/LogServices/PostCodes/" 3468 "Entries"; 3469 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3470 asyncResp->res.jsonValue["Description"] = 3471 "Collection of POST Code Log Entries"; 3472 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3473 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3474 3475 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 3476 } 3477 }; 3478 3479 } // namespace redfish 3480