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