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