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