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