diff options
Diffstat (limited to 'redfish-core/lib')
-rw-r--r-- | redfish-core/lib/account_service.hpp | 136 | ||||
-rw-r--r-- | redfish-core/lib/event_service.hpp | 20 | ||||
-rw-r--r-- | redfish-core/lib/eventservice_sse.hpp | 26 | ||||
-rw-r--r-- | redfish-core/lib/log_services.hpp | 456 | ||||
-rw-r--r-- | redfish-core/lib/manager_logservices_journal.hpp | 659 | ||||
-rw-r--r-- | redfish-core/lib/managers.hpp | 25 | ||||
-rw-r--r-- | redfish-core/lib/redfish_sessions.hpp | 14 | ||||
-rw-r--r-- | redfish-core/lib/redfish_v1.hpp | 143 | ||||
-rw-r--r-- | redfish-core/lib/systems.hpp | 6 | ||||
-rw-r--r-- | redfish-core/lib/task.hpp | 58 | ||||
-rw-r--r-- | redfish-core/lib/update_service.hpp | 123 | ||||
-rw-r--r-- | redfish-core/lib/virtual_media.hpp | 2 |
12 files changed, 1053 insertions, 615 deletions
diff --git a/redfish-core/lib/account_service.hpp b/redfish-core/lib/account_service.hpp index 05cfbe762d..aa0678272f 100644 --- a/redfish-core/lib/account_service.hpp +++ b/redfish-core/lib/account_service.hpp @@ -23,6 +23,7 @@ #include "persistent_data.hpp" #include "query.hpp" #include "registries/privilege_registry.hpp" +#include "sessions.hpp" #include "utils/collection.hpp" #include "utils/dbus_utils.hpp" #include "utils/json_utils.hpp" @@ -439,12 +440,21 @@ inline void handleRoleMapPatch( // If "LocalRole" info is provided if (localRole) { + std::string priv = getPrivilegeFromRoleId(*localRole); + if (priv.empty()) + { + messages::propertyValueNotInList( + asyncResp->res, *localRole, + std::format("RemoteRoleMapping/{}/LocalRole", + index)); + return; + } setDbusProperty( asyncResp, std::format("RemoteRoleMapping/{}/LocalRole", index), ldapDbusService, roleMapObjData[index].first, "xyz.openbmc_project.User.PrivilegeMapperEntry", - "Privilege", *localRole); + "Privilege", priv); } } // Create a new RoleMapping Object. @@ -1257,6 +1267,45 @@ inline void handleAccountServiceClientCertificatesGet( getClientCertificates(asyncResp, "/Members"_json_pointer); } +using account_service::CertificateMappingAttribute; +using persistent_data::MTLSCommonNameParseMode; +inline CertificateMappingAttribute + getCertificateMapping(MTLSCommonNameParseMode parse) +{ + switch (parse) + { + case MTLSCommonNameParseMode::CommonName: + { + return CertificateMappingAttribute::CommonName; + } + break; + case MTLSCommonNameParseMode::Whole: + { + return CertificateMappingAttribute::Whole; + } + break; + case MTLSCommonNameParseMode::UserPrincipalName: + { + return CertificateMappingAttribute::UserPrincipalName; + } + break; + + case MTLSCommonNameParseMode::Meta: + { + if constexpr (BMCWEB_META_TLS_COMMON_NAME_PARSING) + { + return CertificateMappingAttribute::CommonName; + } + } + break; + default: + { + return CertificateMappingAttribute::Invalid; + } + break; + } +} + inline void handleAccountServiceGet(App& app, const crow::Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) @@ -1300,9 +1349,21 @@ inline void nlohmann::json::object_t clientCertificate; clientCertificate["Enabled"] = authMethodsConfig.tls; - clientCertificate["RespondToUnauthenticatedClients"] = true; - clientCertificate["CertificateMappingAttribute"] = - account_service::CertificateMappingAttribute::CommonName; + clientCertificate["RespondToUnauthenticatedClients"] = + !authMethodsConfig.tlsStrict; + + using account_service::CertificateMappingAttribute; + + CertificateMappingAttribute mapping = + getCertificateMapping(authMethodsConfig.mTLSCommonNameParsingMode); + if (mapping == CertificateMappingAttribute::Invalid) + { + messages::internalError(asyncResp->res); + } + else + { + clientCertificate["CertificateMappingAttribute"] = mapping; + } nlohmann::json::object_t certificates; certificates["@odata.id"] = "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates"; @@ -1400,6 +1461,56 @@ inline void getLDAPConfigData("ActiveDirectory", callback); } +inline void + handleCertificateMappingAttributePatch(crow::Response& res, + const std::string& certMapAttribute) +{ + MTLSCommonNameParseMode parseMode = + persistent_data::getMTLSCommonNameParseMode(certMapAttribute); + if (parseMode == MTLSCommonNameParseMode::Invalid) + { + messages::propertyValueNotInList(res, "CertificateMappingAttribute", + certMapAttribute); + return; + } + + persistent_data::AuthConfigMethods& authMethodsConfig = + persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); + authMethodsConfig.mTLSCommonNameParsingMode = parseMode; +} + +inline void handleRespondToUnauthenticatedClientsPatch( + App& app, const crow::Request& req, crow::Response& res, + bool respondToUnauthenticatedClients) +{ + if (req.session != nullptr) + { + // Sanity check. If the user isn't currently authenticated with mutual + // TLS, they very likely are about to permanently lock themselves out. + // Make sure they're using mutual TLS before allowing locking. + if (req.session->sessionType != persistent_data::SessionType::MutualTLS) + { + messages::propertyValueExternalConflict( + res, + "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients", + respondToUnauthenticatedClients); + return; + } + } + + persistent_data::AuthConfigMethods& authMethodsConfig = + persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); + + // Change the settings + authMethodsConfig.tlsStrict = !respondToUnauthenticatedClients; + + // Write settings to disk + persistent_data::getConfig().writeData(); + + // Trigger a reload, to apply the new settings to new connections + app.loadCertificate(); +} + inline void handleAccountServicePatch( App& app, const crow::Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) @@ -1413,9 +1524,12 @@ inline void handleAccountServicePatch( std::optional<uint8_t> minPasswordLength; std::optional<uint16_t> maxPasswordLength; LdapPatchParams ldapObject; + std::optional<std::string> certificateMappingAttribute; + std::optional<bool> respondToUnauthenticatedClients; LdapPatchParams activeDirectoryObject; AuthMethods auth; std::optional<std::string> httpBasicAuth; + // clang-format off if (!json_util::readJsonPatch( req, asyncResp->res, @@ -1430,6 +1544,8 @@ inline void handleAccountServicePatch( "ActiveDirectory/RemoteRoleMapping", activeDirectoryObject.remoteRoleMapData, "ActiveDirectory/ServiceAddresses", activeDirectoryObject.serviceAddressList, "ActiveDirectory/ServiceEnabled", activeDirectoryObject.serviceEnabled, + "MultiFactorAuth/ClientCertificate/CertificateMappingAttribute", certificateMappingAttribute, + "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients", respondToUnauthenticatedClients, "LDAP/Authentication/AuthenticationType", ldapObject.authType, "LDAP/Authentication/Password", ldapObject.password, "LDAP/Authentication/Username", ldapObject.userName, @@ -1469,6 +1585,18 @@ inline void handleAccountServicePatch( } } + if (respondToUnauthenticatedClients) + { + handleRespondToUnauthenticatedClientsPatch( + app, req, asyncResp->res, *respondToUnauthenticatedClients); + } + + if (certificateMappingAttribute) + { + handleCertificateMappingAttributePatch(asyncResp->res, + *certificateMappingAttribute); + } + if (minPasswordLength) { setDbusProperty( diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp index aebc2824b0..7307571703 100644 --- a/redfish-core/lib/event_service.hpp +++ b/redfish-core/lib/event_service.hpp @@ -278,6 +278,7 @@ inline void requestRoutesEventDestinationCollection(App& app) } std::string destUrl; std::string protocol; + std::optional<bool> verifyCertificate; std::optional<std::string> context; std::optional<std::string> subscriptionType; std::optional<std::string> eventFormatType2; @@ -294,7 +295,8 @@ inline void requestRoutesEventDestinationCollection(App& app) "EventFormatType", eventFormatType2, "HttpHeaders", headers, "RegistryPrefixes", regPrefixes, "MessageIds", msgIds, "DeliveryRetryPolicy", retryPolicy, "MetricReportDefinitions", - mrdJsonArray, "ResourceTypes", resTypes)) + mrdJsonArray, "ResourceTypes", resTypes, "VerifyCertificate", + verifyCertificate)) { return; } @@ -433,6 +435,11 @@ inline void requestRoutesEventDestinationCollection(App& app) } subValue->protocol = protocol; + if (verifyCertificate) + { + subValue->verifyCertificate = *verifyCertificate; + } + if (eventFormatType2) { if (std::ranges::find(supportedEvtFormatTypes, *eventFormatType2) == @@ -612,7 +619,7 @@ inline void requestRoutesEventDestinationCollection(App& app) } std::string id = - EventServiceManager::getInstance().addSubscription(subValue); + EventServiceManager::getInstance().addPushSubscription(subValue); if (id.empty()) { messages::internalError(asyncResp->res); @@ -672,6 +679,8 @@ inline void requestRoutesEventDestination(App& app) asyncResp->res.jsonValue["MessageIds"] = subValue->registryMsgIds; asyncResp->res.jsonValue["DeliveryRetryPolicy"] = subValue->retryPolicy; + asyncResp->res.jsonValue["VerifyCertificate"] = + subValue->verifyCertificate; nlohmann::json::array_t mrdJsonArray; for (const auto& mdrUri : subValue->metricReportDefinitions) @@ -706,9 +715,11 @@ inline void requestRoutesEventDestination(App& app) std::optional<std::string> context; std::optional<std::string> retryPolicy; + std::optional<bool> verifyCertificate; std::optional<std::vector<nlohmann::json::object_t>> headers; if (!json_util::readJsonPatch(req, asyncResp->res, "Context", context, + "VerifyCertificate", verifyCertificate, "DeliveryRetryPolicy", retryPolicy, "HttpHeaders", headers)) { @@ -754,6 +765,11 @@ inline void requestRoutesEventDestination(App& app) subValue->retryPolicy = *retryPolicy; } + if (verifyCertificate) + { + subValue->verifyCertificate = *verifyCertificate; + } + EventServiceManager::getInstance().updateSubscriptionData(); }); BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/<str>/") diff --git a/redfish-core/lib/eventservice_sse.hpp b/redfish-core/lib/eventservice_sse.hpp index 3cbca3bc8b..c0a9527e94 100644 --- a/redfish-core/lib/eventservice_sse.hpp +++ b/redfish-core/lib/eventservice_sse.hpp @@ -1,5 +1,6 @@ #pragma once +#include "filter_expr_executor.hpp" #include "privileges.hpp" #include "registries/privilege_registry.hpp" @@ -12,7 +13,8 @@ namespace redfish { -inline void createSubscription(crow::sse_socket::Connection& conn) +inline void createSubscription(crow::sse_socket::Connection& conn, + const crow::Request& req) { EventServiceManager& manager = EventServiceManager::getInstance(&conn.getIoContext()); @@ -23,6 +25,25 @@ inline void createSubscription(crow::sse_socket::Connection& conn) conn.close("Max SSE subscriptions reached"); return; } + + std::optional<filter_ast::LogicalAnd> filter; + + boost::urls::params_base::iterator filterIt = + req.url().params().find("$filter"); + + if (filterIt != req.url().params().end()) + { + std::string_view filterValue = (*filterIt).value; + filter = parseFilter(filterValue); + if (!filter) + { + conn.close(std::format("Bad $filter param: {}", filterValue)); + return; + } + } + + std::string lastEventId(req.getHeaderValue("Last-Event-Id")); + std::shared_ptr<redfish::Subscription> subValue = std::make_shared<redfish::Subscription>(conn); @@ -32,8 +53,9 @@ inline void createSubscription(crow::sse_socket::Connection& conn) subValue->protocol = "Redfish"; subValue->retryPolicy = "TerminateAfterRetries"; subValue->eventFormatType = "Event"; + subValue->filter = filter; - std::string id = manager.addSubscription(subValue, false); + std::string id = manager.addSSESubscription(subValue, lastEventId); if (id.empty()) { conn.close("Internal Error"); diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp index da2d1b55ad..540a1be98e 100644 --- a/redfish-core/lib/log_services.hpp +++ b/redfish-core/lib/log_services.hpp @@ -34,7 +34,6 @@ #include "utils/time_utils.hpp" #include <systemd/sd-id128.h> -#include <systemd/sd-journal.h> #include <tinyxml2.h> #include <unistd.h> @@ -125,110 +124,7 @@ inline std::string getDumpPath(std::string_view dumpType) return dbusDumpPath; } -inline int getJournalMetadata(sd_journal* journal, std::string_view field, - std::string_view& contents) -{ - const char* data = nullptr; - size_t length = 0; - int ret = 0; - // Get the metadata from the requested field of the journal entry - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - const void** dataVoid = reinterpret_cast<const void**>(&data); - - ret = sd_journal_get_data(journal, field.data(), dataVoid, &length); - if (ret < 0) - { - return ret; - } - contents = std::string_view(data, length); - // Only use the content after the "=" character. - contents.remove_prefix(std::min(contents.find('=') + 1, contents.size())); - return ret; -} - -inline int getJournalMetadata(sd_journal* journal, std::string_view field, - const int& base, long int& contents) -{ - int ret = 0; - std::string_view metadata; - // Get the metadata from the requested field of the journal entry - ret = getJournalMetadata(journal, field, metadata); - if (ret < 0) - { - return ret; - } - contents = strtol(metadata.data(), nullptr, base); - return ret; -} - -inline bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) -{ - int ret = 0; - uint64_t timestamp = 0; - ret = sd_journal_get_realtime_usec(journal, ×tamp); - if (ret < 0) - { - BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); - return false; - } - entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp); - return true; -} - -inline bool getUniqueEntryID(sd_journal* journal, std::string& entryID, - const bool firstEntry = true) -{ - int ret = 0; - static sd_id128_t prevBootID{}; - static uint64_t prevTs = 0; - static int index = 0; - if (firstEntry) - { - prevBootID = {}; - prevTs = 0; - } - - // Get the entry timestamp - uint64_t curTs = 0; - sd_id128_t curBootID{}; - ret = sd_journal_get_monotonic_usec(journal, &curTs, &curBootID); - if (ret < 0) - { - BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); - return false; - } - // If the timestamp isn't unique on the same boot, increment the index - bool sameBootIDs = sd_id128_equal(curBootID, prevBootID) != 0; - if (sameBootIDs && (curTs == prevTs)) - { - index++; - } - else - { - // Otherwise, reset it - index = 0; - } - - if (!sameBootIDs) - { - // Save the bootID - prevBootID = curBootID; - } - // Save the timestamp - prevTs = curTs; - - // make entryID as <bootID>_<timestamp>[_<index>] - std::array<char, SD_ID128_STRING_MAX> bootIDStr{}; - sd_id128_to_string(curBootID, bootIDStr.data()); - entryID = std::format("{}_{}", bootIDStr.data(), curTs); - if (index > 0) - { - entryID += "_" + std::to_string(index); - } - return true; -} - -static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, +inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, const bool firstEntry = true) { static time_t prevTs = 0; @@ -267,69 +163,6 @@ static bool getUniqueEntryID(const std::string& logEntry, std::string& entryID, return true; } -// Entry is formed like "BootID_timestamp" or "BootID_timestamp_index" -inline bool - getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - std::string_view entryIDStrView, sd_id128_t& bootID, - uint64_t& timestamp, uint64_t& index) -{ - // Convert the unique ID back to a bootID + timestamp to find the entry - auto underscore1Pos = entryIDStrView.find('_'); - if (underscore1Pos == std::string_view::npos) - { - // EntryID has no bootID or timestamp - messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); - return false; - } - - // EntryID has bootID + timestamp - - // Convert entryIDViewString to BootID - // NOTE: bootID string which needs to be null-terminated for - // sd_id128_from_string() - std::string bootIDStr(entryIDStrView.substr(0, underscore1Pos)); - if (sd_id128_from_string(bootIDStr.c_str(), &bootID) < 0) - { - messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); - return false; - } - - // Get the timestamp from entryID - entryIDStrView.remove_prefix(underscore1Pos + 1); - - auto [timestampEnd, tstampEc] = std::from_chars( - entryIDStrView.begin(), entryIDStrView.end(), timestamp); - if (tstampEc != std::errc()) - { - messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); - return false; - } - entryIDStrView = std::string_view( - timestampEnd, - static_cast<size_t>(std::distance(timestampEnd, entryIDStrView.end()))); - if (entryIDStrView.empty()) - { - index = 0U; - return true; - } - // Timestamp might include optional index, if two events happened at the - // same "time". - if (entryIDStrView[0] != '_') - { - messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); - return false; - } - entryIDStrView.remove_prefix(1); - auto [ptr, indexEc] = std::from_chars(entryIDStrView.begin(), - entryIDStrView.end(), index); - if (indexEc != std::errc() || ptr != entryIDStrView.end()) - { - messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); - return false; - } - return true; -} - static bool getRedfishLogFiles(std::vector<std::filesystem::path>& redfishLogFiles) { @@ -942,7 +775,7 @@ inline std::string getDumpEntryPath(const std::string& dumpPath) if (dumpPath == "/xyz/openbmc_project/dump/bmc/entry") { return std::format("/redfish/v1/Managers/{}/LogServices/Dump/Entries/", - BMCWEB_REDFISH_SYSTEM_URI_NAME); + BMCWEB_REDFISH_MANAGER_URI_NAME); } if (dumpPath == "/xyz/openbmc_project/dump/system/entry") { @@ -2546,291 +2379,6 @@ inline void requestRoutesBMCLogServiceCollection(App& app) std::bind_front(handleBMCLogServicesCollectionGet, std::ref(app))); } -inline void requestRoutesBMCJournalLogService(App& app) -{ - BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") - .privileges(redfish::privileges::getLogService) - .methods(boost::beast::http::verb::get)( - [&app](const crow::Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::string& managerId) { - if (!redfish::setUpRedfishRoute(app, req, asyncResp)) - { - return; - } - - if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) - { - messages::resourceNotFound(asyncResp->res, "Manager", managerId); - return; - } - - asyncResp->res.jsonValue["@odata.type"] = - "#LogService.v1_2_0.LogService"; - asyncResp->res.jsonValue["@odata.id"] = - boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", - BMCWEB_REDFISH_MANAGER_URI_NAME); - asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; - asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; - asyncResp->res.jsonValue["Id"] = "Journal"; - asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; - - std::pair<std::string, std::string> redfishDateTimeOffset = - redfish::time_utils::getDateTimeOffsetNow(); - asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; - asyncResp->res.jsonValue["DateTimeLocalOffset"] = - redfishDateTimeOffset.second; - - asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( - "/redfish/v1/Managers/{}/LogServices/Journal/Entries", - BMCWEB_REDFISH_MANAGER_URI_NAME); - }); -} - -static int - fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, - sd_journal* journal, - nlohmann::json::object_t& bmcJournalLogEntryJson) -{ - // Get the Log Entry contents - int ret = 0; - - std::string message; - std::string_view syslogID; - ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); - if (ret < 0) - { - BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}", - strerror(-ret)); - } - if (!syslogID.empty()) - { - message += std::string(syslogID) + ": "; - } - - std::string_view msg; - ret = getJournalMetadata(journal, "MESSAGE", msg); - if (ret < 0) - { - BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret)); - return 1; - } - message += std::string(msg); - - // Get the severity from the PRIORITY field - long int severity = 8; // Default to an invalid priority - ret = getJournalMetadata(journal, "PRIORITY", 10, severity); - if (ret < 0) - { - BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret)); - } - - // Get the Created time from the timestamp - std::string entryTimeStr; - if (!getEntryTimestamp(journal, entryTimeStr)) - { - return 1; - } - - // Fill in the log entry with the gathered data - bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; - bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( - "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}", - BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID); - bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; - bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; - bmcJournalLogEntryJson["Message"] = std::move(message); - bmcJournalLogEntryJson["EntryType"] = "Oem"; - log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; - if (severity <= 2) - { - severityEnum = log_entry::EventSeverity::Critical; - } - else if (severity <= 4) - { - severityEnum = log_entry::EventSeverity::Warning; - } - - bmcJournalLogEntryJson["Severity"] = severityEnum; - bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; - bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); - return 0; -} - -inline void requestRoutesBMCJournalLogEntryCollection(App& app) -{ - BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") - .privileges(redfish::privileges::getLogEntryCollection) - .methods(boost::beast::http::verb::get)( - [&app](const crow::Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::string& managerId) { - query_param::QueryCapabilities capabilities = { - .canDelegateTop = true, - .canDelegateSkip = true, - }; - query_param::Query delegatedQuery; - if (!redfish::setUpRedfishRouteWithDelegation( - app, req, asyncResp, delegatedQuery, capabilities)) - { - return; - } - - if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) - { - messages::resourceNotFound(asyncResp->res, "Manager", managerId); - return; - } - - size_t skip = delegatedQuery.skip.value_or(0); - size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); - - // Collections don't include the static data added by SubRoute - // because it has a duplicate entry for members - asyncResp->res.jsonValue["@odata.type"] = - "#LogEntryCollection.LogEntryCollection"; - asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( - "/redfish/v1/Managers/{}/LogServices/Journal/Entries", - BMCWEB_REDFISH_MANAGER_URI_NAME); - asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; - asyncResp->res.jsonValue["Description"] = - "Collection of BMC Journal Entries"; - nlohmann::json& logEntryArray = asyncResp->res.jsonValue["Members"]; - logEntryArray = nlohmann::json::array(); - - // Go through the journal and use the timestamp to create a - // unique ID for each entry - sd_journal* journalTmp = nullptr; - int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); - if (ret < 0) - { - BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); - messages::internalError(asyncResp->res); - return; - } - std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( - journalTmp, sd_journal_close); - journalTmp = nullptr; - uint64_t entryCount = 0; - // Reset the unique ID on the first entry - bool firstEntry = true; - SD_JOURNAL_FOREACH(journal.get()) - { - entryCount++; - // Handle paging using skip (number of entries to skip from - // the start) and top (number of entries to display) - if (entryCount <= skip || entryCount > skip + top) - { - continue; - } - - std::string idStr; - if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) - { - continue; - } - firstEntry = false; - - nlohmann::json::object_t bmcJournalLogEntry; - if (fillBMCJournalLogEntryJson(idStr, journal.get(), - bmcJournalLogEntry) != 0) - { - messages::internalError(asyncResp->res); - return; - } - logEntryArray.emplace_back(std::move(bmcJournalLogEntry)); - } - asyncResp->res.jsonValue["Members@odata.count"] = entryCount; - if (skip + top < entryCount) - { - asyncResp->res - .jsonValue["Members@odata.nextLink"] = boost::urls::format( - "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", - BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); - } - }); -} - -inline void requestRoutesBMCJournalLogEntry(App& app) -{ - BMCWEB_ROUTE( - app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") - .privileges(redfish::privileges::getLogEntry) - .methods(boost::beast::http::verb::get)( - [&app](const crow::Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::string& managerId, const std::string& entryID) { - if (!redfish::setUpRedfishRoute(app, req, asyncResp)) - { - return; - } - - if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) - { - messages::resourceNotFound(asyncResp->res, "Manager", managerId); - return; - } - - // Convert the unique ID back to a timestamp to find the entry - sd_id128_t bootID{}; - uint64_t ts = 0; - uint64_t index = 0; - if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) - { - return; - } - - sd_journal* journalTmp = nullptr; - int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); - if (ret < 0) - { - BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); - messages::internalError(asyncResp->res); - return; - } - std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( - journalTmp, sd_journal_close); - journalTmp = nullptr; - // Go to the timestamp in the log and move to the entry at the - // index tracking the unique ID - std::string idStr; - bool firstEntry = true; - ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); - if (ret < 0) - { - BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", - strerror(-ret)); - messages::internalError(asyncResp->res); - return; - } - for (uint64_t i = 0; i <= index; i++) - { - sd_journal_next(journal.get()); - if (!getUniqueEntryID(journal.get(), idStr, firstEntry)) - { - messages::internalError(asyncResp->res); - return; - } - firstEntry = false; - } - // Confirm that the entry ID matches what was requested - if (idStr != entryID) - { - messages::resourceNotFound(asyncResp->res, "LogEntry", entryID); - return; - } - - nlohmann::json::object_t bmcJournalLogEntry; - if (fillBMCJournalLogEntryJson(entryID, journal.get(), - bmcJournalLogEntry) != 0) - { - messages::internalError(asyncResp->res); - return; - } - asyncResp->res.jsonValue.update(bmcJournalLogEntry); - }); -} - inline void getDumpServiceInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& dumpType) diff --git a/redfish-core/lib/manager_logservices_journal.hpp b/redfish-core/lib/manager_logservices_journal.hpp new file mode 100644 index 0000000000..797a44b45e --- /dev/null +++ b/redfish-core/lib/manager_logservices_journal.hpp @@ -0,0 +1,659 @@ +#pragma once + +#include "app.hpp" +#include "error_messages.hpp" +#include "generated/enums/log_entry.hpp" +#include "query.hpp" +#include "registries/base_message_registry.hpp" +#include "registries/privilege_registry.hpp" +#include "utils/time_utils.hpp" + +#include <systemd/sd-journal.h> + +#include <boost/beast/http/verb.hpp> + +#include <array> +#include <memory> +#include <string> +#include <string_view> + +namespace redfish +{ +// Entry is formed like "BootID_timestamp" or "BootID_timestamp_index" +inline bool + getTimestampFromID(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + std::string_view entryIDStrView, sd_id128_t& bootID, + uint64_t& timestamp, uint64_t& index) +{ + // Convert the unique ID back to a bootID + timestamp to find the entry + auto underscore1Pos = entryIDStrView.find('_'); + if (underscore1Pos == std::string_view::npos) + { + // EntryID has no bootID or timestamp + messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); + return false; + } + + // EntryID has bootID + timestamp + + // Convert entryIDViewString to BootID + // NOTE: bootID string which needs to be null-terminated for + // sd_id128_from_string() + std::string bootIDStr(entryIDStrView.substr(0, underscore1Pos)); + if (sd_id128_from_string(bootIDStr.c_str(), &bootID) < 0) + { + messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); + return false; + } + + // Get the timestamp from entryID + entryIDStrView.remove_prefix(underscore1Pos + 1); + + auto [timestampEnd, tstampEc] = std::from_chars( + entryIDStrView.begin(), entryIDStrView.end(), timestamp); + if (tstampEc != std::errc()) + { + messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); + return false; + } + entryIDStrView = std::string_view( + timestampEnd, + static_cast<size_t>(std::distance(timestampEnd, entryIDStrView.end()))); + if (entryIDStrView.empty()) + { + index = 0U; + return true; + } + // Timestamp might include optional index, if two events happened at the + // same "time". + if (entryIDStrView[0] != '_') + { + messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); + return false; + } + entryIDStrView.remove_prefix(1); + auto [ptr, indexEc] = std::from_chars(entryIDStrView.begin(), + entryIDStrView.end(), index); + if (indexEc != std::errc() || ptr != entryIDStrView.end()) + { + messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); + return false; + } + if (index <= 1) + { + // Indexes go directly from no postfix (handled above) to _2 + // so if we ever see _0 or _1, it's incorrect + messages::resourceNotFound(asyncResp->res, "LogEntry", entryIDStrView); + return false; + } + + // URI indexes are one based, journald is zero based + index -= 1; + return true; +} + +inline std::string getUniqueEntryID(uint64_t index, uint64_t curTs, + sd_id128_t& curBootID) +{ + // make entryID as <bootID>_<timestamp>[_<index>] + std::array<char, SD_ID128_STRING_MAX> bootIDStr{}; + sd_id128_to_string(curBootID, bootIDStr.data()); + std::string postfix; + if (index > 0) + { + postfix = std::format("_{}", index + 1); + } + return std::format("{}_{}{}", bootIDStr.data(), curTs, postfix); +} + +inline int getJournalMetadata(sd_journal* journal, std::string_view field, + std::string_view& contents) +{ + const char* data = nullptr; + size_t length = 0; + int ret = 0; + // Get the metadata from the requested field of the journal entry + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + const void** dataVoid = reinterpret_cast<const void**>(&data); + + ret = sd_journal_get_data(journal, field.data(), dataVoid, &length); + if (ret < 0) + { + return ret; + } + contents = std::string_view(data, length); + // Only use the content after the "=" character. + contents.remove_prefix(std::min(contents.find('=') + 1, contents.size())); + return ret; +} + +inline int getJournalMetadataInt(sd_journal* journal, std::string_view field, + const int& base, long int& contents) +{ + int ret = 0; + std::string_view metadata; + // Get the metadata from the requested field of the journal entry + ret = getJournalMetadata(journal, field, metadata); + if (ret < 0) + { + return ret; + } + contents = strtol(metadata.data(), nullptr, base); + return ret; +} + +inline bool getEntryTimestamp(sd_journal* journal, std::string& entryTimestamp) +{ + int ret = 0; + uint64_t timestamp = 0; + ret = sd_journal_get_realtime_usec(journal, ×tamp); + if (ret < 0) + { + BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", strerror(-ret)); + return false; + } + entryTimestamp = redfish::time_utils::getDateTimeUintUs(timestamp); + return true; +} + +inline bool + fillBMCJournalLogEntryJson(const std::string& bmcJournalLogEntryID, + sd_journal* journal, + nlohmann::json::object_t& bmcJournalLogEntryJson) +{ + // Get the Log Entry contents + std::string message; + std::string_view syslogID; + int ret = getJournalMetadata(journal, "SYSLOG_IDENTIFIER", syslogID); + if (ret < 0) + { + BMCWEB_LOG_DEBUG("Failed to read SYSLOG_IDENTIFIER field: {}", + strerror(-ret)); + } + if (!syslogID.empty()) + { + message += std::string(syslogID) + ": "; + } + + std::string_view msg; + ret = getJournalMetadata(journal, "MESSAGE", msg); + if (ret < 0) + { + BMCWEB_LOG_ERROR("Failed to read MESSAGE field: {}", strerror(-ret)); + return false; + } + message += std::string(msg); + + // Get the severity from the PRIORITY field + long int severity = 8; // Default to an invalid priority + ret = getJournalMetadataInt(journal, "PRIORITY", 10, severity); + if (ret < 0) + { + BMCWEB_LOG_DEBUG("Failed to read PRIORITY field: {}", strerror(-ret)); + } + + // Get the Created time from the timestamp + std::string entryTimeStr; + if (!getEntryTimestamp(journal, entryTimeStr)) + { + return false; + } + + // Fill in the log entry with the gathered data + bmcJournalLogEntryJson["@odata.type"] = "#LogEntry.v1_9_0.LogEntry"; + bmcJournalLogEntryJson["@odata.id"] = boost::urls::format( + "/redfish/v1/Managers/{}/LogServices/Journal/Entries/{}", + BMCWEB_REDFISH_MANAGER_URI_NAME, bmcJournalLogEntryID); + bmcJournalLogEntryJson["Name"] = "BMC Journal Entry"; + bmcJournalLogEntryJson["Id"] = bmcJournalLogEntryID; + bmcJournalLogEntryJson["Message"] = std::move(message); + bmcJournalLogEntryJson["EntryType"] = "Oem"; + log_entry::EventSeverity severityEnum = log_entry::EventSeverity::OK; + if (severity <= 2) + { + severityEnum = log_entry::EventSeverity::Critical; + } + else if (severity <= 4) + { + severityEnum = log_entry::EventSeverity::Warning; + } + + bmcJournalLogEntryJson["Severity"] = severityEnum; + bmcJournalLogEntryJson["OemRecordFormat"] = "BMC Journal Entry"; + bmcJournalLogEntryJson["Created"] = std::move(entryTimeStr); + return true; +} + +inline void handleManagersLogServiceJournalGet( + App& app, const crow::Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& managerId) +{ + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) + { + messages::resourceNotFound(asyncResp->res, "Manager", managerId); + return; + } + + asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService"; + asyncResp->res.jsonValue["@odata.id"] = + boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal", + BMCWEB_REDFISH_MANAGER_URI_NAME); + asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service"; + asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service"; + asyncResp->res.jsonValue["Id"] = "Journal"; + asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; + + std::pair<std::string, std::string> redfishDateTimeOffset = + redfish::time_utils::getDateTimeOffsetNow(); + asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first; + asyncResp->res.jsonValue["DateTimeLocalOffset"] = + redfishDateTimeOffset.second; + + asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format( + "/redfish/v1/Managers/{}/LogServices/Journal/Entries", + BMCWEB_REDFISH_MANAGER_URI_NAME); +} + +struct JournalReadState +{ + std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal; + uint64_t index = 0; + sd_id128_t prevBootID{}; + uint64_t prevTs = 0; +}; + +inline void + readJournalEntries(uint64_t topEntryCount, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + JournalReadState&& readState) +{ + nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"]; + nlohmann::json::array_t* logEntryArray = + logEntry.get_ptr<nlohmann::json::array_t*>(); + if (logEntryArray == nullptr) + { + messages::internalError(asyncResp->res); + return; + } + + // The Journal APIs unfortunately do blocking calls to the filesystem, and + // can be somewhat expensive. Short of creating our own io_uring based + // implementation of sd-journal, which would be difficult, the best thing we + // can do is to only parse a certain number of entries at a time. The + // current chunk size is selected arbitrarily to ensure that we're not + // trying to process thousands of entries at the same time. + // The implementation will process the number of entries, then return + // control to the io_context to let other operations continue. + size_t segmentCountRemaining = 10; + + // Reset the unique ID on the first entry + for (uint64_t entryCount = logEntryArray->size(); + entryCount < topEntryCount; entryCount++) + { + if (segmentCountRemaining == 0) + { + boost::asio::post(crow::connections::systemBus->get_io_context(), + [asyncResp, topEntryCount, + readState = std::move(readState)]() mutable { + readJournalEntries(topEntryCount, asyncResp, + std::move(readState)); + }); + return; + } + + // Get the entry timestamp + sd_id128_t curBootID{}; + uint64_t curTs = 0; + int ret = sd_journal_get_monotonic_usec(readState.journal.get(), &curTs, + &curBootID); + if (ret < 0) + { + BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", + strerror(-ret)); + messages::internalError(asyncResp->res); + return; + } + + // If the timestamp isn't unique on the same boot, increment the index + bool sameBootIDs = sd_id128_equal(curBootID, readState.prevBootID) != 0; + if (sameBootIDs && (curTs == readState.prevTs)) + { + readState.index++; + } + else + { + // Otherwise, reset it + readState.index = 0; + } + + // Save the bootID + readState.prevBootID = curBootID; + + // Save the timestamp + readState.prevTs = curTs; + + std::string idStr = getUniqueEntryID(readState.index, curTs, curBootID); + + nlohmann::json::object_t bmcJournalLogEntry; + if (!fillBMCJournalLogEntryJson(idStr, readState.journal.get(), + bmcJournalLogEntry)) + { + messages::internalError(asyncResp->res); + return; + } + logEntryArray->emplace_back(std::move(bmcJournalLogEntry)); + + ret = sd_journal_next(readState.journal.get()); + if (ret < 0) + { + messages::internalError(asyncResp->res); + return; + } + if (ret == 0) + { + break; + } + segmentCountRemaining--; + } +} + +inline void handleManagersJournalLogEntryCollectionGet( + App& app, const crow::Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& managerId) +{ + query_param::QueryCapabilities capabilities = { + .canDelegateTop = true, + .canDelegateSkip = true, + }; + query_param::Query delegatedQuery; + if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, + delegatedQuery, capabilities)) + { + return; + } + + if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) + { + messages::resourceNotFound(asyncResp->res, "Manager", managerId); + return; + } + + size_t skip = delegatedQuery.skip.value_or(0); + size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop); + + // Collections don't include the static data added by SubRoute + // because it has a duplicate entry for members + asyncResp->res.jsonValue["@odata.type"] = + "#LogEntryCollection.LogEntryCollection"; + asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( + "/redfish/v1/Managers/{}/LogServices/Journal/Entries", + BMCWEB_REDFISH_MANAGER_URI_NAME); + asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries"; + asyncResp->res.jsonValue["Description"] = + "Collection of BMC Journal Entries"; + asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t(); + + // Go through the journal and use the timestamp to create a + // unique ID for each entry + sd_journal* journalTmp = nullptr; + int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); + if (ret < 0) + { + BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); + messages::internalError(asyncResp->res); + return; + } + + std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( + journalTmp, sd_journal_close); + journalTmp = nullptr; + + // Seek to the end + if (sd_journal_seek_tail(journal.get()) < 0) + { + messages::internalError(asyncResp->res); + return; + } + + // Get the last entry + if (sd_journal_previous(journal.get()) < 0) + { + messages::internalError(asyncResp->res); + return; + } + + // Get the last sequence number + uint64_t endSeqNum = 0; +#if SYSTEMD_VERSION >= 254 + { + if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0) + { + messages::internalError(asyncResp->res); + return; + } + } +#endif + + // Seek to the beginning + if (sd_journal_seek_head(journal.get()) < 0) + { + messages::internalError(asyncResp->res); + return; + } + + // Get the first entry + if (sd_journal_next(journal.get()) < 0) + { + messages::internalError(asyncResp->res); + return; + } + + // Get the first sequence number + uint64_t startSeqNum = 0; +#if SYSTEMD_VERSION >= 254 + { + if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0) + { + messages::internalError(asyncResp->res); + return; + } + } +#endif + + BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum, + endSeqNum); + + // Add 1 to account for the last entry + uint64_t totalEntries = endSeqNum - startSeqNum + 1; + asyncResp->res.jsonValue["Members@odata.count"] = totalEntries; + if (skip + top < totalEntries) + { + asyncResp->res.jsonValue["Members@odata.nextLink"] = + boost::urls::format( + "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}", + BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top)); + } + uint64_t index = 0; + sd_id128_t curBootID{}; + uint64_t curTs = 0; + if (skip > 0) + { + if (sd_journal_next_skip(journal.get(), skip) < 0) + { + messages::internalError(asyncResp->res); + return; + } + + // Get the entry timestamp + ret = sd_journal_get_monotonic_usec(journal.get(), &curTs, &curBootID); + if (ret < 0) + { + BMCWEB_LOG_ERROR("Failed to read entry timestamp: {}", + strerror(-ret)); + messages::internalError(asyncResp->res); + return; + } + + uint64_t endChunkSeqNum = 0; +#if SYSTEMD_VERSION >= 254 + { + if (sd_journal_get_seqnum(journal.get(), &endChunkSeqNum, nullptr) < + 0) + { + messages::internalError(asyncResp->res); + return; + } + } +#endif + + // Seek to the first entry with the same timestamp and boot + ret = sd_journal_seek_monotonic_usec(journal.get(), curBootID, curTs); + if (ret < 0) + { + BMCWEB_LOG_ERROR("Failed to seek: {}", strerror(-ret)); + messages::internalError(asyncResp->res); + return; + } + if (sd_journal_next(journal.get()) < 0) + { + messages::internalError(asyncResp->res); + return; + } + uint64_t startChunkSeqNum = 0; +#if SYSTEMD_VERSION >= 254 + { + if (sd_journal_get_seqnum(journal.get(), &startChunkSeqNum, + nullptr) < 0) + { + messages::internalError(asyncResp->res); + return; + } + } +#endif + + // Get the difference between the start and end. Most of the time this + // will be 0 + BMCWEB_LOG_DEBUG("start={} end={}", startChunkSeqNum, endChunkSeqNum); + index = endChunkSeqNum - startChunkSeqNum; + if (index > endChunkSeqNum) + { + // Detect underflows. Should never happen. + messages::internalError(asyncResp->res); + return; + } + if (index > 0) + { + BMCWEB_LOG_DEBUG("index = {}", index); + if (sd_journal_next_skip(journal.get(), index) < 0) + { + messages::internalError(asyncResp->res); + return; + } + } + } + // If this is the first entry of this series, reset the timestamps so the + // Index doesn't increment + if (index == 0) + { + curBootID = {}; + curTs = 0; + } + else + { + index -= 1; + } + BMCWEB_LOG_DEBUG("Index was {}", index); + readJournalEntries(top, asyncResp, + {std::move(journal), index, curBootID, curTs}); +} + +inline void handleManagersJournalEntriesLogEntryGet( + App& app, const crow::Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& managerId, const std::string& entryID) +{ + if (!redfish::setUpRedfishRoute(app, req, asyncResp)) + { + return; + } + + if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) + { + messages::resourceNotFound(asyncResp->res, "Manager", managerId); + return; + } + + // Convert the unique ID back to a timestamp to find the entry + sd_id128_t bootID{}; + uint64_t ts = 0; + uint64_t index = 0; + if (!getTimestampFromID(asyncResp, entryID, bootID, ts, index)) + { + return; + } + + sd_journal* journalTmp = nullptr; + int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY); + if (ret < 0) + { + BMCWEB_LOG_ERROR("failed to open journal: {}", strerror(-ret)); + messages::internalError(asyncResp->res); + return; + } + std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal( + journalTmp, sd_journal_close); + journalTmp = nullptr; + // Go to the timestamp in the log and move to the entry at the + // index tracking the unique ID + ret = sd_journal_seek_monotonic_usec(journal.get(), bootID, ts); + if (ret < 0) + { + BMCWEB_LOG_ERROR("failed to seek to an entry in journal{}", + strerror(-ret)); + messages::internalError(asyncResp->res); + return; + } + + if (sd_journal_next_skip(journal.get(), index + 1) < 0) + { + messages::internalError(asyncResp->res); + return; + } + + std::string idStr = getUniqueEntryID(index, ts, bootID); + + nlohmann::json::object_t bmcJournalLogEntry; + if (!fillBMCJournalLogEntryJson(entryID, journal.get(), bmcJournalLogEntry)) + { + messages::internalError(asyncResp->res); + return; + } + asyncResp->res.jsonValue.update(bmcJournalLogEntry); +} + +inline void requestRoutesBMCJournalLogService(App& app) +{ + BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/") + .privileges(redfish::privileges::getLogService) + .methods(boost::beast::http::verb::get)( + std::bind_front(handleManagersLogServiceJournalGet, std::ref(app))); + + BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/") + .privileges(redfish::privileges::getLogEntryCollection) + .methods(boost::beast::http::verb::get)(std::bind_front( + handleManagersJournalLogEntryCollectionGet, std::ref(app))); + + BMCWEB_ROUTE( + app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/") + .privileges(redfish::privileges::getLogEntry) + .methods(boost::beast::http::verb::get)(std::bind_front( + handleManagersJournalEntriesLogEntryGet, std::ref(app))); +} +} // namespace redfish diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp index f0a4e0ab21..c9ac95cac3 100644 --- a/redfish-core/lib/managers.hpp +++ b/redfish-core/lib/managers.hpp @@ -334,19 +334,20 @@ inline void nlohmann::json& configRoot = asyncResp->res.jsonValue["Oem"]["OpenBmc"]["Fan"]; nlohmann::json& fans = configRoot["FanControllers"]; - fans["@odata.type"] = "#OemManager.FanControllers"; + fans["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanControllers"; fans["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanControllers", BMCWEB_REDFISH_MANAGER_URI_NAME); nlohmann::json& pids = configRoot["PidControllers"]; - pids["@odata.type"] = "#OemManager.PidControllers"; + pids["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.PidControllers"; pids["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/PidControllers", BMCWEB_REDFISH_MANAGER_URI_NAME); nlohmann::json& stepwise = configRoot["StepwiseControllers"]; - stepwise["@odata.type"] = "#OemManager.StepwiseControllers"; + stepwise["@odata.type"] = + "#OpenBMCManager.v1_0_0.Manager.StepwiseControllers"; stepwise["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/StepwiseControllers", BMCWEB_REDFISH_MANAGER_URI_NAME); @@ -355,11 +356,11 @@ inline void zones["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan/FanZones", BMCWEB_REDFISH_MANAGER_URI_NAME); - zones["@odata.type"] = "#OemManager.FanZones"; + zones["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.FanZones"; configRoot["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc/Fan", BMCWEB_REDFISH_MANAGER_URI_NAME); - configRoot["@odata.type"] = "#OemManager.Fan"; + configRoot["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager.Fan"; configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles; if (!currentProfile.empty()) @@ -449,7 +450,8 @@ inline void ("/Oem/OpenBmc/Fan/FanZones"_json_pointer / name) .to_string()); zone["@odata.id"] = std::move(url); - zone["@odata.type"] = "#OemManager.FanZone"; + zone["@odata.type"] = + "#OpenBMCManager.v1_0_0.Manager.FanZone"; config = &zone; } @@ -470,7 +472,7 @@ inline void .to_string()); controller["@odata.id"] = std::move(url); controller["@odata.type"] = - "#OemManager.StepwiseController"; + "#OpenBMCManager.v1_0_0.Manager.StepwiseController"; controller["Direction"] = *classPtr; } @@ -494,7 +496,8 @@ inline void name) .to_string()); element["@odata.id"] = std::move(url); - element["@odata.type"] = "#OemManager.FanController"; + element["@odata.type"] = + "#OpenBMCManager.v1_0_0.Manager.FanController"; } else { @@ -503,7 +506,8 @@ inline void name) .to_string()); element["@odata.id"] = std::move(url); - element["@odata.type"] = "#OemManager.PidController"; + element["@odata.type"] = + "#OpenBMCManager.v1_0_0.Manager.PidController"; } } else @@ -1999,10 +2003,9 @@ inline void requestRoutesManager(App& app) // default oem data nlohmann::json& oem = asyncResp->res.jsonValue["Oem"]; nlohmann::json& oemOpenbmc = oem["OpenBmc"]; - oem["@odata.type"] = "#OemManager.Oem"; oem["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}#/Oem", BMCWEB_REDFISH_MANAGER_URI_NAME); - oemOpenbmc["@odata.type"] = "#OemManager.OpenBmc"; + oemOpenbmc["@odata.type"] = "#OpenBMCManager.v1_0_0.Manager"; oemOpenbmc["@odata.id"] = boost::urls::format("/redfish/v1/Managers/{}#/Oem/OpenBmc", BMCWEB_REDFISH_MANAGER_URI_NAME); diff --git a/redfish-core/lib/redfish_sessions.hpp b/redfish-core/lib/redfish_sessions.hpp index dba1aac770..225e87219f 100644 --- a/redfish-core/lib/redfish_sessions.hpp +++ b/redfish-core/lib/redfish_sessions.hpp @@ -27,6 +27,9 @@ #include <boost/url/format.hpp> +#include <string> +#include <vector> + namespace redfish { @@ -137,15 +140,14 @@ inline void inline nlohmann::json getSessionCollectionMembers() { - std::vector<const std::string*> sessionIds = - persistent_data::SessionStore::getInstance().getUniqueIds( - false, persistent_data::PersistenceType::TIMEOUT); + std::vector<std::string> sessionIds = + persistent_data::SessionStore::getInstance().getAllUniqueIds(); nlohmann::json ret = nlohmann::json::array(); - for (const std::string* uid : sessionIds) + for (const std::string& uid : sessionIds) { nlohmann::json::object_t session; session["@odata.id"] = - boost::urls::format("/redfish/v1/SessionService/Sessions/{}", *uid); + boost::urls::format("/redfish/v1/SessionService/Sessions/{}", uid); ret.emplace_back(std::move(session)); } return ret; @@ -244,7 +246,7 @@ inline void handleSessionCollectionPost( std::shared_ptr<persistent_data::UserSession> session = persistent_data::SessionStore::getInstance().generateUserSession( username, req.ipAddress, clientId, - persistent_data::PersistenceType::TIMEOUT, isConfigureSelfOnly); + persistent_data::SessionType::Session, isConfigureSelfOnly); if (session == nullptr) { messages::internalError(asyncResp->res); diff --git a/redfish-core/lib/redfish_v1.hpp b/redfish-core/lib/redfish_v1.hpp index bbda45bcdd..41bcafea08 100644 --- a/redfish-core/lib/redfish_v1.hpp +++ b/redfish-core/lib/redfish_v1.hpp @@ -6,7 +6,6 @@ #include "http_response.hpp" #include "query.hpp" #include "registries/privilege_registry.hpp" -#include "schemas.hpp" #include "utility.hpp" #include <boost/url/format.hpp> @@ -86,15 +85,32 @@ inline void json["Name"] = "JsonSchemaFile Collection"; json["Description"] = "Collection of JsonSchemaFiles"; nlohmann::json::array_t members; - for (std::string_view schema : schemas) + + std::error_code ec; + std::filesystem::directory_iterator dirList( + "/usr/share/www/redfish/v1/JsonSchemas", ec); + if (ec) + { + messages::internalError(asyncResp->res); + return; + } + for (const std::filesystem::path& file : dirList) { + std::string filename = file.filename(); + std::vector<std::string> split; + bmcweb::split(split, filename, '.'); + if (split.empty()) + { + continue; + } nlohmann::json::object_t member; member["@odata.id"] = boost::urls::format("/redfish/v1/JsonSchemas/{}", - schema); + split[0]); members.emplace_back(std::move(member)); } + + json["Members@odata.count"] = members.size(); json["Members"] = std::move(members); - json["Members@odata.count"] = schemas.size(); } inline void jsonSchemaGet(App& app, const crow::Request& req, @@ -106,40 +122,97 @@ inline void jsonSchemaGet(App& app, const crow::Request& req, return; } - if (std::ranges::find(schemas, schema) == schemas.end()) + std::error_code ec; + std::filesystem::directory_iterator dirList( + "/usr/share/www/redfish/v1/JsonSchemas", ec); + if (ec) { messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); return; } + for (const std::filesystem::path& file : dirList) + { + std::string filename = file.filename(); + std::vector<std::string> split; + bmcweb::split(split, filename, '.'); + if (split.empty()) + { + continue; + } + BMCWEB_LOG_DEBUG("Checking {}", split[0]); + if (split[0] != schema) + { + continue; + } + + nlohmann::json& json = asyncResp->res.jsonValue; + json["@odata.id"] = boost::urls::format("/redfish/v1/JsonSchemas/{}", + schema); + json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile"; + json["Name"] = schema + " Schema File"; + json["Description"] = schema + " Schema File Location"; + json["Id"] = schema; + std::string schemaName = std::format("#{}.{}", schema, schema); + json["Schema"] = std::move(schemaName); + constexpr std::array<std::string_view, 1> languages{"en"}; + json["Languages"] = languages; + json["Languages@odata.count"] = languages.size(); + + nlohmann::json::array_t locationArray; + nlohmann::json::object_t locationEntry; + locationEntry["Language"] = "en"; + + locationEntry["PublicationUri"] = boost::urls::format( + "http://redfish.dmtf.org/schemas/v1/{}", filename); + locationEntry["Uri"] = boost::urls::format( + "/redfish/v1/JsonSchemas/{}/{}", schema, filename); + + locationArray.emplace_back(locationEntry); + + json["Location"] = std::move(locationArray); + json["Location@odata.count"] = 1; + return; + } + messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); +} - nlohmann::json& json = asyncResp->res.jsonValue; - json["@odata.id"] = boost::urls::format("/redfish/v1/JsonSchemas/{}", - schema); - json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile"; - json["Name"] = schema + " Schema File"; - json["Description"] = schema + " Schema File Location"; - json["Id"] = schema; - std::string schemaName = "#"; - schemaName += schema; - schemaName += "."; - schemaName += schema; - json["Schema"] = std::move(schemaName); - constexpr std::array<std::string_view, 1> languages{"en"}; - json["Languages"] = languages; - json["Languages@odata.count"] = languages.size(); - - nlohmann::json::array_t locationArray; - nlohmann::json::object_t locationEntry; - locationEntry["Language"] = "en"; - locationEntry["PublicationUri"] = "http://redfish.dmtf.org/schemas/v1/" + - schema + ".json"; - locationEntry["Uri"] = boost::urls::format( - "/redfish/v1/JsonSchemas/{}/{}", schema, std::string(schema) + ".json"); - - locationArray.emplace_back(locationEntry); - - json["Location"] = std::move(locationArray); - json["Location@odata.count"] = 1; +inline void + jsonSchemaGetFile(const crow::Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& schema, const std::string& schemaFile) +{ + // Sanity check the filename + if (schemaFile.find_first_not_of( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") != + std::string::npos) + { + messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); + return; + } + // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json + // Make sure the two paths match. + if (!schemaFile.starts_with(schema)) + { + messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); + return; + } + std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas"); + filepath /= schemaFile; + if (filepath.is_relative()) + { + messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); + return; + } + + if (!asyncResp->res.openFile(filepath)) + { + BMCWEB_LOG_DEBUG("failed to read file"); + asyncResp->res.result( + boost::beast::http::status::internal_server_error); + return; + } + + messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema); } inline void requestRoutesRedfish(App& app) @@ -148,6 +221,10 @@ inline void requestRoutesRedfish(App& app) .methods(boost::beast::http::verb::get)( std::bind_front(redfishGet, std::ref(app))); + BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>") + .privileges(redfish::privileges::getJsonSchemaFile) + .methods(boost::beast::http::verb::get)(jsonSchemaGetFile); + BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/") .privileges(redfish::privileges::getJsonSchemaFileCollection) .methods(boost::beast::http::verb::get)( diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp index 5d7dba1c36..8c4f991298 100644 --- a/redfish-core/lib/systems.hpp +++ b/redfish-core/lib/systems.hpp @@ -1965,8 +1965,8 @@ inline void nlohmann::json& oemPFR = asyncResp->res.jsonValue["Oem"]["OpenBmc"]["FirmwareProvisioning"]; asyncResp->res.jsonValue["Oem"]["OpenBmc"]["@odata.type"] = - "#OemComputerSystem.OpenBmc"; - oemPFR["@odata.type"] = "#OemComputerSystem.FirmwareProvisioning"; + "#OpenBMCComputerSystem.v1_0_0.OpenBmc"; + oemPFR["@odata.type"] = "#OpenBMCComputerSystem.FirmwareProvisioning"; if (ec) { @@ -2817,7 +2817,7 @@ inline void handleComputerSystemCollectionGet( BMCWEB_LOG_CRITICAL("Count wasn't found??"); return; } - uint64_t* count = val->get_ptr<uint64_t*>(); + int64_t* count = val->get_ptr<int64_t*>(); if (count == nullptr) { BMCWEB_LOG_CRITICAL("Count wasn't found??"); diff --git a/redfish-core/lib/task.hpp b/redfish-core/lib/task.hpp index a2959b8455..ec92acaa6b 100644 --- a/redfish-core/lib/task.hpp +++ b/redfish-core/lib/task.hpp @@ -144,7 +144,8 @@ struct TaskData : std::enable_shared_from_this<TaskData> { res.result(boost::beast::http::status::accepted); std::string strIdx = std::to_string(index); - std::string uri = "/redfish/v1/TaskService/Tasks/" + strIdx; + boost::urls::url uri = + boost::urls::format("/redfish/v1/TaskService/Tasks/{}", strIdx); res.jsonValue["@odata.id"] = uri; res.jsonValue["@odata.type"] = "#Task.v1_4_3.Task"; @@ -152,8 +153,11 @@ struct TaskData : std::enable_shared_from_this<TaskData> res.jsonValue["TaskState"] = state; res.jsonValue["TaskStatus"] = status; + boost::urls::url taskMonitor = boost::urls::format( + "/redfish/v1/TaskService/TaskMonitors/{}", strIdx); + res.addHeader(boost::beast::http::field::location, - uri + "/Monitor"); + taskMonitor.buffer()); res.addHeader(boost::beast::http::field::retry_after, std::to_string(retryAfterSeconds)); } @@ -199,9 +203,6 @@ struct TaskData : std::enable_shared_from_this<TaskData> static void sendTaskEvent(std::string_view state, size_t index) { - std::string origin = "/redfish/v1/TaskService/Tasks/" + - std::to_string(index); - std::string resType = "Task"; // TaskState enums which should send out an event are: // "Starting" = taskResumed // "Running" = taskStarted @@ -213,59 +214,50 @@ struct TaskData : std::enable_shared_from_this<TaskData> // "Killed" = taskRemoved // "Exception" = taskCompletedWarning // "Cancelled" = taskCancelled + nlohmann::json event; + std::string indexStr = std::to_string(index); if (state == "Starting") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskResumed(std::to_string(index)), origin, - resType); + event = redfish::messages::taskResumed(indexStr); } else if (state == "Running") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskStarted(std::to_string(index)), origin, - resType); + event = redfish::messages::taskStarted(indexStr); } else if ((state == "Suspended") || (state == "Interrupted") || (state == "Pending")) { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskPaused(std::to_string(index)), origin, - resType); + event = redfish::messages::taskPaused(indexStr); } else if (state == "Stopping") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskAborted(std::to_string(index)), origin, - resType); + event = redfish::messages::taskAborted(indexStr); } else if (state == "Completed") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskCompletedOK(std::to_string(index)), - origin, resType); + event = redfish::messages::taskCompletedOK(indexStr); } else if (state == "Killed") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskRemoved(std::to_string(index)), origin, - resType); + event = redfish::messages::taskRemoved(indexStr); } else if (state == "Exception") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskCompletedWarning(std::to_string(index)), - origin, resType); + event = redfish::messages::taskCompletedWarning(indexStr); } else if (state == "Cancelled") { - redfish::EventServiceManager::getInstance().sendEvent( - redfish::messages::taskCancelled(std::to_string(index)), origin, - resType); + event = redfish::messages::taskCancelled(indexStr); } else { BMCWEB_LOG_INFO("sendTaskEvent: No events to send"); + return; } + boost::urls::url origin = + boost::urls::format("/redfish/v1/TaskService/Tasks/{}", index); + EventServiceManager::getInstance().sendEvent(event, origin.buffer(), + "Task"); } void startTimer(const std::chrono::seconds& timeout) @@ -325,7 +317,7 @@ struct TaskData : std::enable_shared_from_this<TaskData> inline void requestRoutesTaskMonitor(App& app) { - BMCWEB_ROUTE(app, "/redfish/v1/TaskService/Tasks/<str>/Monitor/") + BMCWEB_ROUTE(app, "/redfish/v1/TaskService/TaskMonitors/<str>/") .privileges(redfish::privileges::getTask) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, @@ -414,8 +406,8 @@ inline void requestRoutesTask(App& app) boost::urls::format("/redfish/v1/TaskService/Tasks/{}", strParam); if (!ptr->gave204) { - asyncResp->res.jsonValue["TaskMonitor"] = - "/redfish/v1/TaskService/Tasks/" + strParam + "/Monitor"; + asyncResp->res.jsonValue["TaskMonitor"] = boost::urls::format( + "/redfish/v1/TaskService/TaskMonitors/{}", strParam); } asyncResp->res.jsonValue["HidePayload"] = !ptr->payload; @@ -428,7 +420,7 @@ inline void requestRoutesTask(App& app) p.httpOperation; asyncResp->res.jsonValue["Payload"]["HttpHeaders"] = p.httpHeaders; asyncResp->res.jsonValue["Payload"]["JsonBody"] = p.jsonBody.dump( - 2, ' ', true, nlohmann::json::error_handler_t::replace); + -1, ' ', true, nlohmann::json::error_handler_t::replace); } asyncResp->res.jsonValue["PercentComplete"] = ptr->percentComplete; }); diff --git a/redfish-core/lib/update_service.hpp b/redfish-core/lib/update_service.hpp index 9a399affa4..ade9e5722f 100644 --- a/redfish-core/lib/update_service.hpp +++ b/redfish-core/lib/update_service.hpp @@ -810,8 +810,12 @@ inline std::optional<MultiPartUpdateParameters> if (param.second == "UpdateParameters") { std::vector<std::string> tempTargets; - nlohmann::json content = - nlohmann::json::parse(formpart.content); + nlohmann::json content = nlohmann::json::parse(formpart.content, + nullptr, false); + if (content.is_discarded()) + { + return std::nullopt; + } nlohmann::json::object_t* obj = content.get_ptr<nlohmann::json::object_t*>(); if (obj == nullptr) @@ -908,12 +912,16 @@ inline void startUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, "StartUpdate", sdbusplus::message::unix_fd(memfd.fd), applyTime); } -inline void getAssociatedUpdateInterface( - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, task::Payload payload, - const MemoryFileDescriptor& memfd, const std::string& applyTime, - const boost::system::error_code& ec, - const dbus::utility::MapperGetSubTreeResponse& subtree) +inline void getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + task::Payload payload, const MemoryFileDescriptor& memfd, + const std::string& applyTime, const std::string& target, + const boost::system::error_code& ec, + const dbus::utility::MapperGetSubTreeResponse& subtree) { + using SwInfoMap = std::unordered_map< + std::string, std::pair<sdbusplus::message::object_path, std::string>>; + SwInfoMap swInfoMap; + if (ec) { BMCWEB_LOG_ERROR("error_code = {}", ec); @@ -921,35 +929,37 @@ inline void getAssociatedUpdateInterface( messages::internalError(asyncResp->res); return; } - BMCWEB_LOG_DEBUG("Found {} startUpdate subtree paths", subtree.size()); + BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size()); - if (subtree.size() > 1) + for (const auto& entry : subtree) { - BMCWEB_LOG_ERROR("Found more than one startUpdate subtree paths"); - messages::internalError(asyncResp->res); + sdbusplus::message::object_path path(entry.first); + std::string swId = path.filename(); + swInfoMap.emplace(swId, make_pair(path, entry.second[0].first)); + } + + auto swEntry = swInfoMap.find(target); + if (swEntry == swInfoMap.end()) + { + BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target); + messages::propertyValueFormatError(asyncResp->res, target, "Targets"); return; } - auto objectPath = subtree[0].first; - auto serviceName = subtree[0].second[0].first; + BMCWEB_LOG_DEBUG("Found software version path {} serviceName {}", + swEntry->second.first.str, swEntry->second.second); - BMCWEB_LOG_DEBUG("Found objectPath {} serviceName {}", objectPath, - serviceName); - startUpdate(asyncResp, std::move(payload), memfd, applyTime, objectPath, - serviceName); + startUpdate(asyncResp, std::move(payload), memfd, applyTime, + swEntry->second.first.str, swEntry->second.second); } inline void - getSwInfo(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - task::Payload payload, MemoryFileDescriptor memfd, - const std::string& applyTime, const std::string& target, - const boost::system::error_code& ec, - const dbus::utility::MapperGetSubTreePathsResponse& subtree) + handleBMCUpdate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + task::Payload payload, const MemoryFileDescriptor& memfd, + const std::string& applyTime, + const boost::system::error_code& ec, + const dbus::utility::MapperEndPoints& functionalSoftware) { - using SwInfoMap = - std::unordered_map<std::string, sdbusplus::message::object_path>; - SwInfoMap swInfoMap; - if (ec) { BMCWEB_LOG_ERROR("error_code = {}", ec); @@ -957,40 +967,16 @@ inline void messages::internalError(asyncResp->res); return; } - BMCWEB_LOG_DEBUG("Found {} software version paths", subtree.size()); - - for (const auto& objectPath : subtree) - { - sdbusplus::message::object_path path(objectPath); - std::string swId = path.filename(); - swInfoMap.emplace(swId, path); - } - - auto swEntry = swInfoMap.find(target); - if (swEntry == swInfoMap.end()) + if (functionalSoftware.size() != 1) { - BMCWEB_LOG_WARNING("No valid DBus path for Target URI {}", target); - messages::propertyValueFormatError(asyncResp->res, target, "Targets"); + BMCWEB_LOG_ERROR("Found {} functional software endpoints", + functionalSoftware.size()); + messages::internalError(asyncResp->res); return; } - BMCWEB_LOG_DEBUG("Found software version path {}", swEntry->second.str); - - sdbusplus::message::object_path swObjectPath = swEntry->second / - "software_version"; - constexpr std::array<std::string_view, 1> interfaces = { - "xyz.openbmc_project.Software.Update"}; - dbus::utility::getAssociatedSubTree( - swObjectPath, - sdbusplus::message::object_path("/xyz/openbmc_project/software"), 0, - interfaces, - [asyncResp, payload = std::move(payload), memfd = std::move(memfd), - applyTime]( - const boost::system::error_code& ec1, - const dbus::utility::MapperGetSubTreeResponse& subtree1) mutable { - getAssociatedUpdateInterface(asyncResp, std::move(payload), memfd, - applyTime, ec1, subtree1); - }); + startUpdate(asyncResp, std::move(payload), memfd, applyTime, + functionalSoftware[0], "xyz.openbmc_project.Software.Manager"); } inline void @@ -1021,23 +1007,28 @@ inline void if (!targets.empty() && targets[0] == BMCWEB_REDFISH_MANAGER_URI_NAME) { - startUpdate(asyncResp, std::move(payload), memfd, applyTime, - "/xyz/openbmc_project/software/bmc", - "xyz.openbmc_project.Software.Manager"); + dbus::utility::getAssociationEndPoints( + "/xyz/openbmc_project/software/bmc/functional", + [asyncResp, payload = std::move(payload), memfd = std::move(memfd), + applyTime]( + const boost::system::error_code& ec, + const dbus::utility::MapperEndPoints& objectPaths) mutable { + handleBMCUpdate(asyncResp, std::move(payload), memfd, applyTime, ec, + objectPaths); + }); } else { constexpr std::array<std::string_view, 1> interfaces = { "xyz.openbmc_project.Software.Version"}; - dbus::utility::getSubTreePaths( + dbus::utility::getSubTree( "/xyz/openbmc_project/software", 1, interfaces, [asyncResp, payload = std::move(payload), memfd = std::move(memfd), - applyTime, - targets](const boost::system::error_code& ec, - const dbus::utility::MapperGetSubTreePathsResponse& - subtree) mutable { - getSwInfo(asyncResp, std::move(payload), std::move(memfd), - applyTime, targets[0], ec, subtree); + applyTime, targets](const boost::system::error_code& ec, + const dbus::utility::MapperGetSubTreeResponse& + subtree) mutable { + getSwInfo(asyncResp, std::move(payload), memfd, applyTime, + targets[0], ec, subtree); }); } } diff --git a/redfish-core/lib/virtual_media.hpp b/redfish-core/lib/virtual_media.hpp index 37fb2045c9..6e4be6d1ea 100644 --- a/redfish-core/lib/virtual_media.hpp +++ b/redfish-core/lib/virtual_media.hpp @@ -259,7 +259,7 @@ inline nlohmann::json vmItemTemplate(const std::string& name, item["MediaTypes"] = nlohmann::json::array_t({"CD", "USBStick"}); item["TransferMethod"] = "Stream"; item["Oem"]["OpenBMC"]["@odata.type"] = - "#OemVirtualMedia.v1_0_0.VirtualMedia"; + "#OpenBMCVirtualMedia.v1_0_0.VirtualMedia"; item["Oem"]["OpenBMC"]["@odata.id"] = boost::urls::format( "/redfish/v1/Managers/{}/VirtualMedia/{}#/Oem/OpenBMC", name, resName); |