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