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