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_7_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_7_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 997 std::pair<std::string, std::string> redfishDateTimeOffset = 998 crow::utility::getDateTimeOffsetNow(); 999 1000 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 1001 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 1002 redfishDateTimeOffset.second; 1003 1004 asyncResp->res.jsonValue["Entries"] = { 1005 {"@odata.id", 1006 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"}}; 1007 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 1008 1009 {"target", "/redfish/v1/Systems/system/LogServices/EventLog/" 1010 "Actions/LogService.ClearLog"}}; 1011 }); 1012 } 1013 1014 inline void requestRoutesJournalEventLogClear(App& app) 1015 { 1016 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 1017 "LogService.ClearLog/") 1018 .privileges({{"ConfigureComponents"}}) 1019 .methods(boost::beast::http::verb::post)( 1020 [](const crow::Request&, 1021 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1022 // Clear the EventLog by deleting the log files 1023 std::vector<std::filesystem::path> redfishLogFiles; 1024 if (getRedfishLogFiles(redfishLogFiles)) 1025 { 1026 for (const std::filesystem::path& file : redfishLogFiles) 1027 { 1028 std::error_code ec; 1029 std::filesystem::remove(file, ec); 1030 } 1031 } 1032 1033 // Reload rsyslog so it knows to start new log files 1034 crow::connections::systemBus->async_method_call( 1035 [asyncResp](const boost::system::error_code ec) { 1036 if (ec) 1037 { 1038 BMCWEB_LOG_ERROR << "Failed to reload rsyslog: " 1039 << ec; 1040 messages::internalError(asyncResp->res); 1041 return; 1042 } 1043 1044 messages::success(asyncResp->res); 1045 }, 1046 "org.freedesktop.systemd1", "/org/freedesktop/systemd1", 1047 "org.freedesktop.systemd1.Manager", "ReloadUnit", 1048 "rsyslog.service", "replace"); 1049 }); 1050 } 1051 1052 static int fillEventLogEntryJson(const std::string& logEntryID, 1053 const std::string& logEntry, 1054 nlohmann::json& logEntryJson) 1055 { 1056 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 1057 // First get the Timestamp 1058 size_t space = logEntry.find_first_of(' '); 1059 if (space == std::string::npos) 1060 { 1061 return 1; 1062 } 1063 std::string timestamp = logEntry.substr(0, space); 1064 // Then get the log contents 1065 size_t entryStart = logEntry.find_first_not_of(' ', space); 1066 if (entryStart == std::string::npos) 1067 { 1068 return 1; 1069 } 1070 std::string_view entry(logEntry); 1071 entry.remove_prefix(entryStart); 1072 // Use split to separate the entry into its fields 1073 std::vector<std::string> logEntryFields; 1074 boost::split(logEntryFields, entry, boost::is_any_of(","), 1075 boost::token_compress_on); 1076 // We need at least a MessageId to be valid 1077 if (logEntryFields.size() < 1) 1078 { 1079 return 1; 1080 } 1081 std::string& messageID = logEntryFields[0]; 1082 1083 // Get the Message from the MessageRegistry 1084 const message_registries::Message* message = 1085 message_registries::getMessage(messageID); 1086 1087 std::string msg; 1088 std::string severity; 1089 if (message != nullptr) 1090 { 1091 msg = message->message; 1092 severity = message->severity; 1093 } 1094 1095 // Get the MessageArgs from the log if there are any 1096 boost::beast::span<std::string> messageArgs; 1097 if (logEntryFields.size() > 1) 1098 { 1099 std::string& messageArgsStart = logEntryFields[1]; 1100 // If the first string is empty, assume there are no MessageArgs 1101 std::size_t messageArgsSize = 0; 1102 if (!messageArgsStart.empty()) 1103 { 1104 messageArgsSize = logEntryFields.size() - 1; 1105 } 1106 1107 messageArgs = {&messageArgsStart, messageArgsSize}; 1108 1109 // Fill the MessageArgs into the Message 1110 int i = 0; 1111 for (const std::string& messageArg : messageArgs) 1112 { 1113 std::string argStr = "%" + std::to_string(++i); 1114 size_t argPos = msg.find(argStr); 1115 if (argPos != std::string::npos) 1116 { 1117 msg.replace(argPos, argStr.length(), messageArg); 1118 } 1119 } 1120 } 1121 1122 // Get the Created time from the timestamp. The log timestamp is in RFC3339 1123 // format which matches the Redfish format except for the fractional seconds 1124 // between the '.' and the '+', so just remove them. 1125 std::size_t dot = timestamp.find_first_of('.'); 1126 std::size_t plus = timestamp.find_first_of('+'); 1127 if (dot != std::string::npos && plus != std::string::npos) 1128 { 1129 timestamp.erase(dot, plus - dot); 1130 } 1131 1132 // Fill in the log entry with the gathered data 1133 logEntryJson = { 1134 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1135 {"@odata.id", 1136 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" + 1137 logEntryID}, 1138 {"Name", "System Event Log Entry"}, 1139 {"Id", logEntryID}, 1140 {"Message", std::move(msg)}, 1141 {"MessageId", std::move(messageID)}, 1142 {"MessageArgs", messageArgs}, 1143 {"EntryType", "Event"}, 1144 {"Severity", std::move(severity)}, 1145 {"Created", std::move(timestamp)}}; 1146 return 0; 1147 } 1148 1149 inline void requestRoutesJournalEventLogEntryCollection(App& app) 1150 { 1151 BMCWEB_ROUTE(app, 1152 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 1153 .privileges({{"Login"}}) 1154 .methods(boost::beast::http::verb::get)( 1155 [](const crow::Request& req, 1156 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1157 uint64_t skip = 0; 1158 uint64_t top = maxEntriesPerPage; // Show max entries by default 1159 if (!getSkipParam(asyncResp, req, skip)) 1160 { 1161 return; 1162 } 1163 if (!getTopParam(asyncResp, req, top)) 1164 { 1165 return; 1166 } 1167 // Collections don't include the static data added by SubRoute 1168 // because it has a duplicate entry for members 1169 asyncResp->res.jsonValue["@odata.type"] = 1170 "#LogEntryCollection.LogEntryCollection"; 1171 asyncResp->res.jsonValue["@odata.id"] = 1172 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 1173 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1174 asyncResp->res.jsonValue["Description"] = 1175 "Collection of System Event Log Entries"; 1176 1177 nlohmann::json& logEntryArray = 1178 asyncResp->res.jsonValue["Members"]; 1179 logEntryArray = nlohmann::json::array(); 1180 // Go through the log files and create a unique ID for each 1181 // entry 1182 std::vector<std::filesystem::path> redfishLogFiles; 1183 getRedfishLogFiles(redfishLogFiles); 1184 uint64_t entryCount = 0; 1185 std::string logEntry; 1186 1187 // Oldest logs are in the last file, so start there and loop 1188 // backwards 1189 for (auto it = redfishLogFiles.rbegin(); 1190 it < redfishLogFiles.rend(); it++) 1191 { 1192 std::ifstream logStream(*it); 1193 if (!logStream.is_open()) 1194 { 1195 continue; 1196 } 1197 1198 // Reset the unique ID on the first entry 1199 bool firstEntry = true; 1200 while (std::getline(logStream, logEntry)) 1201 { 1202 entryCount++; 1203 // Handle paging using skip (number of entries to skip 1204 // from the start) and top (number of entries to 1205 // display) 1206 if (entryCount <= skip || entryCount > skip + top) 1207 { 1208 continue; 1209 } 1210 1211 std::string idStr; 1212 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1213 { 1214 continue; 1215 } 1216 1217 if (firstEntry) 1218 { 1219 firstEntry = false; 1220 } 1221 1222 logEntryArray.push_back({}); 1223 nlohmann::json& bmcLogEntry = logEntryArray.back(); 1224 if (fillEventLogEntryJson(idStr, logEntry, 1225 bmcLogEntry) != 0) 1226 { 1227 messages::internalError(asyncResp->res); 1228 return; 1229 } 1230 } 1231 } 1232 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 1233 if (skip + top < entryCount) 1234 { 1235 asyncResp->res.jsonValue["Members@odata.nextLink"] = 1236 "/redfish/v1/Systems/system/LogServices/EventLog/" 1237 "Entries?$skip=" + 1238 std::to_string(skip + top); 1239 } 1240 }); 1241 } 1242 1243 inline void requestRoutesJournalEventLogEntry(App& app) 1244 { 1245 BMCWEB_ROUTE( 1246 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1247 .privileges({{"Login"}}) 1248 .methods(boost::beast::http::verb::get)( 1249 [](const crow::Request&, 1250 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1251 const std::string& param) { 1252 const std::string& targetID = param; 1253 1254 // Go through the log files and check the unique ID for each 1255 // entry to find the target entry 1256 std::vector<std::filesystem::path> redfishLogFiles; 1257 getRedfishLogFiles(redfishLogFiles); 1258 std::string logEntry; 1259 1260 // Oldest logs are in the last file, so start there and loop 1261 // backwards 1262 for (auto it = redfishLogFiles.rbegin(); 1263 it < redfishLogFiles.rend(); it++) 1264 { 1265 std::ifstream logStream(*it); 1266 if (!logStream.is_open()) 1267 { 1268 continue; 1269 } 1270 1271 // Reset the unique ID on the first entry 1272 bool firstEntry = true; 1273 while (std::getline(logStream, logEntry)) 1274 { 1275 std::string idStr; 1276 if (!getUniqueEntryID(logEntry, idStr, firstEntry)) 1277 { 1278 continue; 1279 } 1280 1281 if (firstEntry) 1282 { 1283 firstEntry = false; 1284 } 1285 1286 if (idStr == targetID) 1287 { 1288 if (fillEventLogEntryJson( 1289 idStr, logEntry, 1290 asyncResp->res.jsonValue) != 0) 1291 { 1292 messages::internalError(asyncResp->res); 1293 return; 1294 } 1295 return; 1296 } 1297 } 1298 } 1299 // Requested ID was not found 1300 messages::resourceMissingAtURI(asyncResp->res, targetID); 1301 }); 1302 } 1303 1304 inline void requestRoutesDBusEventLogEntryCollection(App& app) 1305 { 1306 BMCWEB_ROUTE(app, 1307 "/redfish/v1/Systems/system/LogServices/EventLog/Entries/") 1308 .privileges({{"Login"}}) 1309 .methods( 1310 boost::beast::http::verb:: 1311 get)([](const crow::Request&, 1312 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1313 // Collections don't include the static data added by SubRoute 1314 // because it has a duplicate entry for members 1315 asyncResp->res.jsonValue["@odata.type"] = 1316 "#LogEntryCollection.LogEntryCollection"; 1317 asyncResp->res.jsonValue["@odata.id"] = 1318 "/redfish/v1/Systems/system/LogServices/EventLog/Entries"; 1319 asyncResp->res.jsonValue["Name"] = "System Event Log Entries"; 1320 asyncResp->res.jsonValue["Description"] = 1321 "Collection of System Event Log Entries"; 1322 1323 // DBus implementation of EventLog/Entries 1324 // Make call to Logging Service to find all log entry objects 1325 crow::connections::systemBus->async_method_call( 1326 [asyncResp](const boost::system::error_code ec, 1327 GetManagedObjectsType& resp) { 1328 if (ec) 1329 { 1330 // TODO Handle for specific error code 1331 BMCWEB_LOG_ERROR 1332 << "getLogEntriesIfaceData resp_handler got error " 1333 << ec; 1334 messages::internalError(asyncResp->res); 1335 return; 1336 } 1337 nlohmann::json& entriesArray = 1338 asyncResp->res.jsonValue["Members"]; 1339 entriesArray = nlohmann::json::array(); 1340 for (auto& objectPath : resp) 1341 { 1342 uint32_t* id = nullptr; 1343 std::time_t timestamp{}; 1344 std::time_t updateTimestamp{}; 1345 std::string* severity = nullptr; 1346 std::string* message = nullptr; 1347 std::string* filePath = nullptr; 1348 bool resolved = false; 1349 for (auto& interfaceMap : objectPath.second) 1350 { 1351 if (interfaceMap.first == 1352 "xyz.openbmc_project.Logging.Entry") 1353 { 1354 for (auto& propertyMap : interfaceMap.second) 1355 { 1356 if (propertyMap.first == "Id") 1357 { 1358 id = std::get_if<uint32_t>( 1359 &propertyMap.second); 1360 } 1361 else if (propertyMap.first == "Timestamp") 1362 { 1363 const uint64_t* millisTimeStamp = 1364 std::get_if<uint64_t>( 1365 &propertyMap.second); 1366 if (millisTimeStamp != nullptr) 1367 { 1368 timestamp = 1369 crow::utility::getTimestamp( 1370 *millisTimeStamp); 1371 } 1372 } 1373 else if (propertyMap.first == 1374 "UpdateTimestamp") 1375 { 1376 const uint64_t* millisTimeStamp = 1377 std::get_if<uint64_t>( 1378 &propertyMap.second); 1379 if (millisTimeStamp != nullptr) 1380 { 1381 updateTimestamp = 1382 crow::utility::getTimestamp( 1383 *millisTimeStamp); 1384 } 1385 } 1386 else if (propertyMap.first == "Severity") 1387 { 1388 severity = std::get_if<std::string>( 1389 &propertyMap.second); 1390 } 1391 else if (propertyMap.first == "Message") 1392 { 1393 message = std::get_if<std::string>( 1394 &propertyMap.second); 1395 } 1396 else if (propertyMap.first == "Resolved") 1397 { 1398 bool* resolveptr = std::get_if<bool>( 1399 &propertyMap.second); 1400 if (resolveptr == nullptr) 1401 { 1402 messages::internalError( 1403 asyncResp->res); 1404 return; 1405 } 1406 resolved = *resolveptr; 1407 } 1408 } 1409 if (id == nullptr || message == nullptr || 1410 severity == nullptr) 1411 { 1412 messages::internalError(asyncResp->res); 1413 return; 1414 } 1415 } 1416 else if (interfaceMap.first == 1417 "xyz.openbmc_project.Common.FilePath") 1418 { 1419 for (auto& propertyMap : interfaceMap.second) 1420 { 1421 if (propertyMap.first == "Path") 1422 { 1423 filePath = std::get_if<std::string>( 1424 &propertyMap.second); 1425 } 1426 } 1427 } 1428 } 1429 // Object path without the 1430 // xyz.openbmc_project.Logging.Entry interface, ignore 1431 // and continue. 1432 if (id == nullptr || message == nullptr || 1433 severity == nullptr) 1434 { 1435 continue; 1436 } 1437 entriesArray.push_back({}); 1438 nlohmann::json& thisEntry = entriesArray.back(); 1439 thisEntry["@odata.type"] = "#LogEntry.v1_8_0.LogEntry"; 1440 thisEntry["@odata.id"] = 1441 "/redfish/v1/Systems/system/" 1442 "LogServices/EventLog/Entries/" + 1443 std::to_string(*id); 1444 thisEntry["Name"] = "System Event Log Entry"; 1445 thisEntry["Id"] = std::to_string(*id); 1446 thisEntry["Message"] = *message; 1447 thisEntry["Resolved"] = resolved; 1448 thisEntry["EntryType"] = "Event"; 1449 thisEntry["Severity"] = 1450 translateSeverityDbusToRedfish(*severity); 1451 thisEntry["Created"] = 1452 crow::utility::getDateTime(timestamp); 1453 thisEntry["Modified"] = 1454 crow::utility::getDateTime(updateTimestamp); 1455 if (filePath != nullptr) 1456 { 1457 thisEntry["AdditionalDataURI"] = 1458 "/redfish/v1/Systems/system/LogServices/" 1459 "EventLog/" 1460 "Entries/" + 1461 std::to_string(*id) + "/attachment"; 1462 } 1463 } 1464 std::sort(entriesArray.begin(), entriesArray.end(), 1465 [](const nlohmann::json& left, 1466 const nlohmann::json& right) { 1467 return (left["Id"] <= right["Id"]); 1468 }); 1469 asyncResp->res.jsonValue["Members@odata.count"] = 1470 entriesArray.size(); 1471 }, 1472 "xyz.openbmc_project.Logging", "/xyz/openbmc_project/logging", 1473 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1474 }); 1475 } 1476 1477 inline void requestRoutesDBusEventLogEntry(App& app) 1478 { 1479 BMCWEB_ROUTE( 1480 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1481 .privileges({{"Login"}}) 1482 .methods(boost::beast::http::verb::get)( 1483 [](const crow::Request&, 1484 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1485 const std::string& param) 1486 1487 { 1488 std::string entryID = param; 1489 dbus::utility::escapePathForDbus(entryID); 1490 1491 // DBus implementation of EventLog/Entries 1492 // Make call to Logging Service to find all log entry objects 1493 crow::connections::systemBus->async_method_call( 1494 [asyncResp, entryID](const boost::system::error_code ec, 1495 GetManagedPropertyType& resp) { 1496 if (ec.value() == EBADR) 1497 { 1498 messages::resourceNotFound( 1499 asyncResp->res, "EventLogEntry", entryID); 1500 return; 1501 } 1502 if (ec) 1503 { 1504 BMCWEB_LOG_ERROR << "EventLogEntry (DBus) " 1505 "resp_handler got error " 1506 << ec; 1507 messages::internalError(asyncResp->res); 1508 return; 1509 } 1510 uint32_t* id = nullptr; 1511 std::time_t timestamp{}; 1512 std::time_t updateTimestamp{}; 1513 std::string* severity = nullptr; 1514 std::string* message = nullptr; 1515 std::string* filePath = nullptr; 1516 bool resolved = false; 1517 1518 for (auto& propertyMap : resp) 1519 { 1520 if (propertyMap.first == "Id") 1521 { 1522 id = std::get_if<uint32_t>(&propertyMap.second); 1523 } 1524 else if (propertyMap.first == "Timestamp") 1525 { 1526 const uint64_t* millisTimeStamp = 1527 std::get_if<uint64_t>(&propertyMap.second); 1528 if (millisTimeStamp != nullptr) 1529 { 1530 timestamp = crow::utility::getTimestamp( 1531 *millisTimeStamp); 1532 } 1533 } 1534 else if (propertyMap.first == "UpdateTimestamp") 1535 { 1536 const uint64_t* millisTimeStamp = 1537 std::get_if<uint64_t>(&propertyMap.second); 1538 if (millisTimeStamp != nullptr) 1539 { 1540 updateTimestamp = 1541 crow::utility::getTimestamp( 1542 *millisTimeStamp); 1543 } 1544 } 1545 else if (propertyMap.first == "Severity") 1546 { 1547 severity = std::get_if<std::string>( 1548 &propertyMap.second); 1549 } 1550 else if (propertyMap.first == "Message") 1551 { 1552 message = std::get_if<std::string>( 1553 &propertyMap.second); 1554 } 1555 else if (propertyMap.first == "Resolved") 1556 { 1557 bool* resolveptr = 1558 std::get_if<bool>(&propertyMap.second); 1559 if (resolveptr == nullptr) 1560 { 1561 messages::internalError(asyncResp->res); 1562 return; 1563 } 1564 resolved = *resolveptr; 1565 } 1566 else if (propertyMap.first == "Path") 1567 { 1568 filePath = std::get_if<std::string>( 1569 &propertyMap.second); 1570 } 1571 } 1572 if (id == nullptr || message == nullptr || 1573 severity == nullptr) 1574 { 1575 messages::internalError(asyncResp->res); 1576 return; 1577 } 1578 asyncResp->res.jsonValue["@odata.type"] = 1579 "#LogEntry.v1_8_0.LogEntry"; 1580 asyncResp->res.jsonValue["@odata.id"] = 1581 "/redfish/v1/Systems/system/LogServices/EventLog/" 1582 "Entries/" + 1583 std::to_string(*id); 1584 asyncResp->res.jsonValue["Name"] = 1585 "System Event Log Entry"; 1586 asyncResp->res.jsonValue["Id"] = std::to_string(*id); 1587 asyncResp->res.jsonValue["Message"] = *message; 1588 asyncResp->res.jsonValue["Resolved"] = resolved; 1589 asyncResp->res.jsonValue["EntryType"] = "Event"; 1590 asyncResp->res.jsonValue["Severity"] = 1591 translateSeverityDbusToRedfish(*severity); 1592 asyncResp->res.jsonValue["Created"] = 1593 crow::utility::getDateTime(timestamp); 1594 asyncResp->res.jsonValue["Modified"] = 1595 crow::utility::getDateTime(updateTimestamp); 1596 if (filePath != nullptr) 1597 { 1598 asyncResp->res.jsonValue["AdditionalDataURI"] = 1599 "/redfish/v1/Systems/system/LogServices/" 1600 "EventLog/" 1601 "attachment/" + 1602 std::to_string(*id); 1603 } 1604 }, 1605 "xyz.openbmc_project.Logging", 1606 "/xyz/openbmc_project/logging/entry/" + entryID, 1607 "org.freedesktop.DBus.Properties", "GetAll", ""); 1608 }); 1609 1610 BMCWEB_ROUTE( 1611 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1612 .privileges({{"ConfigureManager"}}) 1613 .methods(boost::beast::http::verb::patch)( 1614 [](const crow::Request& req, 1615 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1616 const std::string& entryId) { 1617 std::optional<bool> resolved; 1618 1619 if (!json_util::readJson(req, asyncResp->res, "Resolved", 1620 resolved)) 1621 { 1622 return; 1623 } 1624 BMCWEB_LOG_DEBUG << "Set Resolved"; 1625 1626 crow::connections::systemBus->async_method_call( 1627 [asyncResp, resolved, 1628 entryId](const boost::system::error_code ec) { 1629 if (ec) 1630 { 1631 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1632 messages::internalError(asyncResp->res); 1633 return; 1634 } 1635 }, 1636 "xyz.openbmc_project.Logging", 1637 "/xyz/openbmc_project/logging/entry/" + entryId, 1638 "org.freedesktop.DBus.Properties", "Set", 1639 "xyz.openbmc_project.Logging.Entry", "Resolved", 1640 std::variant<bool>(*resolved)); 1641 }); 1642 1643 BMCWEB_ROUTE( 1644 app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/<str>/") 1645 .privileges({{"ConfigureManager"}}) 1646 .methods(boost::beast::http::verb::delete_)( 1647 [](const crow::Request&, 1648 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1649 const std::string& param) 1650 1651 { 1652 BMCWEB_LOG_DEBUG << "Do delete single event entries."; 1653 1654 std::string entryID = param; 1655 1656 dbus::utility::escapePathForDbus(entryID); 1657 1658 // Process response from Logging service. 1659 auto respHandler = [asyncResp, entryID]( 1660 const boost::system::error_code ec) { 1661 BMCWEB_LOG_DEBUG 1662 << "EventLogEntry (DBus) doDelete callback: Done"; 1663 if (ec) 1664 { 1665 if (ec.value() == EBADR) 1666 { 1667 messages::resourceNotFound(asyncResp->res, 1668 "LogEntry", entryID); 1669 return; 1670 } 1671 // TODO Handle for specific error code 1672 BMCWEB_LOG_ERROR << "EventLogEntry (DBus) doDelete " 1673 "respHandler got error " 1674 << ec; 1675 asyncResp->res.result( 1676 boost::beast::http::status::internal_server_error); 1677 return; 1678 } 1679 1680 asyncResp->res.result(boost::beast::http::status::ok); 1681 }; 1682 1683 // Make call to Logging service to request Delete Log 1684 crow::connections::systemBus->async_method_call( 1685 respHandler, "xyz.openbmc_project.Logging", 1686 "/xyz/openbmc_project/logging/entry/" + entryID, 1687 "xyz.openbmc_project.Object.Delete", "Delete"); 1688 }); 1689 } 1690 1691 inline void requestRoutesDBusEventLogEntryDownload(App& app) 1692 { 1693 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Entries/" 1694 "<str>/attachment") 1695 .privileges({{"Login"}}) 1696 .methods(boost::beast::http::verb::get)( 1697 [](const crow::Request& req, 1698 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1699 const std::string& param) 1700 1701 { 1702 std::string_view acceptHeader = req.getHeaderValue("Accept"); 1703 // The iterators in boost/http/rfc7230.hpp end the string if '/' 1704 // is found, so replace it with arbitrary character '|' which is 1705 // not part of the Accept header syntax. 1706 std::string acceptStr = boost::replace_all_copy( 1707 std::string(acceptHeader), "/", "|"); 1708 boost::beast::http::ext_list acceptTypes{acceptStr}; 1709 bool supported = false; 1710 for (const auto& type : acceptTypes) 1711 { 1712 if ((type.first == "*|*") || 1713 (type.first == "application|octet-stream")) 1714 { 1715 supported = true; 1716 break; 1717 } 1718 } 1719 if (!supported) 1720 { 1721 asyncResp->res.result( 1722 boost::beast::http::status::bad_request); 1723 return; 1724 } 1725 1726 std::string entryID = param; 1727 dbus::utility::escapePathForDbus(entryID); 1728 1729 crow::connections::systemBus->async_method_call( 1730 [asyncResp, 1731 entryID](const boost::system::error_code ec, 1732 const sdbusplus::message::unix_fd& unixfd) { 1733 if (ec.value() == EBADR) 1734 { 1735 messages::resourceNotFound( 1736 asyncResp->res, "EventLogAttachment", entryID); 1737 return; 1738 } 1739 if (ec) 1740 { 1741 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 1742 messages::internalError(asyncResp->res); 1743 return; 1744 } 1745 1746 int fd = -1; 1747 fd = dup(unixfd); 1748 if (fd == -1) 1749 { 1750 messages::internalError(asyncResp->res); 1751 return; 1752 } 1753 1754 long long int size = lseek(fd, 0, SEEK_END); 1755 if (size == -1) 1756 { 1757 messages::internalError(asyncResp->res); 1758 return; 1759 } 1760 1761 // Arbitrary max size of 64kb 1762 constexpr int maxFileSize = 65536; 1763 if (size > maxFileSize) 1764 { 1765 BMCWEB_LOG_ERROR 1766 << "File size exceeds maximum allowed size of " 1767 << maxFileSize; 1768 messages::internalError(asyncResp->res); 1769 return; 1770 } 1771 std::vector<char> data(static_cast<size_t>(size)); 1772 long long int rc = lseek(fd, 0, SEEK_SET); 1773 if (rc == -1) 1774 { 1775 messages::internalError(asyncResp->res); 1776 return; 1777 } 1778 rc = read(fd, data.data(), data.size()); 1779 if ((rc == -1) || (rc != size)) 1780 { 1781 messages::internalError(asyncResp->res); 1782 return; 1783 } 1784 close(fd); 1785 1786 std::string_view strData(data.data(), data.size()); 1787 std::string output = 1788 crow::utility::base64encode(strData); 1789 1790 asyncResp->res.addHeader("Content-Type", 1791 "application/octet-stream"); 1792 asyncResp->res.addHeader("Content-Transfer-Encoding", 1793 "Base64"); 1794 asyncResp->res.body() = std::move(output); 1795 }, 1796 "xyz.openbmc_project.Logging", 1797 "/xyz/openbmc_project/logging/entry/" + entryID, 1798 "xyz.openbmc_project.Logging.Entry", "GetEntry"); 1799 }); 1800 } 1801 1802 inline void requestRoutesBMCLogServiceCollection(App& app) 1803 { 1804 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/") 1805 .privileges({{"Login"}}) 1806 .methods(boost::beast::http::verb::get)( 1807 [](const crow::Request&, 1808 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1809 // Collections don't include the static data added by SubRoute 1810 // because it has a duplicate entry for members 1811 asyncResp->res.jsonValue["@odata.type"] = 1812 "#LogServiceCollection.LogServiceCollection"; 1813 asyncResp->res.jsonValue["@odata.id"] = 1814 "/redfish/v1/Managers/bmc/LogServices"; 1815 asyncResp->res.jsonValue["Name"] = 1816 "Open BMC Log Services Collection"; 1817 asyncResp->res.jsonValue["Description"] = 1818 "Collection of LogServices for this Manager"; 1819 nlohmann::json& logServiceArray = 1820 asyncResp->res.jsonValue["Members"]; 1821 logServiceArray = nlohmann::json::array(); 1822 #ifdef BMCWEB_ENABLE_REDFISH_DUMP_LOG 1823 logServiceArray.push_back( 1824 {{"@odata.id", 1825 "/redfish/v1/Managers/bmc/LogServices/Dump"}}); 1826 #endif 1827 #ifdef BMCWEB_ENABLE_REDFISH_BMC_JOURNAL 1828 logServiceArray.push_back( 1829 {{"@odata.id", 1830 "/redfish/v1/Managers/bmc/LogServices/Journal"}}); 1831 #endif 1832 asyncResp->res.jsonValue["Members@odata.count"] = 1833 logServiceArray.size(); 1834 }); 1835 } 1836 1837 inline void requestRoutesBMCJournalLogService(App& app) 1838 { 1839 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/") 1840 .privileges({{"Login"}}) 1841 .methods(boost::beast::http::verb::get)( 1842 [](const crow::Request&, 1843 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1844 1845 { 1846 asyncResp->res.jsonValue["@odata.type"] = 1847 "#LogService.v1_1_0.LogService"; 1848 asyncResp->res.jsonValue["@odata.id"] = 1849 "/redfish/v1/Managers/bmc/LogServices/Journal"; 1850 asyncResp->res.jsonValue["Name"] = 1851 "Open BMC Journal Log Service"; 1852 asyncResp->res.jsonValue["Description"] = 1853 "BMC Journal Log Service"; 1854 asyncResp->res.jsonValue["Id"] = "BMC Journal"; 1855 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 1856 1857 std::pair<std::string, std::string> redfishDateTimeOffset = 1858 crow::utility::getDateTimeOffsetNow(); 1859 asyncResp->res.jsonValue["DateTime"] = 1860 redfishDateTimeOffset.first; 1861 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 1862 redfishDateTimeOffset.second; 1863 1864 asyncResp->res.jsonValue["Entries"] = { 1865 {"@odata.id", 1866 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"}}; 1867 }); 1868 } 1869 1870 static int fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, 1871 sd_journal* journal, 1872 nlohmann::json& bmcJournalLogEntryJson) 1873 { 1874 // Get the Log Entry contents 1875 int ret = 0; 1876 1877 std::string message; 1878 std::string_view syslogID; 1879 ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); 1880 if (ret < 0) 1881 { 1882 BMCWEB_LOG_ERROR << "Failed to read SYSLOG_IDENTIFIER field: " 1883 << strerror(-ret); 1884 } 1885 if (!syslogID.empty()) 1886 { 1887 message += std::string(syslogID) + ": "; 1888 } 1889 1890 std::string_view msg; 1891 ret = getJournalMetadata(journal, "MESSAGE", msg); 1892 if (ret < 0) 1893 { 1894 BMCWEB_LOG_ERROR << "Failed to read MESSAGE field: " << strerror(-ret); 1895 return 1; 1896 } 1897 message += std::string(msg); 1898 1899 // Get the severity from the PRIORITY field 1900 long int severity = 8; // Default to an invalid priority 1901 ret = getJournalMetadata(journal, "PRIORITY", 10, severity); 1902 if (ret < 0) 1903 { 1904 BMCWEB_LOG_ERROR << "Failed to read PRIORITY field: " << strerror(-ret); 1905 } 1906 1907 // Get the Created time from the timestamp 1908 std::string entryTimeStr; 1909 if (!getEntryTimestamp(journal, entryTimeStr)) 1910 { 1911 return 1; 1912 } 1913 1914 // Fill in the log entry with the gathered data 1915 bmcJournalLogEntryJson = { 1916 {"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 1917 {"@odata.id", "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/" + 1918 bmcJournalLogEntryID}, 1919 {"Name", "BMC Journal Entry"}, 1920 {"Id", bmcJournalLogEntryID}, 1921 {"Message", std::move(message)}, 1922 {"EntryType", "Oem"}, 1923 {"Severity", severity <= 2 ? "Critical" 1924 : severity <= 4 ? "Warning" 1925 : "OK"}, 1926 {"OemRecordFormat", "BMC Journal Entry"}, 1927 {"Created", std::move(entryTimeStr)}}; 1928 return 0; 1929 } 1930 1931 inline void requestRoutesBMCJournalLogEntryCollection(App& app) 1932 { 1933 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/") 1934 .privileges({{"Login"}}) 1935 .methods(boost::beast::http::verb::get)( 1936 [](const crow::Request& req, 1937 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1938 static constexpr const long maxEntriesPerPage = 1000; 1939 uint64_t skip = 0; 1940 uint64_t top = maxEntriesPerPage; // Show max entries by default 1941 if (!getSkipParam(asyncResp, req, skip)) 1942 { 1943 return; 1944 } 1945 if (!getTopParam(asyncResp, req, top)) 1946 { 1947 return; 1948 } 1949 // Collections don't include the static data added by SubRoute 1950 // because it has a duplicate entry for members 1951 asyncResp->res.jsonValue["@odata.type"] = 1952 "#LogEntryCollection.LogEntryCollection"; 1953 asyncResp->res.jsonValue["@odata.id"] = 1954 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries"; 1955 asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 1956 asyncResp->res.jsonValue["Description"] = 1957 "Collection of BMC Journal Entries"; 1958 nlohmann::json& logEntryArray = 1959 asyncResp->res.jsonValue["Members"]; 1960 logEntryArray = nlohmann::json::array(); 1961 1962 // Go through the journal and use the timestamp to create a 1963 // unique ID for each entry 1964 sd_journal* journalTmp = nullptr; 1965 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 1966 if (ret < 0) 1967 { 1968 BMCWEB_LOG_ERROR << "failed to open journal: " 1969 << strerror(-ret); 1970 messages::internalError(asyncResp->res); 1971 return; 1972 } 1973 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> 1974 journal(journalTmp, sd_journal_close); 1975 journalTmp = nullptr; 1976 uint64_t entryCount = 0; 1977 // Reset the unique ID on the first entry 1978 bool firstEntry = true; 1979 SD_JOURNAL_FOREACH(journal.get()) 1980 { 1981 entryCount++; 1982 // Handle paging using skip (number of entries to skip from 1983 // the start) and top (number of entries to display) 1984 if (entryCount <= skip || entryCount > skip + top) 1985 { 1986 continue; 1987 } 1988 1989 std::string idStr; 1990 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 1991 { 1992 continue; 1993 } 1994 1995 if (firstEntry) 1996 { 1997 firstEntry = false; 1998 } 1999 2000 logEntryArray.push_back({}); 2001 nlohmann::json& bmcJournalLogEntry = logEntryArray.back(); 2002 if (fillBMCJournalLogEntryJson(idStr, journal.get(), 2003 bmcJournalLogEntry) != 0) 2004 { 2005 messages::internalError(asyncResp->res); 2006 return; 2007 } 2008 } 2009 asyncResp->res.jsonValue["Members@odata.count"] = entryCount; 2010 if (skip + top < entryCount) 2011 { 2012 asyncResp->res.jsonValue["Members@odata.nextLink"] = 2013 "/redfish/v1/Managers/bmc/LogServices/Journal/" 2014 "Entries?$skip=" + 2015 std::to_string(skip + top); 2016 } 2017 }); 2018 } 2019 2020 inline void requestRoutesBMCJournalLogEntry(App& app) 2021 { 2022 BMCWEB_ROUTE(app, 2023 "/redfish/v1/Managers/bmc/LogServices/Journal/Entries/<str>/") 2024 .privileges({{"Login"}}) 2025 .methods(boost::beast::http::verb::get)( 2026 [](const crow::Request&, 2027 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2028 const std::string& entryID) { 2029 // Convert the unique ID back to a timestamp to find the entry 2030 uint64_t ts = 0; 2031 uint64_t index = 0; 2032 if (!getTimestampFromID(asyncResp, entryID, ts, index)) 2033 { 2034 return; 2035 } 2036 2037 sd_journal* journalTmp = nullptr; 2038 int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 2039 if (ret < 0) 2040 { 2041 BMCWEB_LOG_ERROR << "failed to open journal: " 2042 << strerror(-ret); 2043 messages::internalError(asyncResp->res); 2044 return; 2045 } 2046 std::unique_ptr<sd_journal, decltype(&sd_journal_close)> 2047 journal(journalTmp, sd_journal_close); 2048 journalTmp = nullptr; 2049 // Go to the timestamp in the log and move to the entry at the 2050 // index tracking the unique ID 2051 std::string idStr; 2052 bool firstEntry = true; 2053 ret = sd_journal_seek_realtime_usec(journal.get(), ts); 2054 if (ret < 0) 2055 { 2056 BMCWEB_LOG_ERROR << "failed to seek to an entry in journal" 2057 << strerror(-ret); 2058 messages::internalError(asyncResp->res); 2059 return; 2060 } 2061 for (uint64_t i = 0; i <= index; i++) 2062 { 2063 sd_journal_next(journal.get()); 2064 if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) 2065 { 2066 messages::internalError(asyncResp->res); 2067 return; 2068 } 2069 if (firstEntry) 2070 { 2071 firstEntry = false; 2072 } 2073 } 2074 // Confirm that the entry ID matches what was requested 2075 if (idStr != entryID) 2076 { 2077 messages::resourceMissingAtURI(asyncResp->res, entryID); 2078 return; 2079 } 2080 2081 if (fillBMCJournalLogEntryJson(entryID, journal.get(), 2082 asyncResp->res.jsonValue) != 0) 2083 { 2084 messages::internalError(asyncResp->res); 2085 return; 2086 } 2087 }); 2088 } 2089 2090 inline void requestRoutesBMCDumpService(App& app) 2091 { 2092 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/") 2093 .privileges({{"Login"}}) 2094 .methods(boost::beast::http::verb::get)( 2095 [](const crow::Request&, 2096 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2097 asyncResp->res.jsonValue["@odata.id"] = 2098 "/redfish/v1/Managers/bmc/LogServices/Dump"; 2099 asyncResp->res.jsonValue["@odata.type"] = 2100 "#LogService.v1_2_0.LogService"; 2101 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2102 asyncResp->res.jsonValue["Description"] = "BMC Dump LogService"; 2103 asyncResp->res.jsonValue["Id"] = "Dump"; 2104 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2105 2106 std::pair<std::string, std::string> redfishDateTimeOffset = 2107 crow::utility::getDateTimeOffsetNow(); 2108 asyncResp->res.jsonValue["DateTime"] = 2109 redfishDateTimeOffset.first; 2110 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2111 redfishDateTimeOffset.second; 2112 2113 asyncResp->res.jsonValue["Entries"] = { 2114 {"@odata.id", 2115 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"}}; 2116 asyncResp->res.jsonValue["Actions"] = { 2117 {"#LogService.ClearLog", 2118 {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/" 2119 "Actions/LogService.ClearLog"}}}, 2120 {"#LogService.CollectDiagnosticData", 2121 {{"target", "/redfish/v1/Managers/bmc/LogServices/Dump/" 2122 "Actions/LogService.CollectDiagnosticData"}}}}; 2123 }); 2124 } 2125 2126 inline void requestRoutesBMCDumpEntryCollection(App& app) 2127 { 2128 2129 /** 2130 * Functions triggers appropriate requests on DBus 2131 */ 2132 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/") 2133 .privileges({{"Login"}}) 2134 .methods(boost::beast::http::verb::get)( 2135 [](const crow::Request&, 2136 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2137 asyncResp->res.jsonValue["@odata.type"] = 2138 "#LogEntryCollection.LogEntryCollection"; 2139 asyncResp->res.jsonValue["@odata.id"] = 2140 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries"; 2141 asyncResp->res.jsonValue["Name"] = "BMC Dump Entries"; 2142 asyncResp->res.jsonValue["Description"] = 2143 "Collection of BMC Dump Entries"; 2144 2145 getDumpEntryCollection(asyncResp, "BMC"); 2146 }); 2147 } 2148 2149 inline void requestRoutesBMCDumpEntry(App& app) 2150 { 2151 BMCWEB_ROUTE(app, 2152 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2153 .privileges({{"Login"}}) 2154 .methods(boost::beast::http::verb::get)( 2155 [](const crow::Request&, 2156 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2157 const std::string& param) { 2158 getDumpEntryById(asyncResp, param, "BMC"); 2159 }); 2160 BMCWEB_ROUTE(app, 2161 "/redfish/v1/Managers/bmc/LogServices/Dump/Entries/<str>/") 2162 .privileges({{"ConfigureManager"}}) 2163 .methods(boost::beast::http::verb::delete_)( 2164 [](const crow::Request&, 2165 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2166 const std::string& param) { 2167 deleteDumpEntry(asyncResp, param, "bmc"); 2168 }); 2169 } 2170 2171 inline void requestRoutesBMCDumpCreate(App& app) 2172 { 2173 2174 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/" 2175 "Actions/" 2176 "LogService.CollectDiagnosticData/") 2177 .privileges({{"ConfigureManager"}}) 2178 .methods(boost::beast::http::verb::post)( 2179 [](const crow::Request& req, 2180 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2181 createDump(asyncResp, req, "BMC"); 2182 }); 2183 } 2184 2185 inline void requestRoutesBMCDumpClear(App& app) 2186 { 2187 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/LogServices/Dump/" 2188 "Actions/" 2189 "LogService.ClearLog/") 2190 .privileges({{"ConfigureManager"}}) 2191 .methods(boost::beast::http::verb::post)( 2192 [](const crow::Request&, 2193 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2194 clearDump(asyncResp, "BMC"); 2195 }); 2196 } 2197 2198 inline void requestRoutesSystemDumpService(App& app) 2199 { 2200 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/") 2201 .privileges({{"Login"}}) 2202 .methods(boost::beast::http::verb::get)( 2203 [](const crow::Request&, 2204 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2205 2206 { 2207 asyncResp->res.jsonValue["@odata.id"] = 2208 "/redfish/v1/Systems/system/LogServices/Dump"; 2209 asyncResp->res.jsonValue["@odata.type"] = 2210 "#LogService.v1_2_0.LogService"; 2211 asyncResp->res.jsonValue["Name"] = "Dump LogService"; 2212 asyncResp->res.jsonValue["Description"] = 2213 "System Dump LogService"; 2214 asyncResp->res.jsonValue["Id"] = "Dump"; 2215 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2216 2217 std::pair<std::string, std::string> redfishDateTimeOffset = 2218 crow::utility::getDateTimeOffsetNow(); 2219 asyncResp->res.jsonValue["DateTime"] = 2220 redfishDateTimeOffset.first; 2221 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2222 redfishDateTimeOffset.second; 2223 2224 asyncResp->res.jsonValue["Entries"] = { 2225 {"@odata.id", 2226 "/redfish/v1/Systems/system/LogServices/Dump/Entries"}}; 2227 asyncResp->res.jsonValue["Actions"] = { 2228 {"#LogService.ClearLog", 2229 {{"target", 2230 "/redfish/v1/Systems/system/LogServices/Dump/Actions/" 2231 "LogService.ClearLog"}}}, 2232 {"#LogService.CollectDiagnosticData", 2233 {{"target", 2234 "/redfish/v1/Systems/system/LogServices/Dump/Actions/" 2235 "LogService.CollectDiagnosticData"}}}}; 2236 }); 2237 } 2238 2239 inline void requestRoutesSystemDumpEntryCollection(App& app) 2240 { 2241 2242 /** 2243 * Functions triggers appropriate requests on DBus 2244 */ 2245 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/Entries/") 2246 .privileges({{"Login"}}) 2247 .methods(boost::beast::http::verb::get)( 2248 [](const crow::Request&, 2249 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2250 asyncResp->res.jsonValue["@odata.type"] = 2251 "#LogEntryCollection.LogEntryCollection"; 2252 asyncResp->res.jsonValue["@odata.id"] = 2253 "/redfish/v1/Systems/system/LogServices/Dump/Entries"; 2254 asyncResp->res.jsonValue["Name"] = "System Dump Entries"; 2255 asyncResp->res.jsonValue["Description"] = 2256 "Collection of System Dump Entries"; 2257 2258 getDumpEntryCollection(asyncResp, "System"); 2259 }); 2260 } 2261 2262 inline void requestRoutesSystemDumpEntry(App& app) 2263 { 2264 BMCWEB_ROUTE(app, 2265 "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/") 2266 .privileges({{"Login"}}) 2267 .methods(boost::beast::http::verb::get)( 2268 [](const crow::Request&, 2269 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2270 const std::string& param) { 2271 getDumpEntryById(asyncResp, param, "System"); 2272 }); 2273 2274 BMCWEB_ROUTE(app, 2275 "/redfish/v1/Systems/system/LogServices/Dump/Entries/<str>/") 2276 .privileges({{"ConfigureManager"}}) 2277 .methods(boost::beast::http::verb::delete_)( 2278 [](const crow::Request&, 2279 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2280 const std::string& param) { 2281 deleteDumpEntry(asyncResp, param, "system"); 2282 }); 2283 } 2284 2285 inline void requestRoutesSystemDumpCreate(App& app) 2286 { 2287 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/" 2288 "Actions/" 2289 "LogService.CollectDiagnosticData/") 2290 .privileges({{"ConfigureManager"}}) 2291 .methods(boost::beast::http::verb::post)( 2292 [](const crow::Request& req, 2293 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2294 2295 { createDump(asyncResp, req, "System"); }); 2296 } 2297 2298 inline void requestRoutesSystemDumpClear(App& app) 2299 { 2300 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Dump/" 2301 "Actions/" 2302 "LogService.ClearLog/") 2303 .privileges({{"ConfigureManager"}}) 2304 .methods(boost::beast::http::verb::post)( 2305 [](const crow::Request&, 2306 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 2307 2308 { clearDump(asyncResp, "System"); }); 2309 } 2310 2311 inline void requestRoutesCrashdumpService(App& app) 2312 { 2313 // Note: Deviated from redfish privilege registry for GET & HEAD 2314 // method for security reasons. 2315 /** 2316 * Functions triggers appropriate requests on DBus 2317 */ 2318 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/") 2319 .privileges({{"ConfigureManager"}}) 2320 .methods( 2321 boost::beast::http::verb:: 2322 get)([](const crow::Request&, 2323 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2324 // Copy over the static data to include the entries added by 2325 // SubRoute 2326 asyncResp->res.jsonValue["@odata.id"] = 2327 "/redfish/v1/Systems/system/LogServices/Crashdump"; 2328 asyncResp->res.jsonValue["@odata.type"] = 2329 "#LogService.v1_2_0.LogService"; 2330 asyncResp->res.jsonValue["Name"] = "Open BMC Oem Crashdump Service"; 2331 asyncResp->res.jsonValue["Description"] = "Oem Crashdump Service"; 2332 asyncResp->res.jsonValue["Id"] = "Oem Crashdump"; 2333 asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 2334 asyncResp->res.jsonValue["MaxNumberOfRecords"] = 3; 2335 2336 std::pair<std::string, std::string> redfishDateTimeOffset = 2337 crow::utility::getDateTimeOffsetNow(); 2338 asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 2339 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2340 redfishDateTimeOffset.second; 2341 2342 asyncResp->res.jsonValue["Entries"] = { 2343 {"@odata.id", 2344 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"}}; 2345 asyncResp->res.jsonValue["Actions"] = { 2346 {"#LogService.ClearLog", 2347 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2348 "Actions/LogService.ClearLog"}}}, 2349 {"#LogService.CollectDiagnosticData", 2350 {{"target", "/redfish/v1/Systems/system/LogServices/Crashdump/" 2351 "Actions/LogService.CollectDiagnosticData"}}}}; 2352 }); 2353 } 2354 2355 void inline requestRoutesCrashdumpClear(App& app) 2356 { 2357 BMCWEB_ROUTE(app, 2358 "/redfish/v1/Systems/system/LogServices/Crashdump/Actions/" 2359 "LogService.ClearLog/") 2360 .privileges({{"ConfigureComponents"}}) 2361 .methods(boost::beast::http::verb::post)( 2362 [](const crow::Request&, 2363 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2364 crow::connections::systemBus->async_method_call( 2365 [asyncResp](const boost::system::error_code ec, 2366 const std::string&) { 2367 if (ec) 2368 { 2369 messages::internalError(asyncResp->res); 2370 return; 2371 } 2372 messages::success(asyncResp->res); 2373 }, 2374 crashdumpObject, crashdumpPath, deleteAllInterface, 2375 "DeleteAll"); 2376 }); 2377 } 2378 2379 static void 2380 logCrashdumpEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2381 const std::string& logID, nlohmann::json& logEntryJson) 2382 { 2383 auto getStoredLogCallback = 2384 [asyncResp, logID, &logEntryJson]( 2385 const boost::system::error_code ec, 2386 const std::vector<std::pair<std::string, VariantType>>& params) { 2387 if (ec) 2388 { 2389 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 2390 if (ec.value() == 2391 boost::system::linux_error::bad_request_descriptor) 2392 { 2393 messages::resourceNotFound(asyncResp->res, "LogEntry", 2394 logID); 2395 } 2396 else 2397 { 2398 messages::internalError(asyncResp->res); 2399 } 2400 return; 2401 } 2402 2403 std::string timestamp{}; 2404 std::string filename{}; 2405 std::string logfile{}; 2406 parseCrashdumpParameters(params, filename, timestamp, logfile); 2407 2408 if (filename.empty() || timestamp.empty()) 2409 { 2410 messages::resourceMissingAtURI(asyncResp->res, logID); 2411 return; 2412 } 2413 2414 std::string crashdumpURI = 2415 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" + 2416 logID + "/" + filename; 2417 logEntryJson = {{"@odata.type", "#LogEntry.v1_7_0.LogEntry"}, 2418 {"@odata.id", "/redfish/v1/Systems/system/" 2419 "LogServices/Crashdump/Entries/" + 2420 logID}, 2421 {"Name", "CPU Crashdump"}, 2422 {"Id", logID}, 2423 {"EntryType", "Oem"}, 2424 {"AdditionalDataURI", std::move(crashdumpURI)}, 2425 {"DiagnosticDataType", "OEM"}, 2426 {"OEMDiagnosticDataType", "PECICrashdump"}, 2427 {"Created", std::move(timestamp)}}; 2428 }; 2429 crow::connections::systemBus->async_method_call( 2430 std::move(getStoredLogCallback), crashdumpObject, 2431 crashdumpPath + std::string("/") + logID, 2432 "org.freedesktop.DBus.Properties", "GetAll", crashdumpInterface); 2433 } 2434 2435 inline void requestRoutesCrashdumpEntryCollection(App& app) 2436 { 2437 // Note: Deviated from redfish privilege registry for GET & HEAD 2438 // method for security reasons. 2439 /** 2440 * Functions triggers appropriate requests on DBus 2441 */ 2442 BMCWEB_ROUTE(app, 2443 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/") 2444 .privileges({{"ConfigureComponents"}}) 2445 .methods( 2446 boost::beast::http::verb:: 2447 get)([](const crow::Request&, 2448 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2449 // Collections don't include the static data added by SubRoute 2450 // because it has a duplicate entry for members 2451 auto getLogEntriesCallback = [asyncResp]( 2452 const boost::system::error_code ec, 2453 const std::vector<std::string>& 2454 resp) { 2455 if (ec) 2456 { 2457 if (ec.value() != 2458 boost::system::errc::no_such_file_or_directory) 2459 { 2460 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 2461 << ec.message(); 2462 messages::internalError(asyncResp->res); 2463 return; 2464 } 2465 } 2466 asyncResp->res.jsonValue["@odata.type"] = 2467 "#LogEntryCollection.LogEntryCollection"; 2468 asyncResp->res.jsonValue["@odata.id"] = 2469 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries"; 2470 asyncResp->res.jsonValue["Name"] = "Open BMC Crashdump Entries"; 2471 asyncResp->res.jsonValue["Description"] = 2472 "Collection of Crashdump Entries"; 2473 nlohmann::json& logEntryArray = 2474 asyncResp->res.jsonValue["Members"]; 2475 logEntryArray = nlohmann::json::array(); 2476 std::vector<std::string> logIDs; 2477 // Get the list of log entries and build up an empty array big 2478 // enough to hold them 2479 for (const std::string& objpath : resp) 2480 { 2481 // Get the log ID 2482 std::size_t lastPos = objpath.rfind('/'); 2483 if (lastPos == std::string::npos) 2484 { 2485 continue; 2486 } 2487 logIDs.emplace_back(objpath.substr(lastPos + 1)); 2488 2489 // Add a space for the log entry to the array 2490 logEntryArray.push_back({}); 2491 } 2492 // Now go through and set up async calls to fill in the entries 2493 size_t index = 0; 2494 for (const std::string& logID : logIDs) 2495 { 2496 // Add the log entry to the array 2497 logCrashdumpEntry(asyncResp, logID, logEntryArray[index++]); 2498 } 2499 asyncResp->res.jsonValue["Members@odata.count"] = 2500 logEntryArray.size(); 2501 }; 2502 crow::connections::systemBus->async_method_call( 2503 std::move(getLogEntriesCallback), 2504 "xyz.openbmc_project.ObjectMapper", 2505 "/xyz/openbmc_project/object_mapper", 2506 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 2507 std::array<const char*, 1>{crashdumpInterface}); 2508 }); 2509 } 2510 2511 inline void requestRoutesCrashdumpEntry(App& app) 2512 { 2513 // Note: Deviated from redfish privilege registry for GET & HEAD 2514 // method for security reasons. 2515 2516 BMCWEB_ROUTE( 2517 app, "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/") 2518 .privileges({{"ConfigureComponents"}}) 2519 .methods(boost::beast::http::verb::get)( 2520 [](const crow::Request&, 2521 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2522 const std::string& param) { 2523 const std::string& logID = param; 2524 logCrashdumpEntry(asyncResp, logID, asyncResp->res.jsonValue); 2525 }); 2526 } 2527 2528 inline void requestRoutesCrashdumpFile(App& app) 2529 { 2530 // Note: Deviated from redfish privilege registry for GET & HEAD 2531 // method for security reasons. 2532 BMCWEB_ROUTE( 2533 app, 2534 "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/<str>/<str>/") 2535 .privileges({{"ConfigureComponents"}}) 2536 .methods(boost::beast::http::verb::get)( 2537 [](const crow::Request&, 2538 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2539 const std::string& logID, const std::string& fileName) { 2540 auto getStoredLogCallback = 2541 [asyncResp, logID, fileName]( 2542 const boost::system::error_code ec, 2543 const std::vector<std::pair<std::string, VariantType>>& 2544 resp) { 2545 if (ec) 2546 { 2547 BMCWEB_LOG_DEBUG << "failed to get log ec: " 2548 << ec.message(); 2549 messages::internalError(asyncResp->res); 2550 return; 2551 } 2552 2553 std::string dbusFilename{}; 2554 std::string dbusTimestamp{}; 2555 std::string dbusFilepath{}; 2556 2557 parseCrashdumpParameters(resp, dbusFilename, 2558 dbusTimestamp, dbusFilepath); 2559 2560 if (dbusFilename.empty() || dbusTimestamp.empty() || 2561 dbusFilepath.empty()) 2562 { 2563 messages::resourceMissingAtURI(asyncResp->res, 2564 fileName); 2565 return; 2566 } 2567 2568 // Verify the file name parameter is correct 2569 if (fileName != dbusFilename) 2570 { 2571 messages::resourceMissingAtURI(asyncResp->res, 2572 fileName); 2573 return; 2574 } 2575 2576 if (!std::filesystem::exists(dbusFilepath)) 2577 { 2578 messages::resourceMissingAtURI(asyncResp->res, 2579 fileName); 2580 return; 2581 } 2582 std::ifstream ifs(dbusFilepath, std::ios::in | 2583 std::ios::binary | 2584 std::ios::ate); 2585 std::ifstream::pos_type fileSize = ifs.tellg(); 2586 if (fileSize < 0) 2587 { 2588 messages::generalError(asyncResp->res); 2589 return; 2590 } 2591 ifs.seekg(0, std::ios::beg); 2592 2593 auto crashData = std::make_unique<char[]>( 2594 static_cast<unsigned int>(fileSize)); 2595 2596 ifs.read(crashData.get(), static_cast<int>(fileSize)); 2597 2598 // The cast to std::string is intentional in order to 2599 // use the assign() that applies move mechanics 2600 asyncResp->res.body().assign( 2601 static_cast<std::string>(crashData.get())); 2602 2603 // Configure this to be a file download when accessed 2604 // from a browser 2605 asyncResp->res.addHeader("Content-Disposition", 2606 "attachment"); 2607 }; 2608 crow::connections::systemBus->async_method_call( 2609 std::move(getStoredLogCallback), crashdumpObject, 2610 crashdumpPath + std::string("/") + logID, 2611 "org.freedesktop.DBus.Properties", "GetAll", 2612 crashdumpInterface); 2613 }); 2614 } 2615 2616 inline void requestRoutesCrashdumpCollect(App& app) 2617 { 2618 // Note: Deviated from redfish privilege registry for GET & HEAD 2619 // method for security reasons. 2620 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/Crashdump/" 2621 "Actions/LogService.CollectDiagnosticData/") 2622 .privileges({{"ConfigureComponents"}}) 2623 .methods( 2624 boost::beast::http::verb:: 2625 post)([](const crow::Request& req, 2626 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2627 std::string diagnosticDataType; 2628 std::string oemDiagnosticDataType; 2629 if (!redfish::json_util::readJson( 2630 req, asyncResp->res, "DiagnosticDataType", 2631 diagnosticDataType, "OEMDiagnosticDataType", 2632 oemDiagnosticDataType)) 2633 { 2634 return; 2635 } 2636 2637 if (diagnosticDataType != "OEM") 2638 { 2639 BMCWEB_LOG_ERROR 2640 << "Only OEM DiagnosticDataType supported for Crashdump"; 2641 messages::actionParameterValueFormatError( 2642 asyncResp->res, diagnosticDataType, "DiagnosticDataType", 2643 "CollectDiagnosticData"); 2644 return; 2645 } 2646 2647 auto collectCrashdumpCallback = [asyncResp, req]( 2648 const boost::system::error_code 2649 ec, 2650 const std::string&) { 2651 if (ec) 2652 { 2653 if (ec.value() == 2654 boost::system::errc::operation_not_supported) 2655 { 2656 messages::resourceInStandby(asyncResp->res); 2657 } 2658 else if (ec.value() == 2659 boost::system::errc::device_or_resource_busy) 2660 { 2661 messages::serviceTemporarilyUnavailable(asyncResp->res, 2662 "60"); 2663 } 2664 else 2665 { 2666 messages::internalError(asyncResp->res); 2667 } 2668 return; 2669 } 2670 std::shared_ptr<task::TaskData> task = 2671 task::TaskData::createTask( 2672 [](boost::system::error_code err, 2673 sdbusplus::message::message&, 2674 const std::shared_ptr<task::TaskData>& taskData) { 2675 if (!err) 2676 { 2677 taskData->messages.emplace_back( 2678 messages::taskCompletedOK( 2679 std::to_string(taskData->index))); 2680 taskData->state = "Completed"; 2681 } 2682 return task::completed; 2683 }, 2684 "type='signal',interface='org.freedesktop.DBus." 2685 "Properties'," 2686 "member='PropertiesChanged',arg0namespace='com.intel." 2687 "crashdump'"); 2688 task->startTimer(std::chrono::minutes(5)); 2689 task->populateResp(asyncResp->res); 2690 task->payload.emplace(req); 2691 }; 2692 2693 if (oemDiagnosticDataType == "OnDemand") 2694 { 2695 crow::connections::systemBus->async_method_call( 2696 std::move(collectCrashdumpCallback), crashdumpObject, 2697 crashdumpPath, crashdumpOnDemandInterface, 2698 "GenerateOnDemandLog"); 2699 } 2700 else if (oemDiagnosticDataType == "Telemetry") 2701 { 2702 crow::connections::systemBus->async_method_call( 2703 std::move(collectCrashdumpCallback), crashdumpObject, 2704 crashdumpPath, crashdumpTelemetryInterface, 2705 "GenerateTelemetryLog"); 2706 } 2707 else 2708 { 2709 BMCWEB_LOG_ERROR << "Unsupported OEMDiagnosticDataType: " 2710 << oemDiagnosticDataType; 2711 messages::actionParameterValueFormatError( 2712 asyncResp->res, oemDiagnosticDataType, 2713 "OEMDiagnosticDataType", "CollectDiagnosticData"); 2714 return; 2715 } 2716 }); 2717 } 2718 2719 /** 2720 * DBusLogServiceActionsClear class supports POST method for ClearLog action. 2721 */ 2722 inline void requestRoutesDBusLogServiceActionsClear(App& app) 2723 { 2724 /** 2725 * Function handles POST method request. 2726 * The Clear Log actions does not require any parameter.The action deletes 2727 * all entries found in the Entries collection for this Log Service. 2728 */ 2729 2730 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/EventLog/Actions/" 2731 "LogService.ClearLog/") 2732 .privileges({{"ConfigureManager"}}) 2733 .methods(boost::beast::http::verb::post)( 2734 [](const crow::Request&, 2735 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2736 BMCWEB_LOG_DEBUG << "Do delete all entries."; 2737 2738 // Process response from Logging service. 2739 auto respHandler = [asyncResp]( 2740 const boost::system::error_code ec) { 2741 BMCWEB_LOG_DEBUG 2742 << "doClearLog resp_handler callback: Done"; 2743 if (ec) 2744 { 2745 // TODO Handle for specific error code 2746 BMCWEB_LOG_ERROR << "doClearLog resp_handler got error " 2747 << ec; 2748 asyncResp->res.result( 2749 boost::beast::http::status::internal_server_error); 2750 return; 2751 } 2752 2753 asyncResp->res.result( 2754 boost::beast::http::status::no_content); 2755 }; 2756 2757 // Make call to Logging service to request Clear Log 2758 crow::connections::systemBus->async_method_call( 2759 respHandler, "xyz.openbmc_project.Logging", 2760 "/xyz/openbmc_project/logging", 2761 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2762 }); 2763 } 2764 2765 /**************************************************** 2766 * Redfish PostCode interfaces 2767 * using DBUS interface: getPostCodesTS 2768 ******************************************************/ 2769 inline void requestRoutesPostCodesLogService(App& app) 2770 { 2771 BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/PostCodes/") 2772 .privileges({{"Login"}}) 2773 .methods(boost::beast::http::verb::get)( 2774 [](const crow::Request&, 2775 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2776 asyncResp->res.jsonValue = { 2777 {"@odata.id", 2778 "/redfish/v1/Systems/system/LogServices/PostCodes"}, 2779 {"@odata.type", "#LogService.v1_1_0.LogService"}, 2780 {"Name", "POST Code Log Service"}, 2781 {"Description", "POST Code Log Service"}, 2782 {"Id", "BIOS POST Code Log"}, 2783 {"OverWritePolicy", "WrapsWhenFull"}, 2784 {"Entries", 2785 {{"@odata.id", "/redfish/v1/Systems/system/LogServices/" 2786 "PostCodes/Entries"}}}}; 2787 2788 std::pair<std::string, std::string> redfishDateTimeOffset = 2789 crow::utility::getDateTimeOffsetNow(); 2790 asyncResp->res.jsonValue["DateTime"] = 2791 redfishDateTimeOffset.first; 2792 asyncResp->res.jsonValue["DateTimeLocalOffset"] = 2793 redfishDateTimeOffset.second; 2794 2795 asyncResp->res.jsonValue["Actions"]["#LogService.ClearLog"] = { 2796 {"target", 2797 "/redfish/v1/Systems/system/LogServices/PostCodes/" 2798 "Actions/LogService.ClearLog"}}; 2799 }); 2800 } 2801 2802 inline void requestRoutesPostCodesClear(App& app) 2803 { 2804 BMCWEB_ROUTE(app, 2805 "/redfish/v1/Systems/system/LogServices/PostCodes/Actions/" 2806 "LogService.ClearLog/") 2807 .privileges({{"ConfigureComponents"}}) 2808 .methods(boost::beast::http::verb::post)( 2809 [](const crow::Request&, 2810 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 2811 BMCWEB_LOG_DEBUG << "Do delete all postcodes entries."; 2812 2813 // Make call to post-code service to request clear all 2814 crow::connections::systemBus->async_method_call( 2815 [asyncResp](const boost::system::error_code ec) { 2816 if (ec) 2817 { 2818 // TODO Handle for specific error code 2819 BMCWEB_LOG_ERROR 2820 << "doClearPostCodes resp_handler got error " 2821 << ec; 2822 asyncResp->res.result(boost::beast::http::status:: 2823 internal_server_error); 2824 messages::internalError(asyncResp->res); 2825 return; 2826 } 2827 }, 2828 "xyz.openbmc_project.State.Boot.PostCode0", 2829 "/xyz/openbmc_project/State/Boot/PostCode0", 2830 "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll"); 2831 }); 2832 } 2833 2834 static void fillPostCodeEntry( 2835 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 2836 const boost::container::flat_map< 2837 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& postcode, 2838 const uint16_t bootIndex, const uint64_t codeIndex = 0, 2839 const uint64_t skip = 0, const uint64_t top = 0) 2840 { 2841 // Get the Message from the MessageRegistry 2842 const message_registries::Message* message = 2843 message_registries::getMessage("OpenBMC.0.2.BIOSPOSTCode"); 2844 2845 uint64_t currentCodeIndex = 0; 2846 nlohmann::json& logEntryArray = aResp->res.jsonValue["Members"]; 2847 2848 uint64_t firstCodeTimeUs = 0; 2849 for (const std::pair<uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 2850 code : postcode) 2851 { 2852 currentCodeIndex++; 2853 std::string postcodeEntryID = 2854 "B" + std::to_string(bootIndex) + "-" + 2855 std::to_string(currentCodeIndex); // 1 based index in EntryID string 2856 2857 uint64_t usecSinceEpoch = code.first; 2858 uint64_t usTimeOffset = 0; 2859 2860 if (1 == currentCodeIndex) 2861 { // already incremented 2862 firstCodeTimeUs = code.first; 2863 } 2864 else 2865 { 2866 usTimeOffset = code.first - firstCodeTimeUs; 2867 } 2868 2869 // skip if no specific codeIndex is specified and currentCodeIndex does 2870 // not fall between top and skip 2871 if ((codeIndex == 0) && 2872 (currentCodeIndex <= skip || currentCodeIndex > top)) 2873 { 2874 continue; 2875 } 2876 2877 // skip if a specific codeIndex is specified and does not match the 2878 // currentIndex 2879 if ((codeIndex > 0) && (currentCodeIndex != codeIndex)) 2880 { 2881 // This is done for simplicity. 1st entry is needed to calculate 2882 // time offset. To improve efficiency, one can get to the entry 2883 // directly (possibly with flatmap's nth method) 2884 continue; 2885 } 2886 2887 // currentCodeIndex is within top and skip or equal to specified code 2888 // index 2889 2890 // Get the Created time from the timestamp 2891 std::string entryTimeStr; 2892 entryTimeStr = crow::utility::getDateTime( 2893 static_cast<std::time_t>(usecSinceEpoch / 1000 / 1000)); 2894 2895 // assemble messageArgs: BootIndex, TimeOffset(100us), PostCode(hex) 2896 std::ostringstream hexCode; 2897 hexCode << "0x" << std::setfill('0') << std::setw(2) << std::hex 2898 << std::get<0>(code.second); 2899 std::ostringstream timeOffsetStr; 2900 // Set Fixed -Point Notation 2901 timeOffsetStr << std::fixed; 2902 // Set precision to 4 digits 2903 timeOffsetStr << std::setprecision(4); 2904 // Add double to stream 2905 timeOffsetStr << static_cast<double>(usTimeOffset) / 1000 / 1000; 2906 std::vector<std::string> messageArgs = { 2907 std::to_string(bootIndex), timeOffsetStr.str(), hexCode.str()}; 2908 2909 // Get MessageArgs template from message registry 2910 std::string msg; 2911 if (message != nullptr) 2912 { 2913 msg = message->message; 2914 2915 // fill in this post code value 2916 int i = 0; 2917 for (const std::string& messageArg : messageArgs) 2918 { 2919 std::string argStr = "%" + std::to_string(++i); 2920 size_t argPos = msg.find(argStr); 2921 if (argPos != std::string::npos) 2922 { 2923 msg.replace(argPos, argStr.length(), messageArg); 2924 } 2925 } 2926 } 2927 2928 // Get Severity template from message registry 2929 std::string severity; 2930 if (message != nullptr) 2931 { 2932 severity = message->severity; 2933 } 2934 2935 // add to AsyncResp 2936 logEntryArray.push_back({}); 2937 nlohmann::json& bmcLogEntry = logEntryArray.back(); 2938 bmcLogEntry = {{"@odata.type", "#LogEntry.v1_4_0.LogEntry"}, 2939 {"@odata.id", "/redfish/v1/Systems/system/LogServices/" 2940 "PostCodes/Entries/" + 2941 postcodeEntryID}, 2942 {"Name", "POST Code Log Entry"}, 2943 {"Id", postcodeEntryID}, 2944 {"Message", std::move(msg)}, 2945 {"MessageId", "OpenBMC.0.2.BIOSPOSTCode"}, 2946 {"MessageArgs", std::move(messageArgs)}, 2947 {"EntryType", "Event"}, 2948 {"Severity", std::move(severity)}, 2949 {"Created", entryTimeStr}}; 2950 } 2951 } 2952 2953 static void getPostCodeForEntry(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 2954 const uint16_t bootIndex, 2955 const uint64_t codeIndex) 2956 { 2957 crow::connections::systemBus->async_method_call( 2958 [aResp, bootIndex, 2959 codeIndex](const boost::system::error_code ec, 2960 const boost::container::flat_map< 2961 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 2962 postcode) { 2963 if (ec) 2964 { 2965 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 2966 messages::internalError(aResp->res); 2967 return; 2968 } 2969 2970 // skip the empty postcode boots 2971 if (postcode.empty()) 2972 { 2973 return; 2974 } 2975 2976 fillPostCodeEntry(aResp, postcode, bootIndex, codeIndex); 2977 2978 aResp->res.jsonValue["Members@odata.count"] = 2979 aResp->res.jsonValue["Members"].size(); 2980 }, 2981 "xyz.openbmc_project.State.Boot.PostCode0", 2982 "/xyz/openbmc_project/State/Boot/PostCode0", 2983 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 2984 bootIndex); 2985 } 2986 2987 static void getPostCodeForBoot(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 2988 const uint16_t bootIndex, 2989 const uint16_t bootCount, 2990 const uint64_t entryCount, const uint64_t skip, 2991 const uint64_t top) 2992 { 2993 crow::connections::systemBus->async_method_call( 2994 [aResp, bootIndex, bootCount, entryCount, skip, 2995 top](const boost::system::error_code ec, 2996 const boost::container::flat_map< 2997 uint64_t, std::tuple<uint64_t, std::vector<uint8_t>>>& 2998 postcode) { 2999 if (ec) 3000 { 3001 BMCWEB_LOG_DEBUG << "DBUS POST CODE PostCode response error"; 3002 messages::internalError(aResp->res); 3003 return; 3004 } 3005 3006 uint64_t endCount = entryCount; 3007 if (!postcode.empty()) 3008 { 3009 endCount = entryCount + postcode.size(); 3010 3011 if ((skip < endCount) && ((top + skip) > entryCount)) 3012 { 3013 uint64_t thisBootSkip = 3014 std::max(skip, entryCount) - entryCount; 3015 uint64_t thisBootTop = 3016 std::min(top + skip, endCount) - entryCount; 3017 3018 fillPostCodeEntry(aResp, postcode, bootIndex, 0, 3019 thisBootSkip, thisBootTop); 3020 } 3021 aResp->res.jsonValue["Members@odata.count"] = endCount; 3022 } 3023 3024 // continue to previous bootIndex 3025 if (bootIndex < bootCount) 3026 { 3027 getPostCodeForBoot(aResp, static_cast<uint16_t>(bootIndex + 1), 3028 bootCount, endCount, skip, top); 3029 } 3030 else 3031 { 3032 aResp->res.jsonValue["Members@odata.nextLink"] = 3033 "/redfish/v1/Systems/system/LogServices/PostCodes/" 3034 "Entries?$skip=" + 3035 std::to_string(skip + top); 3036 } 3037 }, 3038 "xyz.openbmc_project.State.Boot.PostCode0", 3039 "/xyz/openbmc_project/State/Boot/PostCode0", 3040 "xyz.openbmc_project.State.Boot.PostCode", "GetPostCodesWithTimeStamp", 3041 bootIndex); 3042 } 3043 3044 static void 3045 getCurrentBootNumber(const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3046 const uint64_t skip, const uint64_t top) 3047 { 3048 uint64_t entryCount = 0; 3049 crow::connections::systemBus->async_method_call( 3050 [aResp, entryCount, skip, 3051 top](const boost::system::error_code ec, 3052 const std::variant<uint16_t>& bootCount) { 3053 if (ec) 3054 { 3055 BMCWEB_LOG_DEBUG << "DBUS response error " << ec; 3056 messages::internalError(aResp->res); 3057 return; 3058 } 3059 auto pVal = std::get_if<uint16_t>(&bootCount); 3060 if (pVal) 3061 { 3062 getPostCodeForBoot(aResp, 1, *pVal, entryCount, skip, top); 3063 } 3064 else 3065 { 3066 BMCWEB_LOG_DEBUG << "Post code boot index failed."; 3067 } 3068 }, 3069 "xyz.openbmc_project.State.Boot.PostCode0", 3070 "/xyz/openbmc_project/State/Boot/PostCode0", 3071 "org.freedesktop.DBus.Properties", "Get", 3072 "xyz.openbmc_project.State.Boot.PostCode", "CurrentBootCycleCount"); 3073 } 3074 3075 inline void requestRoutesPostCodesEntryCollection(App& app) 3076 { 3077 BMCWEB_ROUTE(app, 3078 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/") 3079 .privileges({{"Login"}}) 3080 .methods(boost::beast::http::verb::get)( 3081 [](const crow::Request& req, 3082 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 3083 asyncResp->res.jsonValue["@odata.type"] = 3084 "#LogEntryCollection.LogEntryCollection"; 3085 asyncResp->res.jsonValue["@odata.id"] = 3086 "/redfish/v1/Systems/system/LogServices/PostCodes/Entries"; 3087 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3088 asyncResp->res.jsonValue["Description"] = 3089 "Collection of POST Code Log Entries"; 3090 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3091 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3092 3093 uint64_t skip = 0; 3094 uint64_t top = maxEntriesPerPage; // Show max entries by default 3095 if (!getSkipParam(asyncResp, req, skip)) 3096 { 3097 return; 3098 } 3099 if (!getTopParam(asyncResp, req, top)) 3100 { 3101 return; 3102 } 3103 getCurrentBootNumber(asyncResp, skip, top); 3104 }); 3105 } 3106 3107 inline void requestRoutesPostCodesEntry(App& app) 3108 { 3109 BMCWEB_ROUTE( 3110 app, "/redfish/v1/Systems/system/LogServices/PostCodes/Entries/<str>/") 3111 .privileges({{"Login"}}) 3112 .methods(boost::beast::http::verb::get)( 3113 [](const crow::Request&, 3114 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 3115 const std::string& targetID) { 3116 size_t bootPos = targetID.find('B'); 3117 if (bootPos == std::string::npos) 3118 { 3119 // Requested ID was not found 3120 messages::resourceMissingAtURI(asyncResp->res, targetID); 3121 return; 3122 } 3123 std::string_view bootIndexStr(targetID); 3124 bootIndexStr.remove_prefix(bootPos + 1); 3125 uint16_t bootIndex = 0; 3126 uint64_t codeIndex = 0; 3127 size_t dashPos = bootIndexStr.find('-'); 3128 3129 if (dashPos == std::string::npos) 3130 { 3131 return; 3132 } 3133 std::string_view codeIndexStr(bootIndexStr); 3134 bootIndexStr.remove_suffix(dashPos); 3135 codeIndexStr.remove_prefix(dashPos + 1); 3136 3137 bootIndex = static_cast<uint16_t>( 3138 strtoul(std::string(bootIndexStr).c_str(), nullptr, 0)); 3139 codeIndex = 3140 strtoul(std::string(codeIndexStr).c_str(), nullptr, 0); 3141 if (bootIndex == 0 || codeIndex == 0) 3142 { 3143 BMCWEB_LOG_DEBUG << "Get Post Code invalid entry string " 3144 << targetID; 3145 } 3146 3147 asyncResp->res.jsonValue["@odata.type"] = 3148 "#LogEntry.v1_4_0.LogEntry"; 3149 asyncResp->res.jsonValue["@odata.id"] = 3150 "/redfish/v1/Systems/system/LogServices/PostCodes/" 3151 "Entries"; 3152 asyncResp->res.jsonValue["Name"] = "BIOS POST Code Log Entries"; 3153 asyncResp->res.jsonValue["Description"] = 3154 "Collection of POST Code Log Entries"; 3155 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 3156 asyncResp->res.jsonValue["Members@odata.count"] = 0; 3157 3158 getPostCodeForEntry(asyncResp, bootIndex, codeIndex); 3159 }); 3160 } 3161 3162 } // namespace redfish 3163