140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3b0983db2SEd Tanous #pragma once 4b0983db2SEd Tanous 5d7857201SEd Tanous #include "bmcweb_config.h" 6d7857201SEd Tanous 7b0983db2SEd Tanous #include "app.hpp" 8d7857201SEd Tanous #include "async_resp.hpp" 9d7857201SEd Tanous #include "dbus_singleton.hpp" 10b0983db2SEd Tanous #include "error_messages.hpp" 11d7857201SEd Tanous #include "http_request.hpp" 12d7857201SEd Tanous #include "logging.hpp" 13b0983db2SEd Tanous #include "query.hpp" 14b0983db2SEd Tanous #include "registries/privilege_registry.hpp" 15d7857201SEd Tanous #include "utility.hpp" 16*08fad5d9SCorey Ethington #include "utils/etag_utils.hpp" 1760e995cdSEd Tanous #include "utils/journal_utils.hpp" 18d7857201SEd Tanous #include "utils/query_param.hpp" 19b0983db2SEd Tanous #include "utils/time_utils.hpp" 20b0983db2SEd Tanous 21b0983db2SEd Tanous #include <systemd/sd-journal.h> 22b0983db2SEd Tanous 23d7857201SEd Tanous #include <boost/asio/post.hpp> 24b0983db2SEd Tanous #include <boost/beast/http/verb.hpp> 25d7857201SEd Tanous #include <boost/url/format.hpp> 26b0983db2SEd Tanous 27d7857201SEd Tanous #include <cstddef> 28d7857201SEd Tanous #include <cstdint> 29d7857201SEd Tanous #include <functional> 30b0983db2SEd Tanous #include <memory> 31b0983db2SEd Tanous #include <string> 32b0983db2SEd Tanous #include <string_view> 33d7857201SEd Tanous #include <utility> 34b0983db2SEd Tanous 35b0983db2SEd Tanous namespace redfish 36b0983db2SEd Tanous { 37b0983db2SEd Tanous 3884177a2fSEd Tanous inline void handleManagersLogServiceJournalGet( 3984177a2fSEd Tanous App& app, const crow::Request& req, 40b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 4184177a2fSEd Tanous const std::string& managerId) 4284177a2fSEd Tanous { 43b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 44b0983db2SEd Tanous { 45b0983db2SEd Tanous return; 46b0983db2SEd Tanous } 47b0983db2SEd Tanous 48b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 49b0983db2SEd Tanous { 50b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 51b0983db2SEd Tanous return; 52b0983db2SEd Tanous } 53b0983db2SEd Tanous 5484177a2fSEd Tanous asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; 55b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = 56b0983db2SEd Tanous boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", 57b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 58b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; 59b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; 60b0983db2SEd Tanous asyncResp->res.jsonValue["Id"] = "Journal"; 61b0983db2SEd Tanous asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 62b0983db2SEd Tanous 63b0983db2SEd Tanous std::pair<std::string, std::string> redfishDateTimeOffset = 64b0983db2SEd Tanous redfish::time_utils::getDateTimeOffsetNow(); 65b0983db2SEd Tanous asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; 66b0983db2SEd Tanous asyncResp->res.jsonValue["DateTimeLocalOffset"] = 67b0983db2SEd Tanous redfishDateTimeOffset.second; 68b0983db2SEd Tanous 69b0983db2SEd Tanous asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( 70b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 71b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 72*08fad5d9SCorey Ethington 73*08fad5d9SCorey Ethington etag_utils::setEtagOmitDateTimeHandler(asyncResp); 74b0983db2SEd Tanous } 75b0983db2SEd Tanous 76055713e4SEd Tanous struct JournalReadState 77055713e4SEd Tanous { 78055713e4SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal; 79055713e4SEd Tanous }; 80055713e4SEd Tanous 81bd79bce8SPatrick Williams inline void readJournalEntries( 82bd79bce8SPatrick Williams uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 83055713e4SEd Tanous JournalReadState&& readState) 84055713e4SEd Tanous { 85055713e4SEd Tanous nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"]; 86055713e4SEd Tanous nlohmann::json::array_t* logEntryArray = 87055713e4SEd Tanous logEntry.get_ptr<nlohmann::json::array_t*>(); 88055713e4SEd Tanous if (logEntryArray == nullptr) 89055713e4SEd Tanous { 90055713e4SEd Tanous messages::internalError(asyncResp->res); 91055713e4SEd Tanous return; 92055713e4SEd Tanous } 93055713e4SEd Tanous 94055713e4SEd Tanous // The Journal APIs unfortunately do blocking calls to the filesystem, and 95055713e4SEd Tanous // can be somewhat expensive. Short of creating our own io_uring based 96055713e4SEd Tanous // implementation of sd-journal, which would be difficult, the best thing we 97055713e4SEd Tanous // can do is to only parse a certain number of entries at a time. The 98055713e4SEd Tanous // current chunk size is selected arbitrarily to ensure that we're not 99055713e4SEd Tanous // trying to process thousands of entries at the same time. 100055713e4SEd Tanous // The implementation will process the number of entries, then return 101055713e4SEd Tanous // control to the io_context to let other operations continue. 102055713e4SEd Tanous size_t segmentCountRemaining = 10; 103055713e4SEd Tanous 104055713e4SEd Tanous // Reset the unique ID on the first entry 105055713e4SEd Tanous for (uint64_t entryCount = logEntryArray->size(); 106055713e4SEd Tanous entryCount < topEntryCount; entryCount++) 107055713e4SEd Tanous { 108055713e4SEd Tanous if (segmentCountRemaining == 0) 109055713e4SEd Tanous { 110055713e4SEd Tanous boost::asio::post(crow::connections::systemBus->get_io_context(), 111055713e4SEd Tanous [asyncResp, topEntryCount, 112055713e4SEd Tanous readState = std::move(readState)]() mutable { 113055713e4SEd Tanous readJournalEntries(topEntryCount, asyncResp, 114055713e4SEd Tanous std::move(readState)); 115055713e4SEd Tanous }); 116055713e4SEd Tanous return; 117055713e4SEd Tanous } 118055713e4SEd Tanous 119055713e4SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 1208274eb11SEd Tanous if (!fillBMCJournalLogEntryJson(readState.journal.get(), 121055713e4SEd Tanous bmcJournalLogEntry)) 122055713e4SEd Tanous { 123055713e4SEd Tanous messages::internalError(asyncResp->res); 124055713e4SEd Tanous return; 125055713e4SEd Tanous } 126055713e4SEd Tanous logEntryArray->emplace_back(std::move(bmcJournalLogEntry)); 127055713e4SEd Tanous 1288274eb11SEd Tanous int ret = sd_journal_next(readState.journal.get()); 129055713e4SEd Tanous if (ret < 0) 130055713e4SEd Tanous { 131055713e4SEd Tanous messages::internalError(asyncResp->res); 132055713e4SEd Tanous return; 133055713e4SEd Tanous } 134055713e4SEd Tanous if (ret == 0) 135055713e4SEd Tanous { 136055713e4SEd Tanous break; 137055713e4SEd Tanous } 138055713e4SEd Tanous segmentCountRemaining--; 139055713e4SEd Tanous } 140055713e4SEd Tanous } 141055713e4SEd Tanous 14284177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet( 14384177a2fSEd Tanous App& app, const crow::Request& req, 144b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 14584177a2fSEd Tanous const std::string& managerId) 14684177a2fSEd Tanous { 147b0983db2SEd Tanous query_param::QueryCapabilities capabilities = { 148b0983db2SEd Tanous .canDelegateTop = true, 149b0983db2SEd Tanous .canDelegateSkip = true, 150b0983db2SEd Tanous }; 151b0983db2SEd Tanous query_param::Query delegatedQuery; 15284177a2fSEd Tanous if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 15384177a2fSEd Tanous delegatedQuery, capabilities)) 154b0983db2SEd Tanous { 155b0983db2SEd Tanous return; 156b0983db2SEd Tanous } 157b0983db2SEd Tanous 158b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 159b0983db2SEd Tanous { 160b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 161b0983db2SEd Tanous return; 162b0983db2SEd Tanous } 163b0983db2SEd Tanous 164b0983db2SEd Tanous size_t skip = delegatedQuery.skip.value_or(0); 165b0983db2SEd Tanous size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); 166b0983db2SEd Tanous 167b0983db2SEd Tanous // Collections don't include the static data added by SubRoute 168b0983db2SEd Tanous // because it has a duplicate entry for members 169b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.type"] = 170b0983db2SEd Tanous "#LogEntryCollection.LogEntryCollection"; 171b0983db2SEd Tanous asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 172b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries", 173b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME); 174b0983db2SEd Tanous asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; 175b0983db2SEd Tanous asyncResp->res.jsonValue["Description"] = 176b0983db2SEd Tanous "Collection of BMC Journal Entries"; 177055713e4SEd Tanous asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t(); 178b0983db2SEd Tanous 179b0983db2SEd Tanous // Go through the journal and use the timestamp to create a 180b0983db2SEd Tanous // unique ID for each entry 181b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 182b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 183b0983db2SEd Tanous if (ret < 0) 184b0983db2SEd Tanous { 1857da633f0SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", ret); 186b0983db2SEd Tanous messages::internalError(asyncResp->res); 187b0983db2SEd Tanous return; 188b0983db2SEd Tanous } 189055713e4SEd Tanous 190b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 191b0983db2SEd Tanous journalTmp, sd_journal_close); 192b0983db2SEd Tanous journalTmp = nullptr; 193b0983db2SEd Tanous 194055713e4SEd Tanous // Seek to the end 195055713e4SEd Tanous if (sd_journal_seek_tail(journal.get()) < 0) 196b0983db2SEd Tanous { 197b0983db2SEd Tanous messages::internalError(asyncResp->res); 198b0983db2SEd Tanous return; 199b0983db2SEd Tanous } 200055713e4SEd Tanous 201055713e4SEd Tanous // Get the last entry 202055713e4SEd Tanous if (sd_journal_previous(journal.get()) < 0) 203055713e4SEd Tanous { 204055713e4SEd Tanous messages::internalError(asyncResp->res); 205055713e4SEd Tanous return; 206b0983db2SEd Tanous } 207055713e4SEd Tanous 208055713e4SEd Tanous // Get the last sequence number 209055713e4SEd Tanous uint64_t endSeqNum = 0; 210058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254 211055713e4SEd Tanous { 212055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0) 213055713e4SEd Tanous { 214055713e4SEd Tanous messages::internalError(asyncResp->res); 215055713e4SEd Tanous return; 216055713e4SEd Tanous } 217055713e4SEd Tanous } 218055713e4SEd Tanous #endif 219055713e4SEd Tanous 220055713e4SEd Tanous // Seek to the beginning 221055713e4SEd Tanous if (sd_journal_seek_head(journal.get()) < 0) 222055713e4SEd Tanous { 223055713e4SEd Tanous messages::internalError(asyncResp->res); 224055713e4SEd Tanous return; 225055713e4SEd Tanous } 226055713e4SEd Tanous 227055713e4SEd Tanous // Get the first entry 228055713e4SEd Tanous if (sd_journal_next(journal.get()) < 0) 229055713e4SEd Tanous { 230055713e4SEd Tanous messages::internalError(asyncResp->res); 231055713e4SEd Tanous return; 232055713e4SEd Tanous } 233055713e4SEd Tanous 234055713e4SEd Tanous // Get the first sequence number 235055713e4SEd Tanous uint64_t startSeqNum = 0; 236058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254 237055713e4SEd Tanous { 238055713e4SEd Tanous if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0) 239055713e4SEd Tanous { 240055713e4SEd Tanous messages::internalError(asyncResp->res); 241055713e4SEd Tanous return; 242055713e4SEd Tanous } 243055713e4SEd Tanous } 244055713e4SEd Tanous #endif 245055713e4SEd Tanous 246055713e4SEd Tanous BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum, 247055713e4SEd Tanous endSeqNum); 248055713e4SEd Tanous 249055713e4SEd Tanous // Add 1 to account for the last entry 250055713e4SEd Tanous uint64_t totalEntries = endSeqNum - startSeqNum + 1; 251055713e4SEd Tanous asyncResp->res.jsonValue["Members@odata.count"] = totalEntries; 252055713e4SEd Tanous if (skip + top < totalEntries) 253b0983db2SEd Tanous { 25484177a2fSEd Tanous asyncResp->res.jsonValue["Members@odata.nextLink"] = 25584177a2fSEd Tanous boost::urls::format( 256b0983db2SEd Tanous "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", 257b0983db2SEd Tanous BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); 258b0983db2SEd Tanous } 259055713e4SEd Tanous uint64_t index = 0; 260055713e4SEd Tanous if (skip > 0) 261055713e4SEd Tanous { 262055713e4SEd Tanous if (sd_journal_next_skip(journal.get(), skip) < 0) 263055713e4SEd Tanous { 264055713e4SEd Tanous messages::internalError(asyncResp->res); 265055713e4SEd Tanous return; 266055713e4SEd Tanous } 267055713e4SEd Tanous } 268055713e4SEd Tanous BMCWEB_LOG_DEBUG("Index was {}", index); 2698274eb11SEd Tanous readJournalEntries(top, asyncResp, {std::move(journal)}); 270b0983db2SEd Tanous } 271b0983db2SEd Tanous 27284177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet( 27384177a2fSEd Tanous App& app, const crow::Request& req, 274b0983db2SEd Tanous const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 27584177a2fSEd Tanous const std::string& managerId, const std::string& entryID) 27684177a2fSEd Tanous { 277b0983db2SEd Tanous if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 278b0983db2SEd Tanous { 279b0983db2SEd Tanous return; 280b0983db2SEd Tanous } 281b0983db2SEd Tanous 282b0983db2SEd Tanous if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 283b0983db2SEd Tanous { 284b0983db2SEd Tanous messages::resourceNotFound(asyncResp->res, "Manager", managerId); 285b0983db2SEd Tanous return; 286b0983db2SEd Tanous } 287b0983db2SEd Tanous 288b0983db2SEd Tanous sd_journal* journalTmp = nullptr; 289b0983db2SEd Tanous int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); 290b0983db2SEd Tanous if (ret < 0) 291b0983db2SEd Tanous { 2927da633f0SEd Tanous BMCWEB_LOG_ERROR("failed to open journal: {}", ret); 293b0983db2SEd Tanous messages::internalError(asyncResp->res); 294b0983db2SEd Tanous return; 295b0983db2SEd Tanous } 296b0983db2SEd Tanous std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( 297b0983db2SEd Tanous journalTmp, sd_journal_close); 298b0983db2SEd Tanous journalTmp = nullptr; 2998274eb11SEd Tanous 3008274eb11SEd Tanous std::string cursor; 3018274eb11SEd Tanous if (!crow::utility::base64Decode(entryID, cursor)) 3028274eb11SEd Tanous { 3038274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3048274eb11SEd Tanous return; 3058274eb11SEd Tanous } 3068274eb11SEd Tanous 3078274eb11SEd Tanous // Go to the cursor in the log 3088274eb11SEd Tanous ret = sd_journal_seek_cursor(journal.get(), cursor.c_str()); 309b0983db2SEd Tanous if (ret < 0) 310b0983db2SEd Tanous { 3118274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3128274eb11SEd Tanous return; 3138274eb11SEd Tanous } 3148274eb11SEd Tanous 3158274eb11SEd Tanous if (sd_journal_next(journal.get()) < 0) 3168274eb11SEd Tanous { 317b0983db2SEd Tanous messages::internalError(asyncResp->res); 318b0983db2SEd Tanous return; 319b0983db2SEd Tanous } 320055713e4SEd Tanous 3218274eb11SEd Tanous ret = sd_journal_test_cursor(journal.get(), cursor.c_str()); 3228274eb11SEd Tanous if (ret == 0) 3238274eb11SEd Tanous { 3248274eb11SEd Tanous messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); 3258274eb11SEd Tanous return; 3268274eb11SEd Tanous } 3278274eb11SEd Tanous if (ret < 0) 328b0983db2SEd Tanous { 329b0983db2SEd Tanous messages::internalError(asyncResp->res); 330b0983db2SEd Tanous return; 331b0983db2SEd Tanous } 332055713e4SEd Tanous 333b0983db2SEd Tanous nlohmann::json::object_t bmcJournalLogEntry; 3348274eb11SEd Tanous if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry)) 335b0983db2SEd Tanous { 336b0983db2SEd Tanous messages::internalError(asyncResp->res); 337b0983db2SEd Tanous return; 338b0983db2SEd Tanous } 339b0983db2SEd Tanous asyncResp->res.jsonValue.update(bmcJournalLogEntry); 340055713e4SEd Tanous } 34184177a2fSEd Tanous 34284177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app) 34384177a2fSEd Tanous { 34484177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") 34584177a2fSEd Tanous .privileges(redfish::privileges::getLogService) 34684177a2fSEd Tanous .methods(boost::beast::http::verb::get)( 34784177a2fSEd Tanous std::bind_front(handleManagersLogServiceJournalGet, std::ref(app))); 34884177a2fSEd Tanous 34984177a2fSEd Tanous BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") 35084177a2fSEd Tanous .privileges(redfish::privileges::getLogEntryCollection) 35184177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 35284177a2fSEd Tanous handleManagersJournalLogEntryCollectionGet, std::ref(app))); 35384177a2fSEd Tanous 35484177a2fSEd Tanous BMCWEB_ROUTE( 35584177a2fSEd Tanous app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") 35684177a2fSEd Tanous .privileges(redfish::privileges::getLogEntry) 35784177a2fSEd Tanous .methods(boost::beast::http::verb::get)(std::bind_front( 35884177a2fSEd Tanous handleManagersJournalEntriesLogEntryGet, std::ref(app))); 359b0983db2SEd Tanous } 360b0983db2SEd Tanous } // namespace redfish 361