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