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