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