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