summaryrefslogtreecommitdiff
path: root/redfish-core/lib
diff options
context:
space:
mode:
Diffstat (limited to 'redfish-core/lib')
-rw-r--r--redfish-core/lib/account_service.hpp136
-rw-r--r--redfish-core/lib/event_service.hpp20
-rw-r--r--redfish-core/lib/eventservice_sse.hpp26
-rw-r--r--redfish-core/lib/log_services.hpp456
-rw-r--r--redfish-core/lib/manager_logservices_journal.hpp659
-rw-r--r--redfish-core/lib/managers.hpp25
-rw-r--r--redfish-core/lib/redfish_sessions.hpp14
-rw-r--r--redfish-core/lib/redfish_v1.hpp143
-rw-r--r--redfish-core/lib/systems.hpp6
-rw-r--r--redfish-core/lib/task.hpp58
-rw-r--r--redfish-core/lib/update_service.hpp123
-rw-r--r--redfish-core/lib/virtual_media.hpp2
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, &timestamp);
- 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, &timestamp);
+ 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);