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