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