1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3b0983db2SEd Tanous #pragma once 4b0983db2SEd Tanous 5b0983db2SEd Tanous #include "app.hpp" 6b0983db2SEd Tanous #include "error_messages.hpp" 7b0983db2SEd Tanous #include "generated/enums/log_entry.hpp" 8b0983db2SEd Tanous #include "query.hpp" 9b0983db2SEd Tanous #include "registries/base_message_registry.hpp" 10b0983db2SEd Tanous #include "registries/privilege_registry.hpp" 1160e995cdSEd Tanous #include "utils/journal_utils.hpp" 12b0983db2SEd Tanous #include "utils/time_utils.hpp" 13b0983db2SEd Tanous 14b0983db2SEd Tanous #include <systemd/sd-journal.h> 15b0983db2SEd Tanous 16b0983db2SEd Tanous #include <boost/beast/http/verb.hpp> 17b0983db2SEd Tanous 18b0983db2SEd Tanous #include <array> 19b0983db2SEd Tanous #include <memory> 20b0983db2SEd Tanous #include <string> 21b0983db2SEd Tanous #include <string_view> 22b0983db2SEd Tanous 23b0983db2SEd Tanous namespace redfish 24b0983db2SEd Tanous { 25b0983db2SEd Tanous 2684177a2fSEd Tanous inline void handleManagersLogServiceJournalGet( 2784177a2fSEd Tanous App& app, const crow::Request& req, 28b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2984177a2fSEd Tanous const std::string& managerId) 3084177a2fSEd Tanous { 31b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 32b0983db2SEd Tanous { 33b0983db2SEd Tanous return; 34b0983db2SEd Tanous } 35b0983db2SEd Tanous 36b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 37b0983db2SEd Tanous { 38b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 39b0983db2SEd Tanous return; 40b0983db2SEd Tanous } 41b0983db2SEd Tanous 4284177a2fSEd Tanous asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 43b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = 44b0983db2SEd Tanous boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 45b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 46b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 47b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 48b0983db2SEd Tanous asyncResp->res.jsonValue["Id"] = "Journal"; 49b0983db2SEd Tanous asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 50b0983db2SEd Tanous 51b0983db2SEd Tanous std::pair<std::string, std::string> redfishDateTimeOffset = 52b0983db2SEd Tanous redfish::time_utils::getDateTimeOffsetNow(); 53b0983db2SEd Tanous asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 54b0983db2SEd Tanous asyncResp->res.jsonValue["DateTimeLocalOffset"] = 55b0983db2SEd Tanous redfishDateTimeOffset.second; 56b0983db2SEd Tanous 57b0983db2SEd Tanous asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 58b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 59b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 60b0983db2SEd Tanous } 61b0983db2SEd Tanous 62055713e4SEd Tanous struct JournalReadState 63055713e4SEd Tanous { 64055713e4SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal; 65055713e4SEd Tanous }; 66055713e4SEd Tanous 67bd79bce8SPatrick Williams inline void readJournalEntries( 68bd79bce8SPatrick Williams uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 69055713e4SEd Tanous JournalReadState&& readState) 70055713e4SEd Tanous { 71055713e4SEd Tanous nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"]; 72055713e4SEd Tanous nlohmann::json::array_t* logEntryArray = 73055713e4SEd Tanous logEntry.get_ptr<nlohmann::json::array_t*>(); 74055713e4SEd Tanous if (logEntryArray == nullptr) 75055713e4SEd Tanous { 76055713e4SEd Tanous messages::internalError(asyncResp->res); 77055713e4SEd Tanous return; 78055713e4SEd Tanous } 79055713e4SEd Tanous 80055713e4SEd Tanous // The Journal APIs unfortunately do blocking calls to the filesystem, and 81055713e4SEd Tanous // can be somewhat expensive. Short of creating our own io_uring based 82055713e4SEd Tanous // implementation of sd-journal, which would be difficult, the best thing we 83055713e4SEd Tanous // can do is to only parse a certain number of entries at a time. The 84055713e4SEd Tanous // current chunk size is selected arbitrarily to ensure that we're not 85055713e4SEd Tanous // trying to process thousands of entries at the same time. 86055713e4SEd Tanous // The implementation will process the number of entries, then return 87055713e4SEd Tanous // control to the io_context to let other operations continue. 88055713e4SEd Tanous size_t segmentCountRemaining = 10; 89055713e4SEd Tanous 90055713e4SEd Tanous // Reset the unique ID on the first entry 91055713e4SEd Tanous for (uint64_t entryCount = logEntryArray->size(); 92055713e4SEd Tanous entryCount < topEntryCount; entryCount++) 93055713e4SEd Tanous { 94055713e4SEd Tanous if (segmentCountRemaining == 0) 95055713e4SEd Tanous { 96055713e4SEd Tanous boost::asio::post(crow::connections::systemBus->get_io_context(), 97055713e4SEd Tanous [asyncResp, topEntryCount, 98055713e4SEd Tanous readState = std::move(readState)]() mutable { 99055713e4SEd Tanous readJournalEntries(topEntryCount, asyncResp, 100055713e4SEd Tanous std::move(readState)); 101055713e4SEd Tanous }); 102055713e4SEd Tanous return; 103055713e4SEd Tanous } 104055713e4SEd Tanous 105055713e4SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 1068274eb11SEd Tanous if (!fillBMCJournalLogEntryJson(readState.journal.get(), 107055713e4SEd Tanous bmcJournalLogEntry)) 108055713e4SEd Tanous { 109055713e4SEd Tanous messages::internalError(asyncResp->res); 110055713e4SEd Tanous return; 111055713e4SEd Tanous } 112055713e4SEd Tanous logEntryArray->emplace_back(std::move(bmcJournalLogEntry)); 113055713e4SEd Tanous 1148274eb11SEd Tanous int ret = sd_journal_next(readState.journal.get()); 115055713e4SEd Tanous if (ret < 0) 116055713e4SEd Tanous { 117055713e4SEd Tanous messages::internalError(asyncResp->res); 118055713e4SEd Tanous return; 119055713e4SEd Tanous } 120055713e4SEd Tanous if (ret == 0) 121055713e4SEd Tanous { 122055713e4SEd Tanous break; 123055713e4SEd Tanous } 124055713e4SEd Tanous segmentCountRemaining--; 125055713e4SEd Tanous } 126055713e4SEd Tanous } 127055713e4SEd Tanous 12884177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet( 12984177a2fSEd Tanous App& app, const crow::Request& req, 130b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 13184177a2fSEd Tanous const std::string& managerId) 13284177a2fSEd Tanous { 133b0983db2SEd Tanous query_param::QueryCapabilities capabilities = { 134b0983db2SEd Tanous .canDelegateTop = true, 135b0983db2SEd Tanous .canDelegateSkip = true, 136b0983db2SEd Tanous }; 137b0983db2SEd Tanous query_param::Query delegatedQuery; 13884177a2fSEd Tanous if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 13984177a2fSEd Tanous delegatedQuery, capabilities)) 140b0983db2SEd Tanous { 141b0983db2SEd Tanous return; 142b0983db2SEd Tanous } 143b0983db2SEd Tanous 144b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 145b0983db2SEd Tanous { 146b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 147b0983db2SEd Tanous return; 148b0983db2SEd Tanous } 149b0983db2SEd Tanous 150b0983db2SEd Tanous size_t skip = delegatedQuery.skip.value_or(0); 151b0983db2SEd Tanous size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 152b0983db2SEd Tanous 153b0983db2SEd Tanous // Collections don't include the static data added by SubRoute 154b0983db2SEd Tanous // because it has a duplicate entry for members 155b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 156b0983db2SEd Tanous "#LogEntryCollection.LogEntryCollection"; 157b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 158b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 159b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 160b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 161b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = 162b0983db2SEd Tanous "Collection of BMC Journal Entries"; 163055713e4SEd Tanous asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t(); 164b0983db2SEd Tanous 165b0983db2SEd Tanous // Go through the journal and use the timestamp to create a 166b0983db2SEd Tanous // unique ID for each entry 167b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 168b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 169b0983db2SEd Tanous if (ret < 0) 170b0983db2SEd Tanous { 1717da633f0SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", ret); 172b0983db2SEd Tanous messages::internalError(asyncResp->res); 173b0983db2SEd Tanous return; 174b0983db2SEd Tanous } 175055713e4SEd Tanous 176b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 177b0983db2SEd Tanous journalTmp, sd_journal_close); 178b0983db2SEd Tanous journalTmp = nullptr; 179b0983db2SEd Tanous 180055713e4SEd Tanous // Seek to the end 181055713e4SEd Tanous if (sd_journal_seek_tail(journal.get()) < 0) 182b0983db2SEd Tanous { 183b0983db2SEd Tanous messages::internalError(asyncResp->res); 184b0983db2SEd Tanous return; 185b0983db2SEd Tanous } 186055713e4SEd Tanous 187055713e4SEd Tanous // Get the last entry 188055713e4SEd Tanous if (sd_journal_previous(journal.get()) < 0) 189055713e4SEd Tanous { 190055713e4SEd Tanous messages::internalError(asyncResp->res); 191055713e4SEd Tanous return; 192b0983db2SEd Tanous } 193055713e4SEd Tanous 194055713e4SEd Tanous // Get the last sequence number 195055713e4SEd Tanous uint64_t endSeqNum = 0; 196058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254 197055713e4SEd Tanous { 198055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0) 199055713e4SEd Tanous { 200055713e4SEd Tanous messages::internalError(asyncResp->res); 201055713e4SEd Tanous return; 202055713e4SEd Tanous } 203055713e4SEd Tanous } 204055713e4SEd Tanous #endif 205055713e4SEd Tanous 206055713e4SEd Tanous // Seek to the beginning 207055713e4SEd Tanous if (sd_journal_seek_head(journal.get()) < 0) 208055713e4SEd Tanous { 209055713e4SEd Tanous messages::internalError(asyncResp->res); 210055713e4SEd Tanous return; 211055713e4SEd Tanous } 212055713e4SEd Tanous 213055713e4SEd Tanous // Get the first entry 214055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 215055713e4SEd Tanous { 216055713e4SEd Tanous messages::internalError(asyncResp->res); 217055713e4SEd Tanous return; 218055713e4SEd Tanous } 219055713e4SEd Tanous 220055713e4SEd Tanous // Get the first sequence number 221055713e4SEd Tanous uint64_t startSeqNum = 0; 222058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254 223055713e4SEd Tanous { 224055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0) 225055713e4SEd Tanous { 226055713e4SEd Tanous messages::internalError(asyncResp->res); 227055713e4SEd Tanous return; 228055713e4SEd Tanous } 229055713e4SEd Tanous } 230055713e4SEd Tanous #endif 231055713e4SEd Tanous 232055713e4SEd Tanous BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum, 233055713e4SEd Tanous endSeqNum); 234055713e4SEd Tanous 235055713e4SEd Tanous // Add 1 to account for the last entry 236055713e4SEd Tanous uint64_t totalEntries = endSeqNum - startSeqNum + 1; 237055713e4SEd Tanous asyncResp->res.jsonValue["Members@odata.count"] = totalEntries; 238055713e4SEd Tanous if (skip + top < totalEntries) 239b0983db2SEd Tanous { 24084177a2fSEd Tanous asyncResp->res.jsonValue["Members@odata.nextLink"] = 24184177a2fSEd Tanous boost::urls::format( 242b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 243b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 244b0983db2SEd Tanous } 245055713e4SEd Tanous uint64_t index = 0; 246055713e4SEd Tanous if (skip > 0) 247055713e4SEd Tanous { 248055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), skip) < 0) 249055713e4SEd Tanous { 250055713e4SEd Tanous messages::internalError(asyncResp->res); 251055713e4SEd Tanous return; 252055713e4SEd Tanous } 253055713e4SEd Tanous } 254055713e4SEd Tanous BMCWEB_LOG_DEBUG("Index was {}", index); 2558274eb11SEd Tanous readJournalEntries(top, asyncResp, {std::move(journal)}); 256b0983db2SEd Tanous } 257b0983db2SEd Tanous 25884177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet( 25984177a2fSEd Tanous App& app, const crow::Request& req, 260b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 26184177a2fSEd Tanous const std::string& managerId, const std::string& entryID) 26284177a2fSEd Tanous { 263b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 264b0983db2SEd Tanous { 265b0983db2SEd Tanous return; 266b0983db2SEd Tanous } 267b0983db2SEd Tanous 268b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 269b0983db2SEd Tanous { 270b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 271b0983db2SEd Tanous return; 272b0983db2SEd Tanous } 273b0983db2SEd Tanous 274b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 275b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 276b0983db2SEd Tanous if (ret < 0) 277b0983db2SEd Tanous { 2787da633f0SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", ret); 279b0983db2SEd Tanous messages::internalError(asyncResp->res); 280b0983db2SEd Tanous return; 281b0983db2SEd Tanous } 282b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 283b0983db2SEd Tanous journalTmp, sd_journal_close); 284b0983db2SEd Tanous journalTmp = nullptr; 2858274eb11SEd Tanous 2868274eb11SEd Tanous std::string cursor; 2878274eb11SEd Tanous if (!crow::utility::base64Decode(entryID, cursor)) 2888274eb11SEd Tanous { 2898274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 2908274eb11SEd Tanous return; 2918274eb11SEd Tanous } 2928274eb11SEd Tanous 2938274eb11SEd Tanous // Go to the cursor in the log 2948274eb11SEd Tanous ret = sd_journal_seek_cursor(journal.get(), cursor.c_str()); 295b0983db2SEd Tanous if (ret < 0) 296b0983db2SEd Tanous { 2978274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 2988274eb11SEd Tanous return; 2998274eb11SEd Tanous } 3008274eb11SEd Tanous 3018274eb11SEd Tanous if (sd_journal_next(journal.get()) < 0) 3028274eb11SEd Tanous { 303b0983db2SEd Tanous messages::internalError(asyncResp->res); 304b0983db2SEd Tanous return; 305b0983db2SEd Tanous } 306055713e4SEd Tanous 3078274eb11SEd Tanous ret = sd_journal_test_cursor(journal.get(), cursor.c_str()); 3088274eb11SEd Tanous if (ret == 0) 3098274eb11SEd Tanous { 3108274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3118274eb11SEd Tanous return; 3128274eb11SEd Tanous } 3138274eb11SEd Tanous if (ret < 0) 314b0983db2SEd Tanous { 315b0983db2SEd Tanous messages::internalError(asyncResp->res); 316b0983db2SEd Tanous return; 317b0983db2SEd Tanous } 318055713e4SEd Tanous 319b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 3208274eb11SEd Tanous if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry)) 321b0983db2SEd Tanous { 322b0983db2SEd Tanous messages::internalError(asyncResp->res); 323b0983db2SEd Tanous return; 324b0983db2SEd Tanous } 325b0983db2SEd Tanous asyncResp->res.jsonValue.update(bmcJournalLogEntry); 326055713e4SEd Tanous } 32784177a2fSEd Tanous 32884177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app) 32984177a2fSEd Tanous { 33084177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 33184177a2fSEd Tanous .privileges(redfish::privileges::getLogService) 33284177a2fSEd Tanous .methods(boost::beast::http::verb::get)( 33384177a2fSEd Tanous std::bind_front(handleManagersLogServiceJournalGet, std::ref(app))); 33484177a2fSEd Tanous 33584177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 33684177a2fSEd Tanous .privileges(redfish::privileges::getLogEntryCollection) 33784177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 33884177a2fSEd Tanous handleManagersJournalLogEntryCollectionGet, std::ref(app))); 33984177a2fSEd Tanous 34084177a2fSEd Tanous BMCWEB_ROUTE( 34184177a2fSEd Tanous app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 34284177a2fSEd Tanous .privileges(redfish::privileges::getLogEntry) 34384177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 34484177a2fSEd Tanous handleManagersJournalEntriesLogEntryGet, std::ref(app))); 345b0983db2SEd Tanous } 346b0983db2SEd Tanous } // namespace redfish 347