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