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