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