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