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