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 if (id == nullptr) 1416 { 1417 messages::internalError(asyncResp->res); 1418 } 1419 } 1420 else if (propertyMap.first == "Timestamp") 1421 { 1422 const uint64_t* millisTimeStamp = 1423 std::get_if<uint64_t>(&propertyMap.second); 1424 if (millisTimeStamp == nullptr) 1425 { 1426 messages::internalError(asyncResp->res); 1427 } 1428 else 1429 { 1430 timestamp = crow::utility::getTimestamp( 1431 *millisTimeStamp); 1432 } 1433 } 1434 else if (propertyMap.first == "UpdateTimestamp") 1435 { 1436 const uint64_t* millisTimeStamp = 1437 std::get_if<uint64_t>(&propertyMap.second); 1438 if (millisTimeStamp == nullptr) 1439 { 1440 messages::internalError(asyncResp->res); 1441 } 1442 else 1443 { 1444 updateTimestamp = 1445 crow::utility::getTimestamp( 1446 *millisTimeStamp); 1447 } 1448 } 1449 else if (propertyMap.first == "Severity") 1450 { 1451 severity = std::get_if<std::string>( 1452 &propertyMap.second); 1453 if (severity == nullptr) 1454 { 1455 messages::internalError(asyncResp->res); 1456 } 1457 } 1458 else if (propertyMap.first == "Message") 1459 { 1460 message = std::get_if<std::string>( 1461 &propertyMap.second); 1462 if (message == nullptr) 1463 { 1464 messages::internalError(asyncResp->res); 1465 } 1466 } 1467 } 1468 thisEntry = { 1469 {"@odata.type", "#LogEntry.v1_6_0.LogEntry"}, 1470 {"@odata.id", 1471 "/redfish/v1/Systems/system/LogServices/EventLog/" 1472 "Entries/" + 1473 std::to_string(*id)}, 1474 {"Name", "System Event Log Entry"}, 1475 {"Id", std::to_string(*id)}, 1476 {"Message", *message}, 1477 {"EntryType", "Event"}, 1478 {"Severity", 1479 translateSeverityDbusToRedfish(*severity)}, 1480 {"Created", crow::utility::getDateTime(timestamp)}, 1481 {"Modified", 1482 crow::utility::getDateTime(updateTimestamp)}}; 1483 } 1484 } 1485 std::sort(entriesArray.begin(), entriesArray.end(), 1486 [](const nlohmann::json& left, 1487 const nlohmann::json& right) { 1488 return (left["Id"] <= right["Id"]); 1489 }); 1490 asyncResp->res.jsonValue["Members@odata.count"] = 1491 entriesArray.size(); 1492 }, 1493 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 1494 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1495 } 1496 }; 1497 1498 class DBusEventLogEntry : public Node 1499 { 1500 public: 1501 DBusEventLogEntry(App& app) : 1502 Node(app, 1503 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/", 1504 std::string()) 1505 { 1506 entityPrivileges = { 1507 {boost::beast::http::verb::get, {{"Login"}}}, 1508 {boost::beast::http::verb::head, {{"Login"}}}, 1509 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1510 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1511 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1512 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1513 } 1514 1515 private: 1516 void doGet(crow::Response& res, const crow::Request&, 1517 const std::vector<std::string>& params) override 1518 { 1519 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1520 if (params.size() != 1) 1521 { 1522 messages::internalError(asyncResp->res); 1523 return; 1524 } 1525 const std::string& entryID = params[0]; 1526 1527 // DBus implementation of EventLog/Entries 1528 // Make call to Logging Service to find all log entry objects 1529 crow::connections::systemBus->async_method_call( 1530 [asyncResp, entryID](const boost::system::error_code ec, 1531 GetManagedPropertyType& resp) { 1532 if (ec) 1533 { 1534 BMCWEB_LOG_ERROR 1535 << "EventLogEntry (DBus) resp_handler got error " << ec; 1536 messages::internalError(asyncResp->res); 1537 return; 1538 } 1539 uint32_t* id = nullptr; 1540 std::time_t timestamp{}; 1541 std::time_t updateTimestamp{}; 1542 std::string* severity = nullptr; 1543 std::string* message = nullptr; 1544 1545 for (auto& propertyMap : resp) 1546 { 1547 if (propertyMap.first == "Id") 1548 { 1549 id = std::get_if<uint32_t>(&propertyMap.second); 1550 if (id == nullptr) 1551 { 1552 messages::internalError(asyncResp->res); 1553 } 1554 } 1555 else if (propertyMap.first == "Timestamp") 1556 { 1557 const uint64_t* millisTimeStamp = 1558 std::get_if<uint64_t>(&propertyMap.second); 1559 if (millisTimeStamp == nullptr) 1560 { 1561 messages::internalError(asyncResp->res); 1562 } 1563 else 1564 { 1565 timestamp = 1566 crow::utility::getTimestamp(*millisTimeStamp); 1567 } 1568 } 1569 else if (propertyMap.first == "UpdateTimestamp") 1570 { 1571 const uint64_t* millisTimeStamp = 1572 std::get_if<uint64_t>(&propertyMap.second); 1573 if (millisTimeStamp == nullptr) 1574 { 1575 messages::internalError(asyncResp->res); 1576 } 1577 else 1578 { 1579 updateTimestamp = 1580 crow::utility::getTimestamp(*millisTimeStamp); 1581 } 1582 } 1583 else if (propertyMap.first == "Severity") 1584 { 1585 severity = 1586 std::get_if<std::string>(&propertyMap.second); 1587 if (severity == nullptr) 1588 { 1589 messages::internalError(asyncResp->res); 1590 } 1591 } 1592 else if (propertyMap.first == "Message") 1593 { 1594 message = std::get_if<std::string>(&propertyMap.second); 1595 if (message == nullptr) 1596 { 1597 messages::internalError(asyncResp->res); 1598 } 1599 } 1600 } 1601 if (id == nullptr || message == nullptr || severity == nullptr) 1602 { 1603 return; 1604 } 1605 asyncResp->res.jsonValue = { 1606 {"@odata.type", "#LogEntry.v1_6_0.LogEntry"}, 1607 {"@odata.id", 1608 "/redfish/v1/Systems/system/LogServices/EventLog/" 1609 "Entries/" + 1610 std::to_string(*id)}, 1611 {"Name", "System Event Log Entry"}, 1612 {"Id", std::to_string(*id)}, 1613 {"Message", *message}, 1614 {"EntryType", "Event"}, 1615 {"Severity", translateSeverityDbusToRedfish(*severity)}, 1616 {"Created", crow::utility::getDateTime(timestamp)}, 1617 {"Modified", crow::utility::getDateTime(updateTimestamp)}}; 1618 }, 1619 "xyz.openbmc_project.Logging", 1620 "/xyz/openbmc_project/logging/entry/" + entryID, 1621 "org.freedesktop.DBus.Properties", "GetAll", 1622 "xyz.openbmc_project.Logging.Entry"); 1623 } 1624 1625 void doDelete(crow::Response& res, const crow::Request&, 1626 const std::vector<std::string>& params) override 1627 { 1628 1629 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1630 1631 auto asyncResp = std::make_shared<AsyncResp>(res); 1632 1633 if (params.size() != 1) 1634 { 1635 messages::internalError(asyncResp->res); 1636 return; 1637 } 1638 std::string entryID = params[0]; 1639 1640 dbus::utility::escapePathForDbus(entryID); 1641 1642 // Process response from Logging service. 1643 auto respHandler = [asyncResp](const boost::system::error_code ec) { 1644 BMCWEB_LOG_DEBUG << "EventLogEntry (DBus) doDelete callback: Done"; 1645 if (ec) 1646 { 1647 // TODO Handle for specific error code 1648 BMCWEB_LOG_ERROR 1649 << "EventLogEntry (DBus) doDelete respHandler got error " 1650 << ec; 1651 asyncResp->res.result( 1652 boost::beast::http::status::internal_server_error); 1653 return; 1654 } 1655 1656 asyncResp->res.result(boost::beast::http::status::ok); 1657 }; 1658 1659 // Make call to Logging service to request Delete Log 1660 crow::connections::systemBus->async_method_call( 1661 respHandler, "xyz.openbmc_project.Logging", 1662 "/xyz/openbmc_project/logging/entry/" + entryID, 1663 "xyz.openbmc_project.Object.Delete", "Delete"); 1664 } 1665 }; 1666 1667 class BMCLogServiceCollection : public Node 1668 { 1669 public: 1670 BMCLogServiceCollection(App& app) : 1671 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 1672 { 1673 entityPrivileges = { 1674 {boost::beast::http::verb::get, {{"Login"}}}, 1675 {boost::beast::http::verb::head, {{"Login"}}}, 1676 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1677 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1678 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1679 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1680 } 1681 1682 private: 1683 /** 1684 * Functions triggers appropriate requests on DBus 1685 */ 1686 void doGet(crow::Response& res, const crow::Request&, 1687 const std::vector<std::string>&) override 1688 { 1689 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1690 // Collections don't include the static data added by SubRoute because 1691 // it has a duplicate entry for members 1692 asyncResp->res.jsonValue["@odata.type"] = 1693 "#LogServiceCollection.LogServiceCollection"; 1694 asyncResp->res.jsonValue["@odata.id"] = 1695 "/redfish/v1/Managers/bmc/LogServices"; 1696 asyncResp->res.jsonValue["Name"] = "Open BMC Log Services Collection"; 1697 asyncResp->res.jsonValue["Description"] = 1698 "Collection of LogServices for this Manager"; 1699 nlohmann::json& logServiceArray = asyncResp->res.jsonValue["Members"]; 1700 logServiceArray = nlohmann::json::array(); 1701 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 1702 logServiceArray.push_back( 1703 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump"}}); 1704 #endif 1705 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1706 logServiceArray.push_back( 1707 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1708 #endif 1709 asyncResp->res.jsonValue["Members@odata.count"] = 1710 logServiceArray.size(); 1711 } 1712 }; 1713 1714 class BMCJournalLogService : public Node 1715 { 1716 public: 1717 BMCJournalLogService(App& app) : 1718 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1719 { 1720 entityPrivileges = { 1721 {boost::beast::http::verb::get, {{"Login"}}}, 1722 {boost::beast::http::verb::head, {{"Login"}}}, 1723 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1724 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1725 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1726 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1727 } 1728 1729 private: 1730 void doGet(crow::Response& res, const crow::Request&, 1731 const std::vector<std::string>&) override 1732 { 1733 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1734 asyncResp->res.jsonValue["@odata.type"] = 1735 "#LogService.v1_1_0.LogService"; 1736 asyncResp->res.jsonValue["@odata.id"] = 1737 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1738 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 1739 asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 1740 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1741 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1742 asyncResp->res.jsonValue["Entries"] = { 1743 {"@odata.id", 1744 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1745 } 1746 }; 1747 1748 static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 1749 sd_journal* journal, 1750 nlohmann::json& bmcJournalLogEntryJson) 1751 { 1752 // Get the Log Entry contents 1753 int ret = 0; 1754 1755 std::string message; 1756 std::string_view syslogID; 1757 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 1758 if (ret < 0) 1759 { 1760 BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: " 1761 << strerror(-ret); 1762 } 1763 if (!syslogID.empty()) 1764 { 1765 message += std::string(syslogID) + ": "; 1766 } 1767 1768 std::string_view msg; 1769 ret = getJournalMetadata(journal, "MESSAGE", msg); 1770 if (ret < 0) 1771 { 1772 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1773 return 1; 1774 } 1775 message += std::string(msg); 1776 1777 // Get the severity from the PRIORITY field 1778 long int severity = 8; // Default to an invalid priority 1779 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1780 if (ret < 0) 1781 { 1782 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1783 } 1784 1785 // Get the Created time from the timestamp 1786 std::string entryTimeStr; 1787 if (!getEntryTimestamp(journal, entryTimeStr)) 1788 { 1789 return 1; 1790 } 1791 1792 // Fill in the log entry with the gathered data 1793 bmcJournalLogEntryJson = { 1794 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1795 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1796 bmcJournalLogEntryID}, 1797 {"Name", "BMC Journal Entry"}, 1798 {"Id", bmcJournalLogEntryID}, 1799 {"Message", std::move(message)}, 1800 {"EntryType", "Oem"}, 1801 {"Severity", 1802 severity <= 2 ? "Critical" : severity <= 4 ? "Warning" : "OK"}, 1803 {"OemRecordFormat", "BMC Journal Entry"}, 1804 {"Created", std::move(entryTimeStr)}}; 1805 return 0; 1806 } 1807 1808 class BMCJournalLogEntryCollection : public Node 1809 { 1810 public: 1811 BMCJournalLogEntryCollection(App& app) : 1812 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1813 { 1814 entityPrivileges = { 1815 {boost::beast::http::verb::get, {{"Login"}}}, 1816 {boost::beast::http::verb::head, {{"Login"}}}, 1817 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1818 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1819 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1820 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1821 } 1822 1823 private: 1824 void doGet(crow::Response& res, const crow::Request& req, 1825 const std::vector<std::string>&) override 1826 { 1827 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1828 static constexpr const long maxEntriesPerPage = 1000; 1829 uint64_t skip = 0; 1830 uint64_t top = maxEntriesPerPage; // Show max entries by default 1831 if (!getSkipParam(asyncResp->res, req, skip)) 1832 { 1833 return; 1834 } 1835 if (!getTopParam(asyncResp->res, req, top)) 1836 { 1837 return; 1838 } 1839 // Collections don't include the static data added by SubRoute because 1840 // it has a duplicate entry for members 1841 asyncResp->res.jsonValue["@odata.type"] = 1842 "#LogEntryCollection.LogEntryCollection"; 1843 asyncResp->res.jsonValue["@odata.id"] = 1844 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1845 asyncResp->res.jsonValue["@odata.id"] = 1846 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1847 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1848 asyncResp->res.jsonValue["Description"] = 1849 "Collection of BMC Journal Entries"; 1850 asyncResp->res.jsonValue["@odata.id"] = 1851 "/redfish/v1/Managers/bmc/LogServices/BmcLog/Entries"; 1852 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 1853 logEntryArray = nlohmann::json::array(); 1854 1855 // Go through the journal and use the timestamp to create a unique ID 1856 // for each entry 1857 sd_journal* journalTmp = nullptr; 1858 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1859 if (ret < 0) 1860 { 1861 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1862 messages::internalError(asyncResp->res); 1863 return; 1864 } 1865 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1866 journalTmp, sd_journal_close); 1867 journalTmp = nullptr; 1868 uint64_t entryCount = 0; 1869 // Reset the unique ID on the first entry 1870 bool firstEntry = true; 1871 SD_JOURNAL_FOREACH(journal.get()) 1872 { 1873 entryCount++; 1874 // Handle paging using skip (number of entries to skip from the 1875 // start) and top (number of entries to display) 1876 if (entryCount <= skip || entryCount > skip + top) 1877 { 1878 continue; 1879 } 1880 1881 std::string idStr; 1882 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1883 { 1884 continue; 1885 } 1886 1887 if (firstEntry) 1888 { 1889 firstEntry = false; 1890 } 1891 1892 logEntryArray.push_back({}); 1893 nlohmann::json& bmcJournalLogEntry = logEntryArray.back(); 1894 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 1895 bmcJournalLogEntry) != 0) 1896 { 1897 messages::internalError(asyncResp->res); 1898 return; 1899 } 1900 } 1901 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1902 if (skip + top < entryCount) 1903 { 1904 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1905 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries?$skip=" + 1906 std::to_string(skip + top); 1907 } 1908 } 1909 }; 1910 1911 class BMCJournalLogEntry : public Node 1912 { 1913 public: 1914 BMCJournalLogEntry(App& app) : 1915 Node(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/", 1916 std::string()) 1917 { 1918 entityPrivileges = { 1919 {boost::beast::http::verb::get, {{"Login"}}}, 1920 {boost::beast::http::verb::head, {{"Login"}}}, 1921 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 1922 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 1923 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 1924 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 1925 } 1926 1927 private: 1928 void doGet(crow::Response& res, const crow::Request&, 1929 const std::vector<std::string>& params) override 1930 { 1931 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1932 if (params.size() != 1) 1933 { 1934 messages::internalError(asyncResp->res); 1935 return; 1936 } 1937 const std::string& entryID = params[0]; 1938 // Convert the unique ID back to a timestamp to find the entry 1939 uint64_t ts = 0; 1940 uint64_t index = 0; 1941 if (!getTimestampFromID(asyncResp->res, entryID, ts, index)) 1942 { 1943 return; 1944 } 1945 1946 sd_journal* journalTmp = nullptr; 1947 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1948 if (ret < 0) 1949 { 1950 BMCWEB_LOG_ERROR << "failed to open journal: " << strerror(-ret); 1951 messages::internalError(asyncResp->res); 1952 return; 1953 } 1954 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 1955 journalTmp, sd_journal_close); 1956 journalTmp = nullptr; 1957 // Go to the timestamp in the log and move to the entry at the index 1958 // tracking the unique ID 1959 std::string idStr; 1960 bool firstEntry = true; 1961 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 1962 if (ret < 0) 1963 { 1964 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 1965 << strerror(-ret); 1966 messages::internalError(asyncResp->res); 1967 return; 1968 } 1969 for (uint64_t i = 0; i <= index; i++) 1970 { 1971 sd_journal_next(journal.get()); 1972 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1973 { 1974 messages::internalError(asyncResp->res); 1975 return; 1976 } 1977 if (firstEntry) 1978 { 1979 firstEntry = false; 1980 } 1981 } 1982 // Confirm that the entry ID matches what was requested 1983 if (idStr != entryID) 1984 { 1985 messages::resourceMissingAtURI(asyncResp->res, entryID); 1986 return; 1987 } 1988 1989 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 1990 asyncResp->res.jsonValue) != 0) 1991 { 1992 messages::internalError(asyncResp->res); 1993 return; 1994 } 1995 } 1996 }; 1997 1998 class BMCDumpService : public Node 1999 { 2000 public: 2001 BMCDumpService(App& app) : 2002 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 2003 { 2004 entityPrivileges = { 2005 {boost::beast::http::verb::get, {{"Login"}}}, 2006 {boost::beast::http::verb::head, {{"Login"}}}, 2007 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2008 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2009 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2010 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2011 } 2012 2013 private: 2014 void doGet(crow::Response& res, const crow::Request&, 2015 const std::vector<std::string>&) override 2016 { 2017 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2018 2019 asyncResp->res.jsonValue["@odata.id"] = 2020 "/redfish/v1/Managers/bmc/LogServices/Dump"; 2021 asyncResp->res.jsonValue["@odata.type"] = 2022 "#LogService.v1_2_0.LogService"; 2023 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2024 asyncResp->res.jsonValue["Description"] = "BMC Dump LogService"; 2025 asyncResp->res.jsonValue["Id"] = "Dump"; 2026 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2027 asyncResp->res.jsonValue["Entries"] = { 2028 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}}; 2029 asyncResp->res.jsonValue["Actions"] = { 2030 {"#LogService.ClearLog", 2031 {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/" 2032 "Actions/LogService.ClearLog"}}}, 2033 {"#LogService.CollectDiagnosticData", 2034 {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/" 2035 "Actions/LogService.CollectDiagnosticData"}}}}; 2036 } 2037 }; 2038 2039 class BMCDumpEntryCollection : public Node 2040 { 2041 public: 2042 BMCDumpEntryCollection(App& app) : 2043 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 2044 { 2045 entityPrivileges = { 2046 {boost::beast::http::verb::get, {{"Login"}}}, 2047 {boost::beast::http::verb::head, {{"Login"}}}, 2048 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2049 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2050 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2051 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2052 } 2053 2054 private: 2055 /** 2056 * Functions triggers appropriate requests on DBus 2057 */ 2058 void doGet(crow::Response& res, const crow::Request&, 2059 const std::vector<std::string>&) override 2060 { 2061 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2062 2063 asyncResp->res.jsonValue["@odata.type"] = 2064 "#LogEntryCollection.LogEntryCollection"; 2065 asyncResp->res.jsonValue["@odata.id"] = 2066 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; 2067 asyncResp->res.jsonValue["Name"] = "BMC Dump Entries"; 2068 asyncResp->res.jsonValue["Description"] = 2069 "Collection of BMC Dump Entries"; 2070 2071 getDumpEntryCollection(asyncResp, "BMC"); 2072 } 2073 }; 2074 2075 class BMCDumpEntry : public Node 2076 { 2077 public: 2078 BMCDumpEntry(App& app) : 2079 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/", 2080 std::string()) 2081 { 2082 entityPrivileges = { 2083 {boost::beast::http::verb::get, {{"Login"}}}, 2084 {boost::beast::http::verb::head, {{"Login"}}}, 2085 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2086 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2087 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2088 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2089 } 2090 2091 private: 2092 void doGet(crow::Response& res, const crow::Request&, 2093 const std::vector<std::string>& params) override 2094 { 2095 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2096 if (params.size() != 1) 2097 { 2098 messages::internalError(asyncResp->res); 2099 return; 2100 } 2101 getDumpEntryById(asyncResp, params[0], "BMC"); 2102 } 2103 2104 void doDelete(crow::Response& res, const crow::Request&, 2105 const std::vector<std::string>& params) override 2106 { 2107 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2108 if (params.size() != 1) 2109 { 2110 messages::internalError(asyncResp->res); 2111 return; 2112 } 2113 deleteDumpEntry(asyncResp, params[0], "bmc"); 2114 } 2115 }; 2116 2117 class BMCDumpCreate : public Node 2118 { 2119 public: 2120 BMCDumpCreate(App& app) : 2121 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/" 2122 "Actions/" 2123 "LogService.CollectDiagnosticData/") 2124 { 2125 entityPrivileges = { 2126 {boost::beast::http::verb::get, {{"Login"}}}, 2127 {boost::beast::http::verb::head, {{"Login"}}}, 2128 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2129 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2130 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2131 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2132 } 2133 2134 private: 2135 void doPost(crow::Response& res, const crow::Request& req, 2136 const std::vector<std::string>&) override 2137 { 2138 createDump(res, req, "BMC"); 2139 } 2140 }; 2141 2142 class BMCDumpClear : public Node 2143 { 2144 public: 2145 BMCDumpClear(App& app) : 2146 Node(app, "/redfish/v1/Managers/bmc/LogServices/Dump/" 2147 "Actions/" 2148 "LogService.ClearLog/") 2149 { 2150 entityPrivileges = { 2151 {boost::beast::http::verb::get, {{"Login"}}}, 2152 {boost::beast::http::verb::head, {{"Login"}}}, 2153 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2154 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2155 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2156 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2157 } 2158 2159 private: 2160 void doPost(crow::Response& res, const crow::Request&, 2161 const std::vector<std::string>&) override 2162 { 2163 clearDump(res, "BMC"); 2164 } 2165 }; 2166 2167 class SystemDumpService : public Node 2168 { 2169 public: 2170 SystemDumpService(App& app) : 2171 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/") 2172 { 2173 entityPrivileges = { 2174 {boost::beast::http::verb::get, {{"Login"}}}, 2175 {boost::beast::http::verb::head, {{"Login"}}}, 2176 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2177 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2178 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2179 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2180 } 2181 2182 private: 2183 void doGet(crow::Response& res, const crow::Request&, 2184 const std::vector<std::string>&) override 2185 { 2186 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2187 2188 asyncResp->res.jsonValue["@odata.id"] = 2189 "/redfish/v1/Systems/system/LogServices/Dump"; 2190 asyncResp->res.jsonValue["@odata.type"] = 2191 "#LogService.v1_2_0.LogService"; 2192 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2193 asyncResp->res.jsonValue["Description"] = "System Dump LogService"; 2194 asyncResp->res.jsonValue["Id"] = "Dump"; 2195 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2196 asyncResp->res.jsonValue["Entries"] = { 2197 {"@odata.id", 2198 "/redfish/v1/Systems/system/LogServices/Dump/Entries"}}; 2199 asyncResp->res.jsonValue["Actions"] = { 2200 {"#LogService.ClearLog", 2201 {{"target", "/redfish/v1/Systems/system/LogServices/Dump/Actions/" 2202 "LogService.ClearLog"}}}, 2203 {"#LogService.CollectDiagnosticData", 2204 {{"target", "/redfish/v1/Systems/system/LogServices/Dump/Actions/" 2205 "LogService.CollectDiagnosticData"}}}}; 2206 } 2207 }; 2208 2209 class SystemDumpEntryCollection : public Node 2210 { 2211 public: 2212 SystemDumpEntryCollection(App& app) : 2213 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/") 2214 { 2215 entityPrivileges = { 2216 {boost::beast::http::verb::get, {{"Login"}}}, 2217 {boost::beast::http::verb::head, {{"Login"}}}, 2218 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2219 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2220 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2221 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2222 } 2223 2224 private: 2225 /** 2226 * Functions triggers appropriate requests on DBus 2227 */ 2228 void doGet(crow::Response& res, const crow::Request&, 2229 const std::vector<std::string>&) override 2230 { 2231 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2232 2233 asyncResp->res.jsonValue["@odata.type"] = 2234 "#LogEntryCollection.LogEntryCollection"; 2235 asyncResp->res.jsonValue["@odata.id"] = 2236 "/redfish/v1/Systems/system/LogServices/Dump/Entries"; 2237 asyncResp->res.jsonValue["Name"] = "System Dump Entries"; 2238 asyncResp->res.jsonValue["Description"] = 2239 "Collection of System Dump Entries"; 2240 2241 getDumpEntryCollection(asyncResp, "System"); 2242 } 2243 }; 2244 2245 class SystemDumpEntry : public Node 2246 { 2247 public: 2248 SystemDumpEntry(App& app) : 2249 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/", 2250 std::string()) 2251 { 2252 entityPrivileges = { 2253 {boost::beast::http::verb::get, {{"Login"}}}, 2254 {boost::beast::http::verb::head, {{"Login"}}}, 2255 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2256 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2257 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2258 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2259 } 2260 2261 private: 2262 void doGet(crow::Response& res, const crow::Request&, 2263 const std::vector<std::string>& params) override 2264 { 2265 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2266 if (params.size() != 1) 2267 { 2268 messages::internalError(asyncResp->res); 2269 return; 2270 } 2271 getDumpEntryById(asyncResp, params[0], "System"); 2272 } 2273 2274 void doDelete(crow::Response& res, const crow::Request&, 2275 const std::vector<std::string>& params) override 2276 { 2277 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2278 if (params.size() != 1) 2279 { 2280 messages::internalError(asyncResp->res); 2281 return; 2282 } 2283 deleteDumpEntry(asyncResp, params[0], "system"); 2284 } 2285 }; 2286 2287 class SystemDumpCreate : public Node 2288 { 2289 public: 2290 SystemDumpCreate(App& app) : 2291 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/" 2292 "Actions/" 2293 "LogService.CollectDiagnosticData/") 2294 { 2295 entityPrivileges = { 2296 {boost::beast::http::verb::get, {{"Login"}}}, 2297 {boost::beast::http::verb::head, {{"Login"}}}, 2298 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2299 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2300 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2301 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2302 } 2303 2304 private: 2305 void doPost(crow::Response& res, const crow::Request& req, 2306 const std::vector<std::string>&) override 2307 { 2308 createDump(res, req, "System"); 2309 } 2310 }; 2311 2312 class SystemDumpClear : public Node 2313 { 2314 public: 2315 SystemDumpClear(App& app) : 2316 Node(app, "/redfish/v1/Systems/system/LogServices/Dump/" 2317 "Actions/" 2318 "LogService.ClearLog/") 2319 { 2320 entityPrivileges = { 2321 {boost::beast::http::verb::get, {{"Login"}}}, 2322 {boost::beast::http::verb::head, {{"Login"}}}, 2323 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2324 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2325 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2326 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2327 } 2328 2329 private: 2330 void doPost(crow::Response& res, const crow::Request&, 2331 const std::vector<std::string>&) override 2332 { 2333 clearDump(res, "System"); 2334 } 2335 }; 2336 2337 class CrashdumpService : public Node 2338 { 2339 public: 2340 CrashdumpService(App& app) : 2341 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 2342 { 2343 // Note: Deviated from redfish privilege registry for GET & HEAD 2344 // method for security reasons. 2345 entityPrivileges = { 2346 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2347 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2348 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2349 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2350 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2351 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2352 } 2353 2354 private: 2355 /** 2356 * Functions triggers appropriate requests on DBus 2357 */ 2358 void doGet(crow::Response& res, const crow::Request&, 2359 const std::vector<std::string>&) override 2360 { 2361 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2362 // Copy over the static data to include the entries added by SubRoute 2363 asyncResp->res.jsonValue["@odata.id"] = 2364 "/redfish/v1/Systems/system/LogServices/Crashdump"; 2365 asyncResp->res.jsonValue["@odata.type"] = 2366 "#LogService.v1_1_0.LogService"; 2367 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 2368 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 2369 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 2370 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2371 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 2372 asyncResp->res.jsonValue["Entries"] = { 2373 {"@odata.id", 2374 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 2375 asyncResp->res.jsonValue["Actions"] = { 2376 {"#LogService.ClearLog", 2377 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2378 "Actions/LogService.ClearLog"}}}, 2379 {"Oem", 2380 {{"#Crashdump.OnDemand", 2381 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2382 "Actions/Oem/Crashdump.OnDemand"}}}, 2383 {"#Crashdump.Telemetry", 2384 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2385 "Actions/Oem/Crashdump.Telemetry"}}}}}}; 2386 2387 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 2388 asyncResp->res.jsonValue["Actions"]["Oem"].push_back( 2389 {"#Crashdump.SendRawPeci", 2390 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2391 "Actions/Oem/Crashdump.SendRawPeci"}}}); 2392 #endif 2393 } 2394 }; 2395 2396 class CrashdumpClear : public Node 2397 { 2398 public: 2399 CrashdumpClear(App& app) : 2400 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 2401 "LogService.ClearLog/") 2402 { 2403 // Note: Deviated from redfish privilege registry for GET & HEAD 2404 // method for security reasons. 2405 entityPrivileges = { 2406 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2407 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2408 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2409 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2410 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2411 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2412 } 2413 2414 private: 2415 void doPost(crow::Response& res, const crow::Request&, 2416 const std::vector<std::string>&) override 2417 { 2418 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2419 2420 crow::connections::systemBus->async_method_call( 2421 [asyncResp](const boost::system::error_code ec, 2422 const std::string&) { 2423 if (ec) 2424 { 2425 messages::internalError(asyncResp->res); 2426 return; 2427 } 2428 messages::success(asyncResp->res); 2429 }, 2430 crashdumpObject, crashdumpPath, deleteAllInterface, "DeleteAll"); 2431 } 2432 }; 2433 2434 static void logCrashdumpEntry(const std::shared_ptr<AsyncResp>& asyncResp, 2435 const std::string& logID, 2436 nlohmann::json& logEntryJson) 2437 { 2438 auto getStoredLogCallback = 2439 [asyncResp, logID, &logEntryJson]( 2440 const boost::system::error_code ec, 2441 const std::vector<std::pair<std::string, VariantType>>& params) { 2442 if (ec) 2443 { 2444 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 2445 if (ec.value() == 2446 boost::system::linux_error::bad_request_descriptor) 2447 { 2448 messages::resourceNotFound(asyncResp->res, "LogEntry", 2449 logID); 2450 } 2451 else 2452 { 2453 messages::internalError(asyncResp->res); 2454 } 2455 return; 2456 } 2457 2458 std::string timestamp{}; 2459 std::string filename{}; 2460 std::string logfile{}; 2461 parseCrashdumpParameters(params, filename, timestamp, logfile); 2462 2463 if (filename.empty() || timestamp.empty()) 2464 { 2465 messages::resourceMissingAtURI(asyncResp->res, logID); 2466 return; 2467 } 2468 2469 std::string crashdumpURI = 2470 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 2471 logID + "/" + filename; 2472 logEntryJson = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2473 {"@odata.id", "/redfish/v1/Systems/system/" 2474 "LogServices/Crashdump/Entries/" + 2475 logID}, 2476 {"Name", "CPU Crashdump"}, 2477 {"Id", logID}, 2478 {"EntryType", "Oem"}, 2479 {"OemRecordFormat", "Crashdump URI"}, 2480 {"Message", std::move(crashdumpURI)}, 2481 {"Created", std::move(timestamp)}}; 2482 }; 2483 crow::connections::systemBus->async_method_call( 2484 std::move(getStoredLogCallback), crashdumpObject, 2485 crashdumpPath + std::string("/") + logID, 2486 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2487 } 2488 2489 class CrashdumpEntryCollection : public Node 2490 { 2491 public: 2492 CrashdumpEntryCollection(App& app) : 2493 Node(app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 2494 { 2495 // Note: Deviated from redfish privilege registry for GET & HEAD 2496 // method for security reasons. 2497 entityPrivileges = { 2498 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2499 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2500 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2501 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2502 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2503 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2504 } 2505 2506 private: 2507 /** 2508 * Functions triggers appropriate requests on DBus 2509 */ 2510 void doGet(crow::Response& res, const crow::Request&, 2511 const std::vector<std::string>&) override 2512 { 2513 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2514 // Collections don't include the static data added by SubRoute because 2515 // it has a duplicate entry for members 2516 auto getLogEntriesCallback = [asyncResp]( 2517 const boost::system::error_code ec, 2518 const std::vector<std::string>& resp) { 2519 if (ec) 2520 { 2521 if (ec.value() != 2522 boost::system::errc::no_such_file_or_directory) 2523 { 2524 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 2525 << ec.message(); 2526 messages::internalError(asyncResp->res); 2527 return; 2528 } 2529 } 2530 asyncResp->res.jsonValue["@odata.type"] = 2531 "#LogEntryCollection.LogEntryCollection"; 2532 asyncResp->res.jsonValue["@odata.id"] = 2533 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 2534 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 2535 asyncResp->res.jsonValue["Description"] = 2536 "Collection of Crashdump Entries"; 2537 nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; 2538 logEntryArray = nlohmann::json::array(); 2539 std::vector<std::string> logIDs; 2540 // Get the list of log entries and build up an empty array big 2541 // enough to hold them 2542 for (const std::string& objpath : resp) 2543 { 2544 // Get the log ID 2545 std::size_t lastPos = objpath.rfind('/'); 2546 if (lastPos == std::string::npos) 2547 { 2548 continue; 2549 } 2550 logIDs.emplace_back(objpath.substr(lastPos + 1)); 2551 2552 // Add a space for the log entry to the array 2553 logEntryArray.push_back({}); 2554 } 2555 // Now go through and set up async calls to fill in the entries 2556 size_t index = 0; 2557 for (const std::string& logID : logIDs) 2558 { 2559 // Add the log entry to the array 2560 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 2561 } 2562 asyncResp->res.jsonValue["Members@odata.count"] = 2563 logEntryArray.size(); 2564 }; 2565 crow::connections::systemBus->async_method_call( 2566 std::move(getLogEntriesCallback), 2567 "xyz.openbmc_project.ObjectMapper", 2568 "/xyz/openbmc_project/object_mapper", 2569 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 2570 std::array<const char*, 1>{crashdumpInterface}); 2571 } 2572 }; 2573 2574 class CrashdumpEntry : public Node 2575 { 2576 public: 2577 CrashdumpEntry(App& app) : 2578 Node(app, 2579 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/", 2580 std::string()) 2581 { 2582 // Note: Deviated from redfish privilege registry for GET & HEAD 2583 // method for security reasons. 2584 entityPrivileges = { 2585 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2586 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2587 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2588 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2589 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2590 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2591 } 2592 2593 private: 2594 void doGet(crow::Response& res, const crow::Request&, 2595 const std::vector<std::string>& params) override 2596 { 2597 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2598 if (params.size() != 1) 2599 { 2600 messages::internalError(asyncResp->res); 2601 return; 2602 } 2603 const std::string& logID = params[0]; 2604 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 2605 } 2606 }; 2607 2608 class CrashdumpFile : public Node 2609 { 2610 public: 2611 CrashdumpFile(App& app) : 2612 Node(app, 2613 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/" 2614 "<str>/", 2615 std::string(), std::string()) 2616 { 2617 // Note: Deviated from redfish privilege registry for GET & HEAD 2618 // method for security reasons. 2619 entityPrivileges = { 2620 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2621 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2622 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2623 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2624 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2625 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2626 } 2627 2628 private: 2629 void doGet(crow::Response& res, const crow::Request&, 2630 const std::vector<std::string>& params) override 2631 { 2632 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2633 if (params.size() != 2) 2634 { 2635 messages::internalError(asyncResp->res); 2636 return; 2637 } 2638 const std::string& logID = params[0]; 2639 const std::string& fileName = params[1]; 2640 2641 auto getStoredLogCallback = 2642 [asyncResp, logID, fileName]( 2643 const boost::system::error_code ec, 2644 const std::vector<std::pair<std::string, VariantType>>& resp) { 2645 if (ec) 2646 { 2647 BMCWEB_LOG_DEBUG << "failed to get log ec: " 2648 << ec.message(); 2649 messages::internalError(asyncResp->res); 2650 return; 2651 } 2652 2653 std::string dbusFilename{}; 2654 std::string dbusTimestamp{}; 2655 std::string dbusFilepath{}; 2656 2657 parseCrashdumpParameters(resp, dbusFilename, dbusTimestamp, 2658 dbusFilepath); 2659 2660 if (dbusFilename.empty() || dbusTimestamp.empty() || 2661 dbusFilepath.empty()) 2662 { 2663 messages::resourceMissingAtURI(asyncResp->res, fileName); 2664 return; 2665 } 2666 2667 // Verify the file name parameter is correct 2668 if (fileName != dbusFilename) 2669 { 2670 messages::resourceMissingAtURI(asyncResp->res, fileName); 2671 return; 2672 } 2673 2674 if (!std::filesystem::exists(dbusFilepath)) 2675 { 2676 messages::resourceMissingAtURI(asyncResp->res, fileName); 2677 return; 2678 } 2679 std::ifstream ifs(dbusFilepath, std::ios::in | 2680 std::ios::binary | 2681 std::ios::ate); 2682 std::ifstream::pos_type fileSize = ifs.tellg(); 2683 if (fileSize < 0) 2684 { 2685 messages::generalError(asyncResp->res); 2686 return; 2687 } 2688 ifs.seekg(0, std::ios::beg); 2689 2690 auto crashData = std::make_unique<char[]>( 2691 static_cast<unsigned int>(fileSize)); 2692 2693 ifs.read(crashData.get(), static_cast<int>(fileSize)); 2694 2695 // The cast to std::string is intentional in order to use the 2696 // assign() that applies move mechanics 2697 asyncResp->res.body().assign( 2698 static_cast<std::string>(crashData.get())); 2699 2700 // Configure this to be a file download when accessed from 2701 // a browser 2702 asyncResp->res.addHeader("Content-Disposition", "attachment"); 2703 }; 2704 crow::connections::systemBus->async_method_call( 2705 std::move(getStoredLogCallback), crashdumpObject, 2706 crashdumpPath + std::string("/") + logID, 2707 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2708 } 2709 }; 2710 2711 class OnDemandCrashdump : public Node 2712 { 2713 public: 2714 OnDemandCrashdump(App& app) : 2715 Node(app, 2716 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2717 "Crashdump.OnDemand/") 2718 { 2719 // Note: Deviated from redfish privilege registry for GET & HEAD 2720 // method for security reasons. 2721 entityPrivileges = { 2722 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2723 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2724 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2725 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2726 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2727 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2728 } 2729 2730 private: 2731 void doPost(crow::Response& res, const crow::Request& req, 2732 const std::vector<std::string>&) override 2733 { 2734 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2735 2736 auto generateonDemandLogCallback = [asyncResp, 2737 req](const boost::system::error_code 2738 ec, 2739 const std::string&) { 2740 if (ec) 2741 { 2742 if (ec.value() == boost::system::errc::operation_not_supported) 2743 { 2744 messages::resourceInStandby(asyncResp->res); 2745 } 2746 else if (ec.value() == 2747 boost::system::errc::device_or_resource_busy) 2748 { 2749 messages::serviceTemporarilyUnavailable(asyncResp->res, 2750 "60"); 2751 } 2752 else 2753 { 2754 messages::internalError(asyncResp->res); 2755 } 2756 return; 2757 } 2758 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2759 [](boost::system::error_code err, sdbusplus::message::message&, 2760 const std::shared_ptr<task::TaskData>& taskData) { 2761 if (!err) 2762 { 2763 taskData->messages.emplace_back( 2764 messages::taskCompletedOK( 2765 std::to_string(taskData->index))); 2766 taskData->state = "Completed"; 2767 } 2768 return task::completed; 2769 }, 2770 "type='signal',interface='org.freedesktop.DBus.Properties'," 2771 "member='PropertiesChanged',arg0namespace='com.intel." 2772 "crashdump'"); 2773 task->startTimer(std::chrono::minutes(5)); 2774 task->populateResp(asyncResp->res); 2775 task->payload.emplace(req); 2776 }; 2777 crow::connections::systemBus->async_method_call( 2778 std::move(generateonDemandLogCallback), crashdumpObject, 2779 crashdumpPath, crashdumpOnDemandInterface, "GenerateOnDemandLog"); 2780 } 2781 }; 2782 2783 class TelemetryCrashdump : public Node 2784 { 2785 public: 2786 TelemetryCrashdump(App& app) : 2787 Node(app, 2788 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2789 "Crashdump.Telemetry/") 2790 { 2791 // Note: Deviated from redfish privilege registry for GET & HEAD 2792 // method for security reasons. 2793 entityPrivileges = { 2794 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2795 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2796 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2797 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2798 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2799 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2800 } 2801 2802 private: 2803 void doPost(crow::Response& res, const crow::Request& req, 2804 const std::vector<std::string>&) override 2805 { 2806 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2807 2808 auto generateTelemetryLogCallback = [asyncResp, req]( 2809 const boost::system::error_code 2810 ec, 2811 const std::string&) { 2812 if (ec) 2813 { 2814 if (ec.value() == boost::system::errc::operation_not_supported) 2815 { 2816 messages::resourceInStandby(asyncResp->res); 2817 } 2818 else if (ec.value() == 2819 boost::system::errc::device_or_resource_busy) 2820 { 2821 messages::serviceTemporarilyUnavailable(asyncResp->res, 2822 "60"); 2823 } 2824 else 2825 { 2826 messages::internalError(asyncResp->res); 2827 } 2828 return; 2829 } 2830 std::shared_ptr<task::TaskData> task = task::TaskData::createTask( 2831 [](boost::system::error_code err, sdbusplus::message::message&, 2832 const std::shared_ptr<task::TaskData>& taskData) { 2833 if (!err) 2834 { 2835 taskData->messages.emplace_back( 2836 messages::taskCompletedOK( 2837 std::to_string(taskData->index))); 2838 taskData->state = "Completed"; 2839 } 2840 return task::completed; 2841 }, 2842 "type='signal',interface='org.freedesktop.DBus.Properties'," 2843 "member='PropertiesChanged',arg0namespace='com.intel." 2844 "crashdump'"); 2845 task->startTimer(std::chrono::minutes(5)); 2846 task->populateResp(asyncResp->res); 2847 task->payload.emplace(req); 2848 }; 2849 crow::connections::systemBus->async_method_call( 2850 std::move(generateTelemetryLogCallback), crashdumpObject, 2851 crashdumpPath, crashdumpTelemetryInterface, "GenerateTelemetryLog"); 2852 } 2853 }; 2854 2855 class SendRawPECI : public Node 2856 { 2857 public: 2858 SendRawPECI(App& app) : 2859 Node(app, 2860 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/Oem/" 2861 "Crashdump.SendRawPeci/") 2862 { 2863 // Note: Deviated from redfish privilege registry for GET & HEAD 2864 // method for security reasons. 2865 entityPrivileges = { 2866 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 2867 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 2868 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2869 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2870 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2871 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2872 } 2873 2874 private: 2875 void doPost(crow::Response& res, const crow::Request& req, 2876 const std::vector<std::string>&) override 2877 { 2878 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2879 std::vector<std::vector<uint8_t>> peciCommands; 2880 2881 if (!json_util::readJson(req, res, "PECICommands", peciCommands)) 2882 { 2883 return; 2884 } 2885 uint32_t idx = 0; 2886 for (auto const& cmd : peciCommands) 2887 { 2888 if (cmd.size() < 3) 2889 { 2890 std::string s("["); 2891 for (auto const& val : cmd) 2892 { 2893 if (val != *cmd.begin()) 2894 { 2895 s += ","; 2896 } 2897 s += std::to_string(val); 2898 } 2899 s += "]"; 2900 messages::actionParameterValueFormatError( 2901 res, s, "PECICommands[" + std::to_string(idx) + "]", 2902 "SendRawPeci"); 2903 return; 2904 } 2905 idx++; 2906 } 2907 // Callback to return the Raw PECI response 2908 auto sendRawPECICallback = 2909 [asyncResp](const boost::system::error_code ec, 2910 const std::vector<std::vector<uint8_t>>& resp) { 2911 if (ec) 2912 { 2913 BMCWEB_LOG_DEBUG << "failed to process PECI commands ec: " 2914 << ec.message(); 2915 messages::internalError(asyncResp->res); 2916 return; 2917 } 2918 asyncResp->res.jsonValue = {{"Name", "PECI Command Response"}, 2919 {"PECIResponse", resp}}; 2920 }; 2921 // Call the SendRawPECI command with the provided data 2922 crow::connections::systemBus->async_method_call( 2923 std::move(sendRawPECICallback), crashdumpObject, crashdumpPath, 2924 crashdumpRawPECIInterface, "SendRawPeci", peciCommands); 2925 } 2926 }; 2927 2928 /** 2929 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2930 */ 2931 class DBusLogServiceActionsClear : public Node 2932 { 2933 public: 2934 DBusLogServiceActionsClear(App& app) : 2935 Node(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2936 "LogService.ClearLog/") 2937 { 2938 entityPrivileges = { 2939 {boost::beast::http::verb::get, {{"Login"}}}, 2940 {boost::beast::http::verb::head, {{"Login"}}}, 2941 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2942 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2943 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2944 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2945 } 2946 2947 private: 2948 /** 2949 * Function handles POST method request. 2950 * The Clear Log actions does not require any parameter.The action deletes 2951 * all entries found in the Entries collection for this Log Service. 2952 */ 2953 void doPost(crow::Response& res, const crow::Request&, 2954 const std::vector<std::string>&) override 2955 { 2956 BMCWEB_LOG_DEBUG << "Do delete all entries."; 2957 2958 auto asyncResp = std::make_shared<AsyncResp>(res); 2959 // Process response from Logging service. 2960 auto respHandler = [asyncResp](const boost::system::error_code ec) { 2961 BMCWEB_LOG_DEBUG << "doClearLog resp_handler callback: Done"; 2962 if (ec) 2963 { 2964 // TODO Handle for specific error code 2965 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " << ec; 2966 asyncResp->res.result( 2967 boost::beast::http::status::internal_server_error); 2968 return; 2969 } 2970 2971 asyncResp->res.result(boost::beast::http::status::no_content); 2972 }; 2973 2974 // Make call to Logging service to request Clear Log 2975 crow::connections::systemBus->async_method_call( 2976 respHandler, "xyz.openbmc_project.Logging", 2977 "/xyz/openbmc_project/logging", 2978 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2979 } 2980 }; 2981 2982 /**************************************************** 2983 * Redfish PostCode interfaces 2984 * using DBUS interface: getPostCodesTS 2985 ******************************************************/ 2986 class PostCodesLogService : public Node 2987 { 2988 public: 2989 PostCodesLogService(App& app) : 2990 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 2991 { 2992 entityPrivileges = { 2993 {boost::beast::http::verb::get, {{"Login"}}}, 2994 {boost::beast::http::verb::head, {{"Login"}}}, 2995 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 2996 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 2997 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 2998 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 2999 } 3000 3001 private: 3002 void doGet(crow::Response& res, const crow::Request&, 3003 const std::vector<std::string>&) override 3004 { 3005 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3006 3007 asyncResp->res.jsonValue = { 3008 {"@odata.id", "/redfish/v1/Systems/system/LogServices/PostCodes"}, 3009 {"@odata.type", "#LogService.v1_1_0.LogService"}, 3010 {"Name", "POST Code Log Service"}, 3011 {"Description", "POST Code Log Service"}, 3012 {"Id", "BIOS POST Code Log"}, 3013 {"OverWritePolicy", "WrapsWhenFull"}, 3014 {"Entries", 3015 {{"@odata.id", 3016 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"}}}}; 3017 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 3018 {"target", "/redfish/v1/Systems/system/LogServices/PostCodes/" 3019 "Actions/LogService.ClearLog"}}; 3020 } 3021 }; 3022 3023 class PostCodesClear : public Node 3024 { 3025 public: 3026 PostCodesClear(App& app) : 3027 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/" 3028 "LogService.ClearLog/") 3029 { 3030 entityPrivileges = { 3031 {boost::beast::http::verb::get, {{"Login"}}}, 3032 {boost::beast::http::verb::head, {{"Login"}}}, 3033 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 3034 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 3035 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 3036 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 3037 } 3038 3039 private: 3040 void doPost(crow::Response& res, const crow::Request&, 3041 const std::vector<std::string>&) override 3042 { 3043 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 3044 3045 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3046 // Make call to post-code service to request clear all 3047 crow::connections::systemBus->async_method_call( 3048 [asyncResp](const boost::system::error_code ec) { 3049 if (ec) 3050 { 3051 // TODO Handle for specific error code 3052 BMCWEB_LOG_ERROR 3053 << "doClearPostCodes resp_handler got error " << ec; 3054 asyncResp->res.result( 3055 boost::beast::http::status::internal_server_error); 3056 messages::internalError(asyncResp->res); 3057 return; 3058 } 3059 }, 3060 "xyz.openbmc_project.State.Boot.PostCode", 3061 "/xyz/openbmc_project/State/Boot/PostCode", 3062 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 3063 } 3064 }; 3065 3066 static void fillPostCodeEntry( 3067 const std::shared_ptr<AsyncResp>& aResp, 3068 const boost::container::flat_map<uint64_t, uint64_t>& postcode, 3069 const uint16_t bootIndex, const uint64_t codeIndex = 0, 3070 const uint64_t skip = 0, const uint64_t top = 0) 3071 { 3072 // Get the Message from the MessageRegistry 3073 const message_registries::Message* message = 3074 message_registries::getMessage("OpenBMC.0.1.BIOSPOSTCode"); 3075 3076 uint64_t currentCodeIndex = 0; 3077 nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"]; 3078 3079 uint64_t firstCodeTimeUs = 0; 3080 for (const std::pair<uint64_t, uint64_t>& code : postcode) 3081 { 3082 currentCodeIndex++; 3083 std::string postcodeEntryID = 3084 "B" + std::to_string(bootIndex) + "-" + 3085 std::to_string(currentCodeIndex); // 1 based index in EntryID string 3086 3087 uint64_t usecSinceEpoch = code.first; 3088 uint64_t usTimeOffset = 0; 3089 3090 if (1 == currentCodeIndex) 3091 { // already incremented 3092 firstCodeTimeUs = code.first; 3093 } 3094 else 3095 { 3096 usTimeOffset = code.first - firstCodeTimeUs; 3097 } 3098 3099 // skip if no specific codeIndex is specified and currentCodeIndex does 3100 // not fall between top and skip 3101 if ((codeIndex == 0) && 3102 (currentCodeIndex <= skip || currentCodeIndex > top)) 3103 { 3104 continue; 3105 } 3106 3107 // skip if a specific codeIndex is specified and does not match the 3108 // currentIndex 3109 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 3110 { 3111 // This is done for simplicity. 1st entry is needed to calculate 3112 // time offset. To improve efficiency, one can get to the entry 3113 // directly (possibly with flatmap's nth method) 3114 continue; 3115 } 3116 3117 // currentCodeIndex is within top and skip or equal to specified code 3118 // index 3119 3120 // Get the Created time from the timestamp 3121 std::string entryTimeStr; 3122 entryTimeStr = crow::utility::getDateTime( 3123 static_cast<std::time_t>(usecSinceEpoch / 1000 / 1000)); 3124 3125 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 3126 std::ostringstream hexCode; 3127 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 3128 << code.second; 3129 std::ostringstream timeOffsetStr; 3130 // Set Fixed -Point Notation 3131 timeOffsetStr << std::fixed; 3132 // Set precision to 4 digits 3133 timeOffsetStr << std::setprecision(4); 3134 // Add double to stream 3135 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 3136 std::vector<std::string> messageArgs = { 3137 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 3138 3139 // Get MessageArgs template from message registry 3140 std::string msg; 3141 if (message != nullptr) 3142 { 3143 msg = message->message; 3144 3145 // fill in this post code value 3146 int i = 0; 3147 for (const std::string& messageArg : messageArgs) 3148 { 3149 std::string argStr = "%" + std::to_string(++i); 3150 size_t argPos = msg.find(argStr); 3151 if (argPos != std::string::npos) 3152 { 3153 msg.replace(argPos, argStr.length(), messageArg); 3154 } 3155 } 3156 } 3157 3158 // Get Severity template from message registry 3159 std::string severity; 3160 if (message != nullptr) 3161 { 3162 severity = message->severity; 3163 } 3164 3165 // add to AsyncResp 3166 logEntryArray.push_back({}); 3167 nlohmann::json& bmcLogEntry = logEntryArray.back(); 3168 bmcLogEntry = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 3169 {"@odata.id", "/redfish/v1/Systems/system/LogServices/" 3170 "PostCodes/Entries/" + 3171 postcodeEntryID}, 3172 {"Name", "POST Code Log Entry"}, 3173 {"Id", postcodeEntryID}, 3174 {"Message", std::move(msg)}, 3175 {"MessageId", "OpenBMC.0.1.BIOSPOSTCode"}, 3176 {"MessageArgs", std::move(messageArgs)}, 3177 {"EntryType", "Event"}, 3178 {"Severity", std::move(severity)}, 3179 {"Created", entryTimeStr}}; 3180 } 3181 } 3182 3183 static void getPostCodeForEntry(const std::shared_ptr<AsyncResp>& aResp, 3184 const uint16_t bootIndex, 3185 const uint64_t codeIndex) 3186 { 3187 crow::connections::systemBus->async_method_call( 3188 [aResp, bootIndex, codeIndex]( 3189 const boost::system::error_code ec, 3190 const boost::container::flat_map<uint64_t, uint64_t>& postcode) { 3191 if (ec) 3192 { 3193 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3194 messages::internalError(aResp->res); 3195 return; 3196 } 3197 3198 // skip the empty postcode boots 3199 if (postcode.empty()) 3200 { 3201 return; 3202 } 3203 3204 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 3205 3206 aResp->res.jsonValue["Members@odata.count"] = 3207 aResp->res.jsonValue["Members"].size(); 3208 }, 3209 "xyz.openbmc_project.State.Boot.PostCode", 3210 "/xyz/openbmc_project/State/Boot/PostCode", 3211 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3212 bootIndex); 3213 } 3214 3215 static void getPostCodeForBoot(const std::shared_ptr<AsyncResp>& aResp, 3216 const uint16_t bootIndex, 3217 const uint16_t bootCount, 3218 const uint64_t entryCount, const uint64_t skip, 3219 const uint64_t top) 3220 { 3221 crow::connections::systemBus->async_method_call( 3222 [aResp, bootIndex, bootCount, entryCount, skip, 3223 top](const boost::system::error_code ec, 3224 const boost::container::flat_map<uint64_t, uint64_t>& postcode) { 3225 if (ec) 3226 { 3227 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3228 messages::internalError(aResp->res); 3229 return; 3230 } 3231 3232 uint64_t endCount = entryCount; 3233 if (!postcode.empty()) 3234 { 3235 endCount = entryCount + postcode.size(); 3236 3237 if ((skip < endCount) && ((top + skip) > entryCount)) 3238 { 3239 uint64_t thisBootSkip = 3240 std::max(skip, entryCount) - entryCount; 3241 uint64_t thisBootTop = 3242 std::min(top + skip, endCount) - entryCount; 3243 3244 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 3245 thisBootSkip, thisBootTop); 3246 } 3247 aResp->res.jsonValue["Members@odata.count"] = endCount; 3248 } 3249 3250 // continue to previous bootIndex 3251 if (bootIndex < bootCount) 3252 { 3253 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 3254 bootCount, endCount, skip, top); 3255 } 3256 else 3257 { 3258 aResp->res.jsonValue["Members@odata.nextLink"] = 3259 "/redfish/v1/Systems/system/LogServices/PostCodes/" 3260 "Entries?$skip=" + 3261 std::to_string(skip + top); 3262 } 3263 }, 3264 "xyz.openbmc_project.State.Boot.PostCode", 3265 "/xyz/openbmc_project/State/Boot/PostCode", 3266 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3267 bootIndex); 3268 } 3269 3270 static void getCurrentBootNumber(const std::shared_ptr<AsyncResp>& aResp, 3271 const uint64_t skip, const uint64_t top) 3272 { 3273 uint64_t entryCount = 0; 3274 crow::connections::systemBus->async_method_call( 3275 [aResp, entryCount, skip, 3276 top](const boost::system::error_code ec, 3277 const std::variant<uint16_t>& bootCount) { 3278 if (ec) 3279 { 3280 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3281 messages::internalError(aResp->res); 3282 return; 3283 } 3284 auto pVal = std::get_if<uint16_t>(&bootCount); 3285 if (pVal) 3286 { 3287 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top); 3288 } 3289 else 3290 { 3291 BMCWEB_LOG_DEBUG << "Post code boot index failed."; 3292 } 3293 }, 3294 "xyz.openbmc_project.State.Boot.PostCode", 3295 "/xyz/openbmc_project/State/Boot/PostCode", 3296 "org.freedesktop.DBus.Properties", "Get", 3297 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount"); 3298 } 3299 3300 class PostCodesEntryCollection : public Node 3301 { 3302 public: 3303 PostCodesEntryCollection(App& app) : 3304 Node(app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 3305 { 3306 entityPrivileges = { 3307 {boost::beast::http::verb::get, {{"Login"}}}, 3308 {boost::beast::http::verb::head, {{"Login"}}}, 3309 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 3310 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 3311 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 3312 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 3313 } 3314 3315 private: 3316 void doGet(crow::Response& res, const crow::Request& req, 3317 const std::vector<std::string>&) override 3318 { 3319 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3320 3321 asyncResp->res.jsonValue["@odata.type"] = 3322 "#LogEntryCollection.LogEntryCollection"; 3323 asyncResp->res.jsonValue["@odata.id"] = 3324 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3325 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3326 asyncResp->res.jsonValue["Description"] = 3327 "Collection of POST Code Log Entries"; 3328 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3329 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3330 3331 uint64_t skip = 0; 3332 uint64_t top = maxEntriesPerPage; // Show max entries by default 3333 if (!getSkipParam(asyncResp->res, req, skip)) 3334 { 3335 return; 3336 } 3337 if (!getTopParam(asyncResp->res, req, top)) 3338 { 3339 return; 3340 } 3341 getCurrentBootNumber(asyncResp, skip, top); 3342 } 3343 }; 3344 3345 class PostCodesEntry : public Node 3346 { 3347 public: 3348 PostCodesEntry(App& app) : 3349 Node(app, 3350 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/", 3351 std::string()) 3352 { 3353 entityPrivileges = { 3354 {boost::beast::http::verb::get, {{"Login"}}}, 3355 {boost::beast::http::verb::head, {{"Login"}}}, 3356 {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, 3357 {boost::beast::http::verb::put, {{"ConfigureManager"}}}, 3358 {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, 3359 {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; 3360 } 3361 3362 private: 3363 void doGet(crow::Response& res, const crow::Request&, 3364 const std::vector<std::string>& params) override 3365 { 3366 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 3367 if (params.size() != 1) 3368 { 3369 messages::internalError(asyncResp->res); 3370 return; 3371 } 3372 3373 const std::string& targetID = params[0]; 3374 3375 size_t bootPos = targetID.find('B'); 3376 if (bootPos == std::string::npos) 3377 { 3378 // Requested ID was not found 3379 messages::resourceMissingAtURI(asyncResp->res, targetID); 3380 return; 3381 } 3382 std::string_view bootIndexStr(targetID); 3383 bootIndexStr.remove_prefix(bootPos + 1); 3384 uint16_t bootIndex = 0; 3385 uint64_t codeIndex = 0; 3386 size_t dashPos = bootIndexStr.find('-'); 3387 3388 if (dashPos == std::string::npos) 3389 { 3390 return; 3391 } 3392 std::string_view codeIndexStr(bootIndexStr); 3393 bootIndexStr.remove_suffix(dashPos); 3394 codeIndexStr.remove_prefix(dashPos + 1); 3395 3396 bootIndex = static_cast<uint16_t>( 3397 strtoul(std::string(bootIndexStr).c_str(), nullptr, 0)); 3398 codeIndex = strtoul(std::string(codeIndexStr).c_str(), nullptr, 0); 3399 if (bootIndex == 0 || codeIndex == 0) 3400 { 3401 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 3402 << params[0]; 3403 } 3404 3405 asyncResp->res.jsonValue["@odata.type"] = "#LogEntry.v1_4_0.LogEntry"; 3406 asyncResp->res.jsonValue["@odata.id"] = 3407 "/redfish/v1/Systems/system/LogServices/PostCodes/" 3408 "Entries"; 3409 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3410 asyncResp->res.jsonValue["Description"] = 3411 "Collection of POST Code Log Entries"; 3412 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3413 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3414 3415 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 3416 } 3417 }; 3418 3419 } // namespace redfish 3420