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