140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3b0983db2SEd Tanous #pragma once 4b0983db2SEd Tanous 5*d7857201SEd Tanous #include "bmcweb_config.h" 6*d7857201SEd Tanous 7b0983db2SEd Tanous #include "app.hpp" 8*d7857201SEd Tanous #include "async_resp.hpp" 9*d7857201SEd Tanous #include "dbus_singleton.hpp" 10b0983db2SEd Tanous #include "error_messages.hpp" 11*d7857201SEd Tanous #include "http_request.hpp" 12*d7857201SEd Tanous #include "logging.hpp" 13b0983db2SEd Tanous #include "query.hpp" 14b0983db2SEd Tanous #include "registries/privilege_registry.hpp" 15*d7857201SEd Tanous #include "utility.hpp" 1660e995cdSEd Tanous #include "utils/journal_utils.hpp" 17*d7857201SEd Tanous #include "utils/query_param.hpp" 18b0983db2SEd Tanous #include "utils/time_utils.hpp" 19b0983db2SEd Tanous 20b0983db2SEd Tanous #include <systemd/sd-journal.h> 21b0983db2SEd Tanous 22*d7857201SEd Tanous #include <boost/asio/post.hpp> 23b0983db2SEd Tanous #include <boost/beast/http/verb.hpp> 24*d7857201SEd Tanous #include <boost/url/format.hpp> 25b0983db2SEd Tanous 26*d7857201SEd Tanous #include <cstddef> 27*d7857201SEd Tanous #include <cstdint> 28*d7857201SEd Tanous #include <functional> 29b0983db2SEd Tanous #include <memory> 30b0983db2SEd Tanous #include <string> 31b0983db2SEd Tanous #include <string_view> 32*d7857201SEd Tanous #include <utility> 33b0983db2SEd Tanous 34b0983db2SEd Tanous namespace redfish 35b0983db2SEd Tanous { 36b0983db2SEd Tanous 3784177a2fSEd Tanous inline void handleManagersLogServiceJournalGet( 3884177a2fSEd Tanous App& app, const crow::Request& req, 39b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4084177a2fSEd Tanous const std::string& managerId) 4184177a2fSEd Tanous { 42b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 43b0983db2SEd Tanous { 44b0983db2SEd Tanous return; 45b0983db2SEd Tanous } 46b0983db2SEd Tanous 47b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 48b0983db2SEd Tanous { 49b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 50b0983db2SEd Tanous return; 51b0983db2SEd Tanous } 52b0983db2SEd Tanous 5384177a2fSEd Tanous asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 54b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = 55b0983db2SEd Tanous boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 56b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 57b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 58b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 59b0983db2SEd Tanous asyncResp->res.jsonValue["Id"] = "Journal"; 60b0983db2SEd Tanous asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 61b0983db2SEd Tanous 62b0983db2SEd Tanous std::pair<std::string, std::string> redfishDateTimeOffset = 63b0983db2SEd Tanous redfish::time_utils::getDateTimeOffsetNow(); 64b0983db2SEd Tanous asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 65b0983db2SEd Tanous asyncResp->res.jsonValue["DateTimeLocalOffset"] = 66b0983db2SEd Tanous redfishDateTimeOffset.second; 67b0983db2SEd Tanous 68b0983db2SEd Tanous asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 69b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 70b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 71b0983db2SEd Tanous } 72b0983db2SEd Tanous 73055713e4SEd Tanous struct JournalReadState 74055713e4SEd Tanous { 75055713e4SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal; 76055713e4SEd Tanous }; 77055713e4SEd Tanous 78bd79bce8SPatrick Williams inline void readJournalEntries( 79bd79bce8SPatrick Williams uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 80055713e4SEd Tanous JournalReadState&& readState) 81055713e4SEd Tanous { 82055713e4SEd Tanous nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"]; 83055713e4SEd Tanous nlohmann::json::array_t* logEntryArray = 84055713e4SEd Tanous logEntry.get_ptr<nlohmann::json::array_t*>(); 85055713e4SEd Tanous if (logEntryArray == nullptr) 86055713e4SEd Tanous { 87055713e4SEd Tanous messages::internalError(asyncResp->res); 88055713e4SEd Tanous return; 89055713e4SEd Tanous } 90055713e4SEd Tanous 91055713e4SEd Tanous // The Journal APIs unfortunately do blocking calls to the filesystem, and 92055713e4SEd Tanous // can be somewhat expensive. Short of creating our own io_uring based 93055713e4SEd Tanous // implementation of sd-journal, which would be difficult, the best thing we 94055713e4SEd Tanous // can do is to only parse a certain number of entries at a time. The 95055713e4SEd Tanous // current chunk size is selected arbitrarily to ensure that we're not 96055713e4SEd Tanous // trying to process thousands of entries at the same time. 97055713e4SEd Tanous // The implementation will process the number of entries, then return 98055713e4SEd Tanous // control to the io_context to let other operations continue. 99055713e4SEd Tanous size_t segmentCountRemaining = 10; 100055713e4SEd Tanous 101055713e4SEd Tanous // Reset the unique ID on the first entry 102055713e4SEd Tanous for (uint64_t entryCount = logEntryArray->size(); 103055713e4SEd Tanous entryCount < topEntryCount; entryCount++) 104055713e4SEd Tanous { 105055713e4SEd Tanous if (segmentCountRemaining == 0) 106055713e4SEd Tanous { 107055713e4SEd Tanous boost::asio::post(crow::connections::systemBus->get_io_context(), 108055713e4SEd Tanous [asyncResp, topEntryCount, 109055713e4SEd Tanous readState = std::move(readState)]() mutable { 110055713e4SEd Tanous readJournalEntries(topEntryCount, asyncResp, 111055713e4SEd Tanous std::move(readState)); 112055713e4SEd Tanous }); 113055713e4SEd Tanous return; 114055713e4SEd Tanous } 115055713e4SEd Tanous 116055713e4SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 1178274eb11SEd Tanous if (!fillBMCJournalLogEntryJson(readState.journal.get(), 118055713e4SEd Tanous bmcJournalLogEntry)) 119055713e4SEd Tanous { 120055713e4SEd Tanous messages::internalError(asyncResp->res); 121055713e4SEd Tanous return; 122055713e4SEd Tanous } 123055713e4SEd Tanous logEntryArray->emplace_back(std::move(bmcJournalLogEntry)); 124055713e4SEd Tanous 1258274eb11SEd Tanous int ret = sd_journal_next(readState.journal.get()); 126055713e4SEd Tanous if (ret < 0) 127055713e4SEd Tanous { 128055713e4SEd Tanous messages::internalError(asyncResp->res); 129055713e4SEd Tanous return; 130055713e4SEd Tanous } 131055713e4SEd Tanous if (ret == 0) 132055713e4SEd Tanous { 133055713e4SEd Tanous break; 134055713e4SEd Tanous } 135055713e4SEd Tanous segmentCountRemaining--; 136055713e4SEd Tanous } 137055713e4SEd Tanous } 138055713e4SEd Tanous 13984177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet( 14084177a2fSEd Tanous App& app, const crow::Request& req, 141b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 14284177a2fSEd Tanous const std::string& managerId) 14384177a2fSEd Tanous { 144b0983db2SEd Tanous query_param::QueryCapabilities capabilities = { 145b0983db2SEd Tanous .canDelegateTop = true, 146b0983db2SEd Tanous .canDelegateSkip = true, 147b0983db2SEd Tanous }; 148b0983db2SEd Tanous query_param::Query delegatedQuery; 14984177a2fSEd Tanous if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 15084177a2fSEd Tanous delegatedQuery, capabilities)) 151b0983db2SEd Tanous { 152b0983db2SEd Tanous return; 153b0983db2SEd Tanous } 154b0983db2SEd Tanous 155b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 156b0983db2SEd Tanous { 157b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 158b0983db2SEd Tanous return; 159b0983db2SEd Tanous } 160b0983db2SEd Tanous 161b0983db2SEd Tanous size_t skip = delegatedQuery.skip.value_or(0); 162b0983db2SEd Tanous size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 163b0983db2SEd Tanous 164b0983db2SEd Tanous // Collections don't include the static data added by SubRoute 165b0983db2SEd Tanous // because it has a duplicate entry for members 166b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 167b0983db2SEd Tanous "#LogEntryCollection.LogEntryCollection"; 168b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 169b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 170b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 171b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 172b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = 173b0983db2SEd Tanous "Collection of BMC Journal Entries"; 174055713e4SEd Tanous asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t(); 175b0983db2SEd Tanous 176b0983db2SEd Tanous // Go through the journal and use the timestamp to create a 177b0983db2SEd Tanous // unique ID for each entry 178b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 179b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 180b0983db2SEd Tanous if (ret < 0) 181b0983db2SEd Tanous { 1827da633f0SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", ret); 183b0983db2SEd Tanous messages::internalError(asyncResp->res); 184b0983db2SEd Tanous return; 185b0983db2SEd Tanous } 186055713e4SEd Tanous 187b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 188b0983db2SEd Tanous journalTmp, sd_journal_close); 189b0983db2SEd Tanous journalTmp = nullptr; 190b0983db2SEd Tanous 191055713e4SEd Tanous // Seek to the end 192055713e4SEd Tanous if (sd_journal_seek_tail(journal.get()) < 0) 193b0983db2SEd Tanous { 194b0983db2SEd Tanous messages::internalError(asyncResp->res); 195b0983db2SEd Tanous return; 196b0983db2SEd Tanous } 197055713e4SEd Tanous 198055713e4SEd Tanous // Get the last entry 199055713e4SEd Tanous if (sd_journal_previous(journal.get()) < 0) 200055713e4SEd Tanous { 201055713e4SEd Tanous messages::internalError(asyncResp->res); 202055713e4SEd Tanous return; 203b0983db2SEd Tanous } 204055713e4SEd Tanous 205055713e4SEd Tanous // Get the last sequence number 206055713e4SEd Tanous uint64_t endSeqNum = 0; 207058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254 208055713e4SEd Tanous { 209055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0) 210055713e4SEd Tanous { 211055713e4SEd Tanous messages::internalError(asyncResp->res); 212055713e4SEd Tanous return; 213055713e4SEd Tanous } 214055713e4SEd Tanous } 215055713e4SEd Tanous #endif 216055713e4SEd Tanous 217055713e4SEd Tanous // Seek to the beginning 218055713e4SEd Tanous if (sd_journal_seek_head(journal.get()) < 0) 219055713e4SEd Tanous { 220055713e4SEd Tanous messages::internalError(asyncResp->res); 221055713e4SEd Tanous return; 222055713e4SEd Tanous } 223055713e4SEd Tanous 224055713e4SEd Tanous // Get the first entry 225055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 226055713e4SEd Tanous { 227055713e4SEd Tanous messages::internalError(asyncResp->res); 228055713e4SEd Tanous return; 229055713e4SEd Tanous } 230055713e4SEd Tanous 231055713e4SEd Tanous // Get the first sequence number 232055713e4SEd Tanous uint64_t startSeqNum = 0; 233058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254 234055713e4SEd Tanous { 235055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0) 236055713e4SEd Tanous { 237055713e4SEd Tanous messages::internalError(asyncResp->res); 238055713e4SEd Tanous return; 239055713e4SEd Tanous } 240055713e4SEd Tanous } 241055713e4SEd Tanous #endif 242055713e4SEd Tanous 243055713e4SEd Tanous BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum, 244055713e4SEd Tanous endSeqNum); 245055713e4SEd Tanous 246055713e4SEd Tanous // Add 1 to account for the last entry 247055713e4SEd Tanous uint64_t totalEntries = endSeqNum - startSeqNum + 1; 248055713e4SEd Tanous asyncResp->res.jsonValue["Members@odata.count"] = totalEntries; 249055713e4SEd Tanous if (skip + top < totalEntries) 250b0983db2SEd Tanous { 25184177a2fSEd Tanous asyncResp->res.jsonValue["Members@odata.nextLink"] = 25284177a2fSEd Tanous boost::urls::format( 253b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 254b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 255b0983db2SEd Tanous } 256055713e4SEd Tanous uint64_t index = 0; 257055713e4SEd Tanous if (skip > 0) 258055713e4SEd Tanous { 259055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), skip) < 0) 260055713e4SEd Tanous { 261055713e4SEd Tanous messages::internalError(asyncResp->res); 262055713e4SEd Tanous return; 263055713e4SEd Tanous } 264055713e4SEd Tanous } 265055713e4SEd Tanous BMCWEB_LOG_DEBUG("Index was {}", index); 2668274eb11SEd Tanous readJournalEntries(top, asyncResp, {std::move(journal)}); 267b0983db2SEd Tanous } 268b0983db2SEd Tanous 26984177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet( 27084177a2fSEd Tanous App& app, const crow::Request& req, 271b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 27284177a2fSEd Tanous const std::string& managerId, const std::string& entryID) 27384177a2fSEd Tanous { 274b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 275b0983db2SEd Tanous { 276b0983db2SEd Tanous return; 277b0983db2SEd Tanous } 278b0983db2SEd Tanous 279b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 280b0983db2SEd Tanous { 281b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 282b0983db2SEd Tanous return; 283b0983db2SEd Tanous } 284b0983db2SEd Tanous 285b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 286b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 287b0983db2SEd Tanous if (ret < 0) 288b0983db2SEd Tanous { 2897da633f0SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", ret); 290b0983db2SEd Tanous messages::internalError(asyncResp->res); 291b0983db2SEd Tanous return; 292b0983db2SEd Tanous } 293b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 294b0983db2SEd Tanous journalTmp, sd_journal_close); 295b0983db2SEd Tanous journalTmp = nullptr; 2968274eb11SEd Tanous 2978274eb11SEd Tanous std::string cursor; 2988274eb11SEd Tanous if (!crow::utility::base64Decode(entryID, cursor)) 2998274eb11SEd Tanous { 3008274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3018274eb11SEd Tanous return; 3028274eb11SEd Tanous } 3038274eb11SEd Tanous 3048274eb11SEd Tanous // Go to the cursor in the log 3058274eb11SEd Tanous ret = sd_journal_seek_cursor(journal.get(), cursor.c_str()); 306b0983db2SEd Tanous if (ret < 0) 307b0983db2SEd Tanous { 3088274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3098274eb11SEd Tanous return; 3108274eb11SEd Tanous } 3118274eb11SEd Tanous 3128274eb11SEd Tanous if (sd_journal_next(journal.get()) < 0) 3138274eb11SEd Tanous { 314b0983db2SEd Tanous messages::internalError(asyncResp->res); 315b0983db2SEd Tanous return; 316b0983db2SEd Tanous } 317055713e4SEd Tanous 3188274eb11SEd Tanous ret = sd_journal_test_cursor(journal.get(), cursor.c_str()); 3198274eb11SEd Tanous if (ret == 0) 3208274eb11SEd Tanous { 3218274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3228274eb11SEd Tanous return; 3238274eb11SEd Tanous } 3248274eb11SEd Tanous if (ret < 0) 325b0983db2SEd Tanous { 326b0983db2SEd Tanous messages::internalError(asyncResp->res); 327b0983db2SEd Tanous return; 328b0983db2SEd Tanous } 329055713e4SEd Tanous 330b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 3318274eb11SEd Tanous if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry)) 332b0983db2SEd Tanous { 333b0983db2SEd Tanous messages::internalError(asyncResp->res); 334b0983db2SEd Tanous return; 335b0983db2SEd Tanous } 336b0983db2SEd Tanous asyncResp->res.jsonValue.update(bmcJournalLogEntry); 337055713e4SEd Tanous } 33884177a2fSEd Tanous 33984177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app) 34084177a2fSEd Tanous { 34184177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 34284177a2fSEd Tanous .privileges(redfish::privileges::getLogService) 34384177a2fSEd Tanous .methods(boost::beast::http::verb::get)( 34484177a2fSEd Tanous std::bind_front(handleManagersLogServiceJournalGet, std::ref(app))); 34584177a2fSEd Tanous 34684177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 34784177a2fSEd Tanous .privileges(redfish::privileges::getLogEntryCollection) 34884177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 34984177a2fSEd Tanous handleManagersJournalLogEntryCollectionGet, std::ref(app))); 35084177a2fSEd Tanous 35184177a2fSEd Tanous BMCWEB_ROUTE( 35284177a2fSEd Tanous app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 35384177a2fSEd Tanous .privileges(redfish::privileges::getLogEntry) 35484177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 35584177a2fSEd Tanous handleManagersJournalEntriesLogEntryGet, std::ref(app))); 356b0983db2SEd Tanous } 357b0983db2SEd Tanous } // namespace redfish 358