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