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