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