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