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