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