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