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