bmcweb: Remove "agentless_patches"

We have migrated all bmcweb platforms to gbmcweb.

Tested: Presubmit will test the build
Fusion-Link: fusion2 not needed since if patches are used, it won't
build

Google-Bug-Id: 320507112
Google-Bug-Id: 236410938
Google-Bug-Id: 236438655
Google-Bug-Id: 240210399
Google-Bug-Id: 283282367
Google-Bug-Id: 237316154
Google-Bug-Id: 245991303
Google-Bug-Id: 253704450
Google-Bug-Id: 262583855
Google-Bug-Id: 289117596
Google-Bug-Id: 289119203
Change-Id: I7645a8c1e55fc9f16fc1f16391750c71c83aa2de
Signed-off-by: Brandon Kim <brandonkim@google.com>
diff --git a/recipes-phosphor/interfaces/bmcweb/0001-bmcweb-New-LogServices-feature-ExternalStorer.patch b/recipes-phosphor/interfaces/bmcweb/0001-bmcweb-New-LogServices-feature-ExternalStorer.patch
deleted file mode 100644
index d93434d..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0001-bmcweb-New-LogServices-feature-ExternalStorer.patch
+++ /dev/null
@@ -1,1535 +0,0 @@
-From ff701f5d8712da2f4596c38207f7ae013f8f40e7 Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Sat, 25 Jun 2022 20:17:41 -0700
-Subject: [PATCH] bmcweb: New LogServices feature ExternalStorer
-
-This is a new feature that lets external users dynamically update
-certain areas of the Redfish tree at runtime. The first usage is in
-LogServices.
-
-Design document:
-https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/52150
-API/examples/tutorial document:
-https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/52295
-
-The GET and POST commands are complete, the remaining commands will
-be implemented soon. The integration hook is complete for LogServices.
-The filesystem is used for all backing storage.
-
-Tested: The unit tests pass. With this script providing appropriate
-external input, the Redfish Service Validator passes:
-https://gist.github.com/Krellan/f511e65166ebe5435fde3f847d28fe73
-
-Patch Tracking Bug: b/236410938
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/bmcweb/+/51303/38
-Upstream-Status: Denied
-Justification:
-Ed disagrees with the architecture of this feature, and wants it to be
-something that can globally apply to all of the Redfish tree instead,
-not just to specific endpoints patched to become ExternalStorer hooks.
-He also wants it to be schema-aware, performing schema validation,
-instead of just accepting any valid JSON.
-
-Change-Id: Iec65d9ff421cbe77afd5f47cbcb6e4330caa0ef1
-Signed-off-by: Josh Lehan <krellan@google.com>
----
- meson.build                                   |    1 +
- redfish-core/include/redfish.hpp              |    3 +
- redfish-core/lib/external_storer.hpp          | 1201 +++++++++++++++++
- redfish-core/lib/log_services.hpp             |   12 +
- .../redfish-core/lib/external_storer_test.cpp |  207 +++
- 5 files changed, 1424 insertions(+)
- create mode 100644 redfish-core/lib/external_storer.hpp
- create mode 100644 test/redfish-core/lib/external_storer_test.cpp
-
-diff --git a/meson.build b/meson.build
-index 9c42e7a6..f444e30a 100644
---- a/meson.build
-+++ b/meson.build
-@@ -390,6 +390,7 @@ srcfiles_unittest = files(
-   'test/redfish-core/include/utils/stl_utils_test.cpp',
-   'test/redfish-core/include/utils/time_utils_test.cpp',
-   'test/redfish-core/lib/chassis_test.cpp',
-+  'test/redfish-core/lib/external_storer_test.cpp',
-   'test/redfish-core/lib/sensors_test.cpp',
-   'test/redfish-core/lib/log_services_dump_test.cpp',
-   'test/redfish-core/lib/service_root_test.cpp',
-diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
-index 26a8f8bd..cbdf28b6 100644
---- a/redfish-core/include/redfish.hpp
-+++ b/redfish-core/include/redfish.hpp
-@@ -25,6 +25,7 @@
- #include "environment_metrics.hpp"
- #include "ethernet.hpp"
- #include "event_service.hpp"
-+#include "external_storer.hpp"
- #include "fabric_adapters.hpp"
- #include "hypervisor_system.hpp"
- #include "log_services.hpp"
-@@ -248,6 +249,8 @@ class RedfishService
-         requestRoutesManagerFanModeChangeActionInfo(app);
-         requestRoutesManagerFanModeChangeAction(app);
- 
-+        requestRoutesExternalStorer(app);
-+
-         // Note, this must be the last route registered
-         requestRoutesRedfish(app);
-     }
-diff --git a/redfish-core/lib/external_storer.hpp b/redfish-core/lib/external_storer.hpp
-new file mode 100644
-index 00000000..976a7ac5
---- /dev/null
-+++ b/redfish-core/lib/external_storer.hpp
-@@ -0,0 +1,1201 @@
-+#pragma once
-+
-+// ExternalStorer allows external users (HTTP clients) to temporarily store
-+// their data on this Redfish server, hence its name.
-+
-+// The intended use cases are for catching logging messages and hardware error
-+// notifications from the host, but ExternalStorer is not limited to these.
-+// The backing store for this data is a RAM disk (tmpfs), so it will be lost
-+// when the BMC reboots or powers down. Not intended for long-term storage.
-+
-+// To comply with relevant Redfish schemas, ExternalStorer will carefully
-+// merge user-provided data with what is already on the system. Overwriting
-+// system-provided data is not allowed. There are 3 addressing levels:
-+
-+// Hook = The integration point, an existing Redfish URL containing one
-+// writable collection, as allowed by a schema.
-+// Instance = An external user will POST to create a new collection here,
-+// which will be added to what is already visible at the Hook.
-+// Entry = An external user will POST to create a new entry here,
-+// which will be added to that collection (the Instance).
-+
-+// Example usage:
-+// GET /redfish/v1/Systems/system/LogServices
-+// -> This is a hook, it contains a writable "Members" collection
-+// POST /redfish/v1/Systems/system/LogServices
-+// -> Created instance, let's assume BMC assigns the ID of "MY_LOG"
-+// GET /redfish/v1/Systems/system/LogServices/MY_LOG
-+// -> As per schema, this contains an additional path component "Entries"
-+// GET /redfish/v1/Systems/system/LogServices/MY_LOG/Entries
-+// -> This is a container, ready to go, contains empty "Members" collection
-+// POST /redfish/v1/Systems/system/LogServices/MY_LOG/Entries
-+// -> Created entry, let's assume BMC assigns the ID of "MY_ALERT"
-+// GET /redfish/v1/Systems/system/LogServices/MY_LOG/Entries/MY_ALERT
-+// -> This retrieves the data previously stored when creating that entry
-+// GET /redfish/v1/Systems/system/LogServices/MY_LOG/Entries
-+// -> This container is no longer empty, it now has one element
-+// GET /redfish/v1/Systems/system/LogServices/MY_LOG
-+// -> Retrieves some content previously stored when creating the instance
-+// GET /redfish/v1/Systems/system/LogServices
-+// -> The "Members" collection now contains "MY_LOG" in addition to before
-+
-+// All data is expressed in the form of a JSON dictionary. The backing store
-+// uses a similar directory layout, including the extra "Entries"
-+// subdirectory. The JSON content for the collection itself is stored as two
-+// special-case "index.json" filenames within that collection's directories.
-+// The on-disk file format is whatever is provided by the defaults for the
-+// nlohmann::json::dump() and nlohmann::json::parse() functions.
-+
-+// Filesystem layout:
-+// Directory /run/bmcweb/redfish/v1/HOOK/INSTANCE
-+// Directory /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE
-+// Directory /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE/ENTRY
-+// File /run/bmcweb/redfish/v1/HOOK/INSTANCE/index.json
-+// File /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE/index.json
-+// File /run/bmcweb/redfish/v1/HOOK/INSTANCE/MIDDLE/ENTRY/index.json
-+
-+// HOOK is hardcoded, trimmed from URL, example: "Systems/system/LogServices"
-+// MIDDLE is hardcoded, a single word, example: "Entries"
-+// INSTANCE and ENTRY are user-generated (within reason) or BMC-generated
-+// Each ENTRY index.json file contains JSON of one entry within an instance
-+// The two higher "index.json" files contain JSON of that instance itself
-+
-+// SECURITY WARNING: There is currently no limit on the amount of storage
-+// taken, nor any automatic cleanup of old content, so clients can cause
-+// a denial of service attack by consuming all storage. This will be
-+// addressed by future work.
-+
-+#include "app.hpp"
-+#include "openbmc_dbus_rest.hpp"
-+#include "query.hpp"
-+#include "registries/privilege_registry.hpp"
-+
-+#include <boost/uuid/uuid.hpp>
-+#include <boost/uuid/uuid_generators.hpp>
-+#include <boost/uuid/uuid_io.hpp>
-+
-+#include <fstream>
-+
-+namespace external_storer
-+{
-+
-+// These should become constexpr in a future compiler
-+inline const std::filesystem::path defPathPrefix{"/run/bmcweb"};
-+inline const std::filesystem::path redfishPrefix{"/redfish/v1"};
-+inline const std::filesystem::path jsonFilename{"index.json"};
-+
-+// This class only holds configuration and accounting data for the hook.
-+// As for user-provided data, it is intentionally not here, as it is always
-+// fetched from the filesystem backing store when needed.
-+class Hook
-+{
-+  private:
-+    // Relative location of hook, after redfishPrefix, before instance
-+    std::filesystem::path pathBase;
-+
-+    // Optional middle keyword, required by some schemas, example "Entries"
-+    std::filesystem::path pathMiddle;
-+
-+    // Disallow these user instances, avoid already-existing path components
-+    std::vector<std::string> denyList;
-+
-+    // Automatically expand these fields when listing the array of entries
-+    std::vector<std::string> expandList;
-+
-+    // The root directory for local storage, changeable only for testing
-+    std::filesystem::path pathPrefix;
-+
-+  public:
-+    Hook(const std::string& b, const std::string& m,
-+         const std::vector<std::string>& d, const std::vector<std::string>& e) :
-+        pathBase(b),
-+        pathMiddle(m), denyList(d), expandList(e), pathPrefix(defPathPrefix)
-+    {}
-+
-+    void handleCreateInstance(
-+        const crow::Request& req,
-+        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
-+    void handleCreateMiddle(const crow::Request& req,
-+                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                            const std::string& instance);
-+    void handleCreateEntry(const crow::Request& req,
-+                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                           const std::string& instance,
-+                           const std::string& middle);
-+
-+    // The 0-argument Get handled by just-in-time insert at integration point
-+    void handleGetInstance(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                           const std::string& instance);
-+    void handleGetMiddle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                         const std::string& instance,
-+                         const std::string& middle);
-+    void handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                        const std::string& instance, const std::string& middle,
-+                        const std::string& entry);
-+
-+    // For use by the integration point
-+    std::vector<std::string> listInstances(void) const;
-+
-+    // Utility functions for building up locations
-+    std::filesystem::path locBase() const;
-+    std::filesystem::path locInstance(const std::string& instance) const;
-+    std::filesystem::path locMiddle(const std::string& instance) const;
-+    std::filesystem::path locEntry(const std::string& instance,
-+                                   const std::string& entry) const;
-+    std::filesystem::path locToFileDir(const std::filesystem::path& loc) const;
-+    std::filesystem::path locToFileJson(const std::filesystem::path& loc) const;
-+
-+    // For use only during testing
-+    void deleteAll(void);
-+    void setPathPrefix(const std::filesystem::path& newPrefix);
-+};
-+
-+std::filesystem::path safeAppend(const std::filesystem::path& a,
-+                                 const std::filesystem::path& b)
-+{
-+    std::filesystem::path result{a};
-+
-+    // Unfortunately, a / b returns surprising/wrong results if b is absolute
-+    if (b.is_absolute())
-+    {
-+        // The absolute path already starts with necessary directory separator
-+        result += b;
-+        return result;
-+    }
-+
-+    result /= b;
-+    return result;
-+}
-+
-+std::filesystem::path Hook::locBase(void) const
-+{
-+    return safeAppend(redfishPrefix, pathBase);
-+}
-+
-+std::filesystem::path Hook::locInstance(const std::string& instance) const
-+{
-+    return safeAppend(locBase(), instance);
-+}
-+
-+std::filesystem::path Hook::locMiddle(const std::string& instance) const
-+{
-+    // The middle component is optional, some schemas might not need it
-+    if (pathMiddle.empty())
-+    {
-+        return locInstance(instance);
-+    }
-+
-+    return safeAppend(locInstance(instance), pathMiddle);
-+}
-+
-+std::filesystem::path Hook::locEntry(const std::string& instance,
-+                                     const std::string& entry) const
-+{
-+    return safeAppend(locMiddle(instance), entry);
-+}
-+
-+std::filesystem::path Hook::locToFileDir(const std::filesystem::path& loc) const
-+{
-+    return safeAppend(pathPrefix, loc);
-+}
-+
-+std::filesystem::path
-+    Hook::locToFileJson(const std::filesystem::path& loc) const
-+{
-+    // Safe to use / operator here, jsonFilename constant always relative
-+    return locToFileDir(loc) / jsonFilename;
-+}
-+
-+std::optional<nlohmann::json>
-+    readJsonFile(const std::filesystem::path& filename)
-+{
-+    nlohmann::json content;
-+    std::ifstream input;
-+
-+    input.open(filename);
-+
-+    if (!input)
-+    {
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "Error opening " << filename
-+                         << " input: " << strerror(err);
-+        return std::nullopt;
-+    }
-+
-+    // Must supply 3rd argument to avoid throwing exceptions
-+    content = nlohmann::json::parse(input, nullptr, false);
-+
-+    input.close();
-+
-+    // Must be good, or if not, must be at EOF, to deem file I/O successful
-+    if (!(input.good()))
-+    {
-+        if (!(input.eof()))
-+        {
-+            int err = errno;
-+            BMCWEB_LOG_ERROR << "Error closing " << filename
-+                             << " input: " << strerror(err);
-+            return std::nullopt;
-+        }
-+    }
-+
-+    // Even if file I/O successful, content must be a valid JSON dictionary
-+    if (content.is_discarded())
-+    {
-+        BMCWEB_LOG_ERROR << "Input " << filename << " not valid JSON";
-+        return std::nullopt;
-+    }
-+    if (!(content.is_object()))
-+    {
-+        BMCWEB_LOG_ERROR << "Input " << filename << " not JSON dictionary";
-+        return std::nullopt;
-+    }
-+
-+    return {content};
-+}
-+
-+std::optional<std::streampos>
-+    writeJsonFile(const std::filesystem::path& filename,
-+                  const nlohmann::json& content)
-+{
-+    std::ofstream output;
-+    std::streampos size;
-+
-+    output.open(filename, std::ofstream::trunc);
-+
-+    if (!output)
-+    {
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "Error opening " << filename
-+                         << " output: " << strerror(err);
-+        return std::nullopt;
-+    }
-+
-+    // Must supply 4th argument to avoid throwing exceptions
-+    output << content.dump(-1, ' ', false,
-+                           nlohmann::json::error_handler_t::replace);
-+
-+    size = output.tellp();
-+
-+    output.close();
-+
-+    if (!(output.good()))
-+    {
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "Error closing " << filename
-+                         << " output: " << strerror(err);
-+        return std::nullopt;
-+    }
-+
-+    return {size};
-+}
-+
-+// The "proposedName" should be a basename, with no directory separators
-+// Conservative filename rules to begin with, can relax later if needed
-+bool validateFilename(const std::filesystem::path& proposedName)
-+{
-+    if (!(crow::openbmc_mapper::validateFilename(proposedName)))
-+    {
-+        BMCWEB_LOG_ERROR << "Filename contains invalid characters";
-+        return false;
-+    }
-+
-+    return true;
-+}
-+
-+bool validateFilename(const std::filesystem::path& name,
-+                      const std::vector<std::string>& denyList)
-+{
-+    if (!(validateFilename(name)))
-+    {
-+        // Error message has already been printed
-+        return false;
-+    }
-+
-+    // Must not be within the denylist
-+    if (std::find(denyList.begin(), denyList.end(), name) != denyList.end())
-+    {
-+        BMCWEB_LOG_ERROR << "Filename " << name << " is reserved";
-+        return false;
-+    }
-+
-+    return true;
-+}
-+
-+std::string extractId(const nlohmann::json& content)
-+{
-+    std::string id;
-+
-+    if (content.is_object())
-+    {
-+        auto foundId = content.find("Id");
-+        if (foundId != content.end())
-+        {
-+            if (foundId->is_string())
-+            {
-+                id = foundId.value();
-+                if (!(id.empty()))
-+                {
-+                    return id;
-+                }
-+            }
-+        }
-+    }
-+
-+    boost::uuids::random_generator gen;
-+
-+    // Roll a random UUID for server-assigned ID
-+    id = boost::uuids::to_string(gen());
-+    BMCWEB_LOG_INFO << "Generated UUID " << id;
-+
-+    return id;
-+}
-+
-+void stripFieldsId(nlohmann::json& content)
-+{
-+    if (!(content.is_object()))
-+    {
-+        return;
-+    }
-+
-+    // No need, this is already implied by the filename on disk
-+    auto foundId = content.find("Id");
-+    if (foundId != content.end())
-+    {
-+        content.erase(foundId);
-+    }
-+
-+    // No need, this will be dynamically built when output to user
-+    auto foundOdataId = content.find("@odata.id");
-+    if (foundOdataId != content.end())
-+    {
-+        content.erase(foundOdataId);
-+    }
-+}
-+
-+void stripFieldsMembers(nlohmann::json& content)
-+{
-+    if (!(content.is_object()))
-+    {
-+        return;
-+    }
-+
-+    // Entries must be added one at a time, using separate POST commands
-+    auto foundMembers = content.find("Members");
-+    if (foundMembers != content.end())
-+    {
-+        content.erase(foundMembers);
-+    }
-+
-+    // No need, this will be dynamically built when output to user
-+    auto foundCount = content.find("Members@odata.count");
-+    if (foundCount != content.end())
-+    {
-+        content.erase(foundCount);
-+    }
-+}
-+
-+void insertResponseLocation(crow::Response& response,
-+                            const std::string& location)
-+{
-+    // Add Location to header
-+    response.addHeader(boost::beast::http::field::location, location);
-+
-+    // Add Location to body, but must dig through schema first
-+    if (!(response.jsonValue.is_object()))
-+    {
-+        BMCWEB_LOG_ERROR << "No Location because not object";
-+        return;
-+    }
-+
-+    // ExtendedInfo must already be an array of at least 1 element (object)
-+    auto ei = response.jsonValue.find("@Message.ExtendedInfo");
-+    if (ei == response.jsonValue.end())
-+    {
-+        BMCWEB_LOG_ERROR << "No Location because no ExtendedInfo";
-+        return;
-+    }
-+    if (!(ei->is_array()))
-+    {
-+        BMCWEB_LOG_ERROR << "No Location because ExtendedInfo not array";
-+        return;
-+    }
-+    if (ei->empty())
-+    {
-+        BMCWEB_LOG_ERROR << "No Location because ExtendedInfo empty";
-+        return;
-+    }
-+    if (!((*ei)[0].is_object()))
-+    {
-+        BMCWEB_LOG_ERROR
-+            << "No Location because ExtendedInfo element not object";
-+        return;
-+    }
-+
-+    // MessageArgs must be an array, create if it does not already exist
-+    auto ma = (*ei)[0].find("MessageArgs");
-+    if (ma == (*ei)[0].end())
-+    {
-+        (*ei)[0]["MessageArgs"] = nlohmann::json::array();
-+        ma = (*ei)[0].find("MessageArgs");
-+    }
-+    if (!(ma->is_array()))
-+    {
-+        BMCWEB_LOG_ERROR << "No Location because MessageArgs not array";
-+        return;
-+    }
-+
-+    ma->emplace_back(location);
-+}
-+
-+void Hook::handleCreateInstance(
-+    const crow::Request& req,
-+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
-+{
-+    nlohmann::json content;
-+    content = nlohmann::json::parse(req.body(), nullptr, false);
-+    if (content.is_discarded())
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded content not JSON";
-+        redfish::messages::malformedJSON(asyncResp->res);
-+        return;
-+    }
-+    if (!(content.is_object()))
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded JSON type not a dictionary";
-+        redfish::messages::unrecognizedRequestBody(asyncResp->res);
-+        return;
-+    }
-+
-+    std::string idInstance = extractId(content);
-+    stripFieldsId(content);
-+
-+    auto innerContent = nlohmann::json::object();
-+
-+    if (!(pathMiddle.empty()))
-+    {
-+        // Promote the inner layer to its own JSON object
-+        auto foundMiddle = content.find(pathMiddle);
-+        if (foundMiddle != content.end())
-+        {
-+            innerContent = foundMiddle.value();
-+            content.erase(foundMiddle);
-+
-+            if (!(innerContent.is_object()))
-+            {
-+                BMCWEB_LOG_ERROR << "Interior JSON type not a dictionary";
-+                redfish::messages::unrecognizedRequestBody(asyncResp->res);
-+                return;
-+            }
-+
-+            // Also trim "Id" and "@odata.id" from the inner layer
-+            stripFieldsId(innerContent);
-+
-+            // Trim "Members" as well, user not allowed bulk upload yet
-+            stripFieldsMembers(innerContent);
-+        }
-+    }
-+
-+    if (!(validateFilename(idInstance, denyList)))
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded instance ID not acceptable";
-+        redfish::messages::actionParameterValueFormatError(
-+            asyncResp->res, idInstance, "Id", "POST");
-+        return;
-+    }
-+
-+    std::filesystem::path outerUrl = locInstance(idInstance);
-+    std::filesystem::path outerDir = locToFileDir(outerUrl);
-+
-+    std::error_code ec;
-+
-+    if (std::filesystem::exists(outerDir, ec))
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded instance ID already exists on system";
-+        redfish::messages::resourceAlreadyExists(asyncResp->res, "String", "Id",
-+                                                 idInstance);
-+        return;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << outerDir
-+                         << " duplicate: " << ec.message();
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    std::filesystem::path innerUrl = locMiddle(idInstance);
-+    std::filesystem::path innerDir = locToFileDir(innerUrl);
-+
-+    // If no middle keyword, then no need to create multiple layers
-+    if (pathMiddle.empty())
-+    {
-+        innerDir = outerDir;
-+    }
-+
-+    std::filesystem::create_directories(innerDir, ec);
-+
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem making " << innerDir
-+                         << " directories: " << ec.message();
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    std::filesystem::path outerFilename = locToFileJson(outerUrl);
-+
-+    if (!(writeJsonFile(outerFilename, content).has_value()))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem writing file " << outerFilename;
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    if (!(pathMiddle.empty()))
-+    {
-+        std::filesystem::path innerFilename = locToFileJson(innerUrl);
-+
-+        if (!(writeJsonFile(innerFilename, innerContent).has_value()))
-+        {
-+            BMCWEB_LOG_ERROR << "Problem writing file " << innerFilename;
-+            redfish::messages::operationFailed(asyncResp->res);
-+            return;
-+        }
-+    }
-+
-+    redfish::messages::created(asyncResp->res);
-+
-+    insertResponseLocation(asyncResp->res, outerUrl);
-+}
-+
-+void Hook::handleCreateMiddle(
-+    const crow::Request& req,
-+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+    const std::string& idInstance)
-+{
-+    // Before doing anything with filesystem, validate naming restrictions
-+    if (!(validateFilename(idInstance, denyList)))
-+    {
-+        BMCWEB_LOG_ERROR << "Instance ID within URL is not acceptable";
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    nlohmann::json content;
-+
-+    // Keep this in sync with post-I/O validation in readJsonFile()
-+    content = nlohmann::json::parse(req.body(), nullptr, false);
-+    if (content.is_discarded())
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded content not JSON";
-+        redfish::messages::malformedJSON(asyncResp->res);
-+        return;
-+    }
-+    if (!(content.is_object()))
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded JSON type not a dictionary";
-+        redfish::messages::unrecognizedRequestBody(asyncResp->res);
-+        return;
-+    }
-+
-+    std::string idEntry = extractId(content);
-+
-+    // Unlike instance, no need to do a second layer of trimming underneath
-+    stripFieldsId(content);
-+
-+    // Unlike instance, names on denyList are perfectly OK for entry
-+    if (!(validateFilename(idEntry)))
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded entry ID not acceptable";
-+        redfish::messages::actionParameterValueFormatError(
-+            asyncResp->res, idEntry, "Id", "POST");
-+        return;
-+    }
-+
-+    std::filesystem::path outerUrl = locInstance(idInstance);
-+    std::filesystem::path outerDir = locToFileDir(outerUrl);
-+
-+    std::error_code ec;
-+
-+    // The instance must already have been created earlier
-+    if (!(std::filesystem::exists(outerDir, ec)))
-+    {
-+        BMCWEB_LOG_ERROR << "Cannot add entry to nonexistent instance "
-+                         << idInstance;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << outerDir
-+                         << " existence: " << ec.message();
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    std::filesystem::path entryUrl = locEntry(idInstance, idEntry);
-+    std::filesystem::path entryDir = locToFileDir(entryUrl);
-+
-+    std::filesystem::create_directories(entryDir, ec);
-+
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem making " << entryDir
-+                         << " directories: " << ec.message();
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    std::filesystem::path entryFilename = locToFileJson(entryUrl);
-+
-+    if (std::filesystem::exists(entryFilename, ec))
-+    {
-+        BMCWEB_LOG_ERROR << "Uploaded entry ID already exists within instance";
-+        redfish::messages::resourceAlreadyExists(asyncResp->res, "String", "Id",
-+                                                 idEntry);
-+        return;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << entryFilename
-+                         << " duplicate: " << ec.message();
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    if (!(writeJsonFile(entryFilename, content).has_value()))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem writing file " << entryFilename;
-+        redfish::messages::operationFailed(asyncResp->res);
-+        return;
-+    }
-+
-+    redfish::messages::created(asyncResp->res);
-+
-+    insertResponseLocation(asyncResp->res, entryUrl);
-+}
-+
-+void Hook::handleCreateEntry(
-+    const crow::Request& req,
-+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+    const std::string& idInstance, const std::string& keywordMiddle)
-+{
-+    // Validate the middle path component in URL is the expected constant
-+    if (keywordMiddle != pathMiddle)
-+    {
-+        BMCWEB_LOG_ERROR << "URL middle path component is not " << pathMiddle;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    // This handler has the same function as if the middle were omitted
-+    handleCreateMiddle(req, asyncResp, idInstance);
-+}
-+
-+// Given a dir, list its subdirs, but only those subdirs which are themselves
-+// directories, and contain an "index.json" file within them.
-+// Those "index.json" files are only checked for existence, nothing more.
-+std::vector<std::filesystem::path>
-+    listJsonDirs(const std::filesystem::path& dir)
-+{
-+    std::vector<std::filesystem::path> files;
-+    std::error_code ec;
-+
-+    // If containing directory not found, there can be no subdirectories
-+    if (!(std::filesystem::exists(dir, ec)))
-+    {
-+        BMCWEB_LOG_INFO << "Location " << dir << " nonexistent";
-+        return files;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << dir
-+                         << " existence: " << ec.message();
-+        return files;
-+    }
-+
-+    // Old-style C++ iter loop, to get error checking, not using ranged for
-+    for (auto entries = std::filesystem::directory_iterator{dir};
-+         entries != std::filesystem::end(entries); entries.increment(ec))
-+    {
-+        if (ec)
-+        {
-+            BMCWEB_LOG_ERROR << "Problem with " << dir
-+                             << " iterating: " << ec.message();
-+            break;
-+        }
-+
-+        const auto& entry = *entries;
-+
-+        // Only match directories
-+        if (!(entry.is_directory()))
-+        {
-+            continue;
-+        }
-+
-+        auto dirBasename = entry.path().filename();
-+
-+        // Validating against denyList not needed for entry, only for instance
-+        if (!(validateFilename(dirBasename)))
-+        {
-+            continue;
-+        }
-+
-+        // Safe to use / operator here, jsonFilename constant always relative
-+        auto jsonWithin = entry.path() / jsonFilename;
-+
-+        // The directory must contain the special JSON filename
-+        if (!(std::filesystem::exists(jsonWithin, ec)))
-+        {
-+            continue;
-+        }
-+        if (ec)
-+        {
-+            BMCWEB_LOG_ERROR << "Problem checking for " << jsonWithin
-+                             << " existence: " << ec.message();
-+            continue;
-+        }
-+
-+        files.emplace_back(dirBasename);
-+    }
-+
-+    return files;
-+}
-+
-+// Returns all existing instances under this hook, as a list of basenames
-+std::vector<std::string> Hook::listInstances(void) const
-+{
-+    auto instanceDirs = listJsonDirs(locToFileDir(locBase()));
-+
-+    std::vector<std::string> result;
-+    for (const auto& instanceDir : instanceDirs)
-+    {
-+        if (!(validateFilename(instanceDir, denyList)))
-+        {
-+            continue;
-+        }
-+
-+        result.emplace_back(instanceDir.string());
-+    }
-+
-+    return result;
-+}
-+
-+void Hook::handleGetInstance(
-+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+    const std::string& idInstance)
-+{
-+    // If optional middle keyword not in use, instance same as middle
-+    if (pathMiddle.empty())
-+    {
-+        return handleGetMiddle(asyncResp, idInstance, pathMiddle);
-+    }
-+
-+    // Before doing anything with filesystem, validate naming restrictions
-+    if (!(validateFilename(idInstance, denyList)))
-+    {
-+        BMCWEB_LOG_ERROR << "Instance ID within URL is not acceptable";
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto outerUrl = locInstance(idInstance);
-+    auto outerFilename = locToFileJson(outerUrl);
-+
-+    std::error_code ec;
-+
-+    if (!(std::filesystem::exists(outerFilename, ec)))
-+    {
-+        BMCWEB_LOG_ERROR << "Instance not found with ID " << idInstance;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << outerFilename
-+                         << " existence: " << ec.message();
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto contentOpt = readJsonFile(outerFilename);
-+    if (!(contentOpt.has_value()))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem reading file " << outerFilename;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto& content = *contentOpt;
-+
-+    // Regenerate these, as they were intentionally trimmed before storage
-+    content["Id"] = idInstance;
-+    content["@odata.id"] = outerUrl;
-+
-+    auto innerUrl = locMiddle(idInstance);
-+
-+    // Synthesize a correct link to middle layer
-+    auto middleObject = nlohmann::json::object();
-+    middleObject["@odata.id"] = innerUrl;
-+    content[pathMiddle] = middleObject;
-+
-+    redfish::messages::success(asyncResp->res);
-+
-+    asyncResp->res.jsonValue = std::move(content);
-+}
-+
-+void Hook::handleGetMiddle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                           const std::string& idInstance,
-+                           const std::string& keywordMiddle)
-+{
-+    // Before doing anything with filesystem, validate naming restrictions
-+    if (!(validateFilename(idInstance, denyList)))
-+    {
-+        BMCWEB_LOG_ERROR << "Instance ID within URL is not acceptable";
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    // Validate the middle path component in URL is the expected constant
-+    if (keywordMiddle != pathMiddle)
-+    {
-+        BMCWEB_LOG_ERROR << "URL middle path component is not " << pathMiddle;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto innerUrl = locMiddle(idInstance);
-+    auto innerDir = locToFileDir(innerUrl);
-+    auto innerFilename = locToFileJson(innerUrl);
-+
-+    std::error_code ec;
-+
-+    if (!(std::filesystem::exists(innerFilename, ec)))
-+    {
-+        BMCWEB_LOG_ERROR << "Instance not found with ID " << idInstance;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << idInstance
-+                         << " existence: " << ec.message();
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto contentOpt = readJsonFile(innerFilename);
-+    if (!(contentOpt.has_value()))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem reading file " << innerFilename;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto& content = *contentOpt;
-+
-+    // Regenerate these, as they were intentionally trimmed before storage
-+    content["Id"] = pathMiddle;
-+    content["@odata.id"] = innerUrl;
-+
-+    // Do not pass denylist in here, it is only for instance, not entry
-+    auto files = listJsonDirs(innerDir);
-+
-+    // Synthesize special "Members" array with links to all our entries
-+    auto membersArray = nlohmann::json::array();
-+    for (const auto& file : files)
-+    {
-+        // Safe to use / operator here, "file" already known to be relative
-+        std::filesystem::path entryUrl = innerUrl / file;
-+
-+        auto fileObject = nlohmann::json::object();
-+        fileObject["@odata.id"] = entryUrl;
-+        fileObject["Id"] = file;
-+
-+        // Automatically expand only the fields listed in expandList
-+        if (!(expandList.empty()))
-+        {
-+            std::filesystem::path entryFilename = locToFileJson(entryUrl);
-+            auto entryContentOpt = readJsonFile(entryFilename);
-+            if (entryContentOpt.has_value())
-+            {
-+                auto entryContent = *entryContentOpt;
-+
-+                for (const auto& key : expandList)
-+                {
-+                    auto valueIter = entryContent.find(key);
-+                    if (valueIter != entryContent.end())
-+                    {
-+                        fileObject[key] = *valueIter;
-+                    }
-+                }
-+            }
-+        }
-+
-+        membersArray += fileObject;
-+    }
-+
-+    // Finish putting the pieces together
-+    content["Members"] = membersArray;
-+    content["Members@odata.count"] = files.size();
-+
-+    redfish::messages::success(asyncResp->res);
-+
-+    asyncResp->res.jsonValue = std::move(content);
-+}
-+
-+void Hook::handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                          const std::string& idInstance,
-+                          const std::string& keywordMiddle,
-+                          const std::string& idEntry)
-+{
-+    // Before doing anything with filesystem, validate naming restrictions
-+    if (!(validateFilename(idInstance, denyList)))
-+    {
-+        BMCWEB_LOG_ERROR << "Instance ID within URL is not acceptable";
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    // Validate the middle path component in URL is the expected constant
-+    if (keywordMiddle != pathMiddle)
-+    {
-+        BMCWEB_LOG_ERROR << "URL middle path component is not " << pathMiddle;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    // Unlike instance, names on denyList are perfectly OK at this layer
-+    if (!(validateFilename(idEntry)))
-+    {
-+        BMCWEB_LOG_ERROR << "Entry ID within URL not acceptable";
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto entryUrl = locEntry(idInstance, idEntry);
-+    auto entryFilename = locToFileJson(entryUrl);
-+
-+    std::error_code ec;
-+
-+    if (!(std::filesystem::exists(entryFilename, ec)))
-+    {
-+        BMCWEB_LOG_ERROR << "Entry not found with ID " << idEntry;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking for " << idEntry
-+                         << " existence: " << ec.message();
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto contentOpt = readJsonFile(entryFilename);
-+    if (!(contentOpt.has_value()))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem reading file " << entryFilename;
-+        asyncResp->res.result(boost::beast::http::status::not_found);
-+        return;
-+    }
-+
-+    auto& content = *contentOpt;
-+
-+    // Regenerate these, as they were intentionally trimmed before storage
-+    content["Id"] = idEntry;
-+    content["@odata.id"] = entryUrl;
-+
-+    redfish::messages::success(asyncResp->res);
-+
-+    asyncResp->res.jsonValue = std::move(content);
-+}
-+
-+void Hook::deleteAll(void)
-+{
-+    std::error_code ec;
-+
-+    auto count = std::filesystem::remove_all(pathPrefix, ec);
-+
-+    if (ec)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem with " << pathPrefix
-+                         << " deleting: " << ec.message();
-+    }
-+
-+    if (count > 0)
-+    {
-+        BMCWEB_LOG_INFO << "Deleted all " << count << " files/dirs from "
-+                        << pathPrefix;
-+    }
-+}
-+
-+void Hook::setPathPrefix(const std::filesystem::path& newPrefix)
-+{
-+    // This function is only for testing, loudly warn if used
-+    BMCWEB_LOG_WARNING << "Changing path prefix to " << newPrefix;
-+
-+    pathPrefix = newPrefix;
-+}
-+
-+// Constructs a hook with known-good settings for usage with LogServices
-+inline Hook makeLogServices()
-+{
-+    const std::string pathBase{"Systems/system/LogServices"};
-+    const std::string midWord{"Entries"};
-+
-+    // These names come from requestRoutesSystemLogServiceCollection()
-+    std::vector<std::string> denyList{"EventLog", "Dump", "Crashdump",
-+                                      "HostLogger"};
-+
-+    // These names come from the "required" field of LogEntry JSON schema
-+    std::vector<std::string> expandList{"EntryType", "@odata.id", "@odata.type",
-+                                        "Id", "Name"};
-+
-+    // Additional useful names to pre-expand
-+    expandList.emplace_back("Created");
-+
-+    return {pathBase, midWord, denyList, expandList};
-+}
-+
-+inline std::shared_ptr<Hook>
-+    rememberLogServices(const std::shared_ptr<Hook>& hookIncoming = nullptr)
-+{
-+    static std::shared_ptr<Hook> hookLogServices = nullptr;
-+
-+    // If incoming pointer is valid, remember it for next time
-+    if (hookIncoming)
-+    {
-+        hookLogServices = hookIncoming;
-+    }
-+
-+    return hookLogServices;
-+}
-+
-+} // namespace external_storer
-+
-+namespace redfish
-+{
-+
-+// The URL layout under LogServices requires "Entries" path component,
-+// which seems unnecessary, but is required by the schema.
-+// POST(HOOK) = create new instance
-+// POST(HOOK/INSTANCE) = create new entry         | these 2 endpoints
-+// POST(HOOK/INSTANCE/Entries) = create new entry | do the same thing
-+// POST(HOOK/INSTANCE/Entries/ENTRY) = not allowed
-+// GET(HOOK) = supplement existing hook with our added instances
-+// GET(HOOK/INSTANCE) = return boilerplate of desired instance
-+// GET(HOOK/INSTANCE/Entries) = return Members array of all entries
-+// GET(HOOK/INSTANCE/Entries/ENTRY) = return content of desired entry
-+inline void requestRoutesExternalStorerLogServices(
-+    App& app, const std::shared_ptr<external_storer::Hook>& hook)
-+{
-+    // Only 0-argument, 1-argument, and 2-argument POST routes exist
-+    // There intentionally is no 3-argument POST handler
-+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/")
-+        .privileges(redfish::privileges::postLogService)
-+        .methods(boost::beast::http::verb::post)(
-+            [&app, hook](const crow::Request& req,
-+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+
-+        hook->handleCreateInstance(req, asyncResp);
-+        });
-+
-+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/")
-+        .privileges(redfish::privileges::postLogService)
-+        .methods(boost::beast::http::verb::post)(
-+            [&app, hook](const crow::Request& req,
-+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                         const std::string& instance) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        hook->handleCreateMiddle(req, asyncResp, instance);
-+        });
-+
-+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/<str>/")
-+        .privileges(redfish::privileges::postLogService)
-+        .methods(boost::beast::http::verb::post)(
-+            [&app, hook](const crow::Request& req,
-+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                         const std::string& instance,
-+                         const std::string& middle) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        hook->handleCreateEntry(req, asyncResp, instance, middle);
-+        });
-+
-+    // Only 1-argument, 2-argument, and 3-argument GET routes are here
-+    // The 0-argument GET route is already handled by the integration point
-+    // It is at log_services.hpp requestRoutesSystemLogServiceCollection()
-+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/")
-+        .privileges(redfish::privileges::getLogService)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app, hook](const crow::Request& req,
-+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                         const std::string& instance) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        hook->handleGetInstance(asyncResp, instance);
-+        });
-+
-+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/<str>/")
-+        .privileges(redfish::privileges::getLogService)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app, hook](const crow::Request& req,
-+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                         const std::string& instance,
-+                         const std::string& middle) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        hook->handleGetMiddle(asyncResp, instance, middle);
-+        });
-+
-+    BMCWEB_ROUTE(app,
-+                 "/redfish/v1/Systems/system/LogServices/<str>/<str>/<str>/")
-+        .privileges(redfish::privileges::getLogService)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app, hook](const crow::Request& req,
-+                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                         const std::string& instance, const std::string& middle,
-+                         const std::string& entry) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        hook->handleGetEntry(asyncResp, instance, middle, entry);
-+        });
-+
-+    // The integration point also needs to access the correct hook
-+    external_storer::rememberLogServices(hook);
-+}
-+
-+// NOTE: Currently, this works, but by luck, perhaps due to the fact that
-+// the ExternalStorer routes are requested last. The router currently does
-+// not cleanly support overlapping routes. So, the wildcard GET matchers
-+// currently can not cleanly coexist with the various hardcoded string
-+// matchers (see denyList) at the same URL position. One possible
-+// solution is to add a priority system to disambiguate, as discussed here:
-+// https://gerrit.openbmc-project.xyz/c/openbmc/bmcweb/+/43502
-+inline void requestRoutesExternalStorer(App& app)
-+{
-+    auto hookLogServices = std::make_shared<external_storer::Hook>(
-+        external_storer::makeLogServices());
-+
-+    // The shared_ptr will be copied, stretching out its lifetime
-+    requestRoutesExternalStorerLogServices(app, hookLogServices);
-+}
-+
-+} // namespace redfish
-diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
-index 9594c953..75c0fdcc 100644
---- a/redfish-core/lib/log_services.hpp
-+++ b/redfish-core/lib/log_services.hpp
-@@ -18,11 +18,13 @@
- #include "app.hpp"
- #include "dbus_utility.hpp"
- #include "error_messages.hpp"
-+#include "external_storer.hpp"
- #include "generated/enums/log_entry.hpp"
- #include "gzfile.hpp"
- #include "http_utility.hpp"
- #include "human_sort.hpp"
- #include "query.hpp"
-+#include "redfish_util.hpp"
- #include "registries.hpp"
- #include "registries/base_message_registry.hpp"
- #include "registries/openbmc_message_registry.hpp"
-@@ -1127,6 +1129,16 @@ inline void
-         logServiceArray.push_back(std::move(crashdump));
- #endif
- 
-+        // This is the ExternalStorer integration point
-+        for (const auto& instance :
-+             external_storer::rememberLogServices()->listInstances())
-+        {
-+            nlohmann::json::object_t externalStorerInstance;
-+            externalStorerInstance["@odata.id"] =
-+                "/redfish/v1/Systems/system/LogServices/" + instance;
-+            logServiceArray.push_back(std::move(externalStorerInstance));
-+        }
-+
-         constexpr std::array<std::string_view, 1> interfaces = {
-             "xyz.openbmc_project.State.Boot.PostCode"};
-         dbus::utility::getSubTreePaths(
-diff --git a/test/redfish-core/lib/external_storer_test.cpp b/test/redfish-core/lib/external_storer_test.cpp
-new file mode 100644
-index 00000000..113c36e7
---- /dev/null
-+++ b/test/redfish-core/lib/external_storer_test.cpp
-@@ -0,0 +1,207 @@
-+#include "redfish-core/lib/external_storer.hpp"
-+
-+#include "gtest/gtest.h"
-+
-+class ExternalStorerTest : public ::testing::Test
-+{
-+  protected:
-+    App app;
-+    std::shared_ptr<external_storer::Hook> hookLogs;
-+
-+  public:
-+    ExternalStorerTest() :
-+        hookLogs(std::make_shared<external_storer::Hook>(
-+            external_storer::makeLogServices()))
-+    {
-+        // Use our own Hook object, as we need it later in destructor
-+        redfish::requestRoutesExternalStorerLogServices(app, hookLogs);
-+
-+        // Customize directory to be non-systemwide for testing
-+        hookLogs->setPathPrefix("./ExternalStorer_Test");
-+
-+        // Clean up partial results of any previous interrupted run
-+        hookLogs->deleteAll();
-+    }
-+
-+    ~ExternalStorerTest() override
-+    {
-+        // Clean up after ourselves
-+        hookLogs->deleteAll();
-+    }
-+
-+    ExternalStorerTest(const ExternalStorerTest& copy) = delete;
-+    ExternalStorerTest& operator=(const ExternalStorerTest& assign) = delete;
-+    ExternalStorerTest(ExternalStorerTest&& move) = delete;
-+    ExternalStorerTest& operator=(ExternalStorerTest&& assign) = delete;
-+};
-+
-+TEST_F(ExternalStorerTest, CreateGetInstance)
-+{
-+    // Must not exist originally
-+    auto resp1 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetInstance(resp1, "MyInstance");
-+    EXPECT_EQ(resp1->res.result(), boost::beast::http::status::not_found);
-+
-+    boost::beast::http::request<boost::beast::http::string_body> upBody;
-+    std::error_code ec;
-+    auto upJson = nlohmann::json::object();
-+
-+    upJson["Id"] = "MyInstance";
-+    upJson["Layer"] = "Outer";
-+    upJson["Entries"] = nlohmann::json::object();
-+    upJson["Entries"]["Layer"] = "Inner";
-+    upBody.body() = upJson.dump();
-+    crow::Request req{upBody, ec};
-+
-+    // Create instance
-+    auto resp2 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleCreateInstance(req, resp2);
-+    EXPECT_EQ(resp2->res.result(), boost::beast::http::status::created);
-+
-+    // Must now exist
-+    auto resp3 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetInstance(resp3, "MyInstance");
-+    EXPECT_EQ(resp3->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp3->res.jsonValue["Layer"], "Outer");
-+
-+    // Outer layer and inner layer must both be individually customizable
-+    auto resp4 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetMiddle(resp4, "MyInstance", "Entries");
-+    EXPECT_EQ(resp4->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp4->res.jsonValue["Layer"], "Inner");
-+}
-+
-+TEST_F(ExternalStorerTest, CreateGetMiddle)
-+{
-+    // Must not exist initially
-+    auto resp1 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetInstance(resp1, "MyInstance");
-+    EXPECT_EQ(resp1->res.result(), boost::beast::http::status::not_found);
-+
-+    boost::beast::http::request<boost::beast::http::string_body> upBody;
-+    std::error_code ec;
-+    auto upJson = nlohmann::json::object();
-+
-+    upJson["Id"] = "MyInstance";
-+    upBody.body() = upJson.dump();
-+    crow::Request req2{upBody, ec};
-+
-+    // Create instance
-+    auto resp2 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleCreateInstance(req2, resp2);
-+    EXPECT_EQ(resp2->res.result(), boost::beast::http::status::created);
-+
-+    // Instance layer must have linkage to middle layer
-+    auto resp3 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetInstance(resp3, "MyInstance");
-+    EXPECT_EQ(resp3->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp3->res.jsonValue["Entries"]["@odata.id"],
-+              "/redfish/v1/Systems/system/LogServices/MyInstance/Entries");
-+
-+    // Entry must not initially exist
-+    auto resp4 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetMiddle(resp4, "MyInstance", "Entries");
-+    EXPECT_EQ(resp4->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp4->res.jsonValue["Members@odata.count"], 0);
-+
-+    upJson = nlohmann::json::object();
-+    upJson["Id"] = "EntryCreatedFromCreateMiddle";
-+    upJson["Hello"] = "There";
-+    upBody.body() = upJson.dump();
-+    crow::Request req5{upBody, ec};
-+
-+    // Create entry, by using middle layer
-+    auto resp5 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleCreateMiddle(req5, resp5, "MyInstance");
-+    EXPECT_EQ(resp5->res.result(), boost::beast::http::status::created);
-+
-+    // Created entry must now appear in array of entries
-+    auto resp6 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetMiddle(resp6, "MyInstance", "Entries");
-+    EXPECT_EQ(resp6->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp6->res.jsonValue["Members@odata.count"], 1);
-+    EXPECT_EQ(
-+        resp6->res.jsonValue["Members"][0]["@odata.id"],
-+        "/redfish/v1/Systems/system/LogServices/MyInstance/Entries/EntryCreatedFromCreateMiddle");
-+
-+    // Entry must now exist
-+    auto resp7 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetEntry(resp7, "MyInstance", "Entries",
-+                             "EntryCreatedFromCreateMiddle");
-+    EXPECT_EQ(resp7->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp7->res.jsonValue["Hello"], "There");
-+}
-+
-+TEST_F(ExternalStorerTest, CreateGetEntry)
-+{
-+    // Must not exist initially
-+    auto resp1 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetInstance(resp1, "MyInstance");
-+    EXPECT_EQ(resp1->res.result(), boost::beast::http::status::not_found);
-+
-+    boost::beast::http::request<boost::beast::http::string_body> upBody;
-+    std::error_code ec;
-+    auto upJson = nlohmann::json::object();
-+
-+    upJson["Id"] = "MyInstance";
-+    upBody.body() = upJson.dump();
-+    crow::Request req2{upBody, ec};
-+
-+    // Create instance
-+    auto resp2 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleCreateInstance(req2, resp2);
-+    EXPECT_EQ(resp2->res.result(), boost::beast::http::status::created);
-+
-+    // Instance layer must have linkage to middle layer
-+    auto resp3 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetInstance(resp3, "MyInstance");
-+    EXPECT_EQ(resp3->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp3->res.jsonValue["Entries"]["@odata.id"],
-+              "/redfish/v1/Systems/system/LogServices/MyInstance/Entries");
-+
-+    // Entry must not initially exist
-+    auto resp4 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetMiddle(resp4, "MyInstance", "Entries");
-+    EXPECT_EQ(resp4->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp4->res.jsonValue["Members@odata.count"], 0);
-+
-+    upJson = nlohmann::json::object();
-+    upJson["Id"] = "EntryCreatedFromCreateEntry";
-+    upJson["Hello"] = "There";
-+    upBody.body() = upJson.dump();
-+    crow::Request req5{upBody, ec};
-+
-+    // Create entry, by using entry layer
-+    auto resp5 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleCreateEntry(req5, resp5, "MyInstance", "Entries");
-+    EXPECT_EQ(resp5->res.result(), boost::beast::http::status::created);
-+
-+    upJson = nlohmann::json::object();
-+    upJson["Id"] = "AnotherEntry";
-+    upJson["Hello"] = "Again";
-+    upBody.body() = upJson.dump();
-+    crow::Request req6{upBody, ec};
-+
-+    // Create another entry, also by using entry layer
-+    auto resp6 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleCreateEntry(req6, resp6, "MyInstance", "Entries");
-+    EXPECT_EQ(resp6->res.result(), boost::beast::http::status::created);
-+
-+    // Both entries must now appear in array of entries
-+    auto resp7 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetMiddle(resp7, "MyInstance", "Entries");
-+    EXPECT_EQ(resp7->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp7->res.jsonValue["Members@odata.count"], 2);
-+
-+    // Both entries must now exist and be distinct from one another
-+    auto resp8 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetEntry(resp8, "MyInstance", "Entries",
-+                             "EntryCreatedFromCreateEntry");
-+    EXPECT_EQ(resp8->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp8->res.jsonValue["Hello"], "There");
-+
-+    auto resp9 = std::make_shared<bmcweb::AsyncResp>();
-+    hookLogs->handleGetEntry(resp9, "MyInstance", "Entries", "AnotherEntry");
-+    EXPECT_EQ(resp9->res.result(), boost::beast::http::status::ok);
-+    EXPECT_EQ(resp9->res.jsonValue["Hello"], "Again");
-+}
--- 
-2.41.0.162.gfafddb0af9-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb/0002-bmcweb-ExternalStorer-for-MemoryMetrics.patch b/recipes-phosphor/interfaces/bmcweb/0002-bmcweb-ExternalStorer-for-MemoryMetrics.patch
deleted file mode 100644
index bd87f76..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0002-bmcweb-ExternalStorer-for-MemoryMetrics.patch
+++ /dev/null
@@ -1,318 +0,0 @@
-From 2f3739717b54c85a74f7a37c48d356e72bb8a160 Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Thu, 21 Apr 2022 02:21:31 -0700
-Subject: [PATCH] bmcweb: ExternalStorer for MemoryMetrics
-
-Integration of ExternalStorer with MemoryMetrics, with GET support
-only. No POST support. The assumption is the file will be created
-on the filesystem, by a process external to bmcweb.
-
-An example file location is:
-/run/bmcweb/redfish/v1/Systems/system/Memory/dimm0/MemoryMetrics
-
-The directory "dimm0" can vary, based on the desired DIMM number.
-
-For the GET operation, if the file does not exist on disk, a
-synthetic result will be faked up. This prevents 404 error from
-happening. There is no more support for D-Bus query.
-
-Known issues: Similar to ProcessorMetrics, the count of DIMM is
-hardcoded, which should not be a concern, as GET is the only
-operation supported (so user can not write arbitrarily many files).
-
-Tested: As part of related ProcessorMetrics work
-
-Patch Tracking Bug: b/236438655
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/bmcweb/+/55313/2
-Upstream-Status: Pending
-Justification:
-This is a new feature, and has not been reviewed yet.
-
-Google-Bug-Id: 228285230
-Signed-off-by: Josh Lehan <krellan@google.com>
-
----
- redfish-core/include/redfish.hpp |   1 +
- redfish-core/lib/memory.hpp      | 245 +++++++++++++++++++++++++++++++
- 2 files changed, 246 insertions(+)
-
-diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
-index 6f955768..17e694bd 100644
---- a/redfish-core/include/redfish.hpp
-+++ b/redfish-core/include/redfish.hpp
-@@ -177,6 +177,7 @@ class RedfishService
-         requestRoutesOperatingConfig(app);
-         requestRoutesMemoryCollection(app);
-         requestRoutesMemory(app);
-+        requestRoutesMemoryMetrics(app);
-         requestRoutesSubProcessorCoreCollection(app);
-         requestRoutesSubProcessorCore(app);
-         requestRoutesSubProcessorThreadCollection(app);
-diff --git a/redfish-core/lib/memory.hpp b/redfish-core/lib/memory.hpp
-index ae938d74..01a20d4c 100644
---- a/redfish-core/lib/memory.hpp
-+++ b/redfish-core/lib/memory.hpp
-@@ -598,6 +598,13 @@ inline void
-     aResp->res.jsonValue[jsonPtr]["@odata.id"] = crow::utility::urlFromPieces(
-         "redfish", "v1", "Systems", "system", "Memory", dimmId);
-     aResp->res.jsonValue[jsonPtr]["@odata.type"] = "#Memory.v1_11_0.Memory";
-+
-+    // Inserting the "Metrics" JSON stanza
-+    std::string metricsUrl = aResp->res.jsonValue[jsonPtr]["@odata.id"];
-+    metricsUrl += "/MemoryMetrics";
-+    nlohmann::json::object_t metricsObj;
-+    metricsObj["@odata.id"] = std::move(metricsUrl);
-+    aResp->res.jsonValue[jsonPtr]["Metrics"] = std::move(metricsObj);
- }
- 
- inline void assembleDimmPartitionData(
-@@ -1136,6 +1143,220 @@ inline void requestRoutesMemoryCollection(App& app)
-         });
- }
- 
-+// Constructs hook with known-good settings for use with MemoryMetrics
-+inline external_storer::Hook makeDimmHook()
-+{
-+    const std::string pathBase{"Systems/system/Memory"};
-+
-+    const std::string emptyString;
-+    const std::vector<std::string> emptyList;
-+
-+    return {pathBase, emptyString, emptyList, emptyList};
-+}
-+
-+// Remembers the ExternalStorer hook and one instance per DIMM in this system
-+inline std::shared_ptr<external_storer::Hook>
-+    rememberDimmHook(const std::string& dimmId)
-+{
-+    static std::shared_ptr<external_storer::Hook> hookMemory = nullptr;
-+
-+    if (!hookMemory)
-+    {
-+        // If not already remembered by static variable, create hook
-+        hookMemory = std::make_shared<external_storer::Hook>(makeDimmHook());
-+    }
-+
-+    auto respGet = std::make_shared<bmcweb::AsyncResp>();
-+
-+    hookMemory->handleGetInstance(respGet, dimmId);
-+    if (respGet->res.result() == boost::beast::http::status::ok)
-+    {
-+        // Already exists, good, nothing more needs to be done
-+        return hookMemory;
-+    }
-+
-+    boost::beast::http::request<boost::beast::http::string_body> upBody;
-+    std::error_code ec;
-+
-+    // Create instance, with name of DIMM, and no further customizations
-+    auto upJson = nlohmann::json::object();
-+    upJson["Id"] = dimmId;
-+
-+    // Must supply 4th argument to avoid throwing exceptions
-+    upBody.body() =
-+        upJson.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
-+    crow::Request reqCreate{upBody, ec};
-+
-+    auto respCreate = std::make_shared<bmcweb::AsyncResp>();
-+    hookMemory->handleCreateInstance(reqCreate, respCreate);
-+    if (respCreate->res.result() != boost::beast::http::status::created)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem creating instance for " << dimmId;
-+        return nullptr;
-+    }
-+
-+    // The instance should now be usable
-+    return hookMemory;
-+}
-+
-+inline nlohmann::json fakeMemoryMetrics(const std::string& dimm)
-+{
-+    nlohmann::json::object_t metricsObj;
-+
-+    metricsObj["Id"] = "Metrics";
-+    metricsObj["Name"] = "Memory Metrics";
-+
-+    nlohmann::json::object_t currentPeriod;
-+
-+    currentPeriod["CorrectableECCErrorCount"] = 0;
-+    currentPeriod["UncorrectableECCErrorCount"] = 0;
-+    currentPeriod["IndeterminateCorrectableErrorCount"] = 0;
-+    currentPeriod["IndeterminateUncorrectableErrorCount"] = 0;
-+
-+    metricsObj["CurrentPeriod"] = std::move(currentPeriod);
-+
-+    // ExternalStorer would normally apply this as invariant if successful
-+    // The caller already applies "@odata.type" as invariant no matter what
-+    std::string url = "/redfish/v1/Systems/system/Memory/";
-+    url += dimm;
-+    url += "/MemoryMetrics";
-+
-+    metricsObj["@odata.id"] = std::move(url);
-+
-+    return metricsObj;
-+}
-+
-+// TODO(): This is cut-and-paste code with the ProcessorMetrics patch
-+// This function is only renamed because of compiler limitation
-+// Merge into a common utility function, once known where to keep it
-+inline int prefixStrToInt2(const std::string& prefix, const std::string& str)
-+{
-+    size_t lenPrefix = prefix.size();
-+    size_t lenStr = str.size();
-+
-+    // Content following prefix must be within 1 to 9 characters in length
-+    if (lenStr < (lenPrefix + 1))
-+    {
-+        BMCWEB_LOG_ERROR << "Index number too short";
-+        return -1;
-+    }
-+    if (lenStr > (lenPrefix + 9))
-+    {
-+        // This length limit prevents 32-bit rollover attacks
-+        BMCWEB_LOG_ERROR << "Index number too long";
-+        return -1;
-+    }
-+
-+    if (str.substr(0, lenPrefix) != prefix)
-+    {
-+        // Prefix does not match
-+        BMCWEB_LOG_ERROR << "Index number wrong prefix";
-+        return -1;
-+    }
-+
-+    std::string numText = str.substr(lenPrefix);
-+    size_t lenText = numText.size();
-+    size_t lenCheck = std::strlen(numText.c_str());
-+    if (lenText != lenCheck)
-+    {
-+        // This comparison prevents 0x00 string truncation attacks
-+        BMCWEB_LOG_ERROR << "Index number wrong length";
-+        return -1;
-+    }
-+
-+    // Cannot use std::stoi because stoi throws
-+    char* endptr = nullptr;
-+    long value = std::strtol(numText.c_str(), &endptr, 10);
-+    if (*endptr != '\0')
-+    {
-+        // The number was followed by extra content
-+        BMCWEB_LOG_ERROR << "Index number wrong content";
-+        return -1;
-+    }
-+
-+    int result = static_cast<int>(value);
-+    return result;
-+}
-+
-+inline int countDimm()
-+{
-+    // TODO(): This is a STUB
-+    return 256;
-+}
-+
-+inline bool checkDimmIndex(const std::string& dimmId)
-+{
-+    int bound = countDimm();
-+    int num = prefixStrToInt2("dimm", dimmId);
-+
-+    if (num < 0)
-+    {
-+        BMCWEB_LOG_ERROR << "DIMM index not parseable";
-+        return false;
-+    }
-+    if (num >= bound)
-+    {
-+        BMCWEB_LOG_ERROR << "DIMM index out of range";
-+        return false;
-+    }
-+
-+    return true;
-+}
-+
-+inline bool getExternalStorerMemoryMetrics(
-+    const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& dimmId)
-+{
-+    if (!checkDimmIndex(dimmId))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem checking DIMM index for " << dimmId;
-+        return false;
-+    }
-+
-+    std::shared_ptr<external_storer::Hook> hook = rememberDimmHook(dimmId);
-+    if (!hook)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem getting hook for " << dimmId;
-+        return false;
-+    }
-+
-+    std::string emptyString;
-+    hook->handleGetEntry(aResp, dimmId, emptyString, "MemoryMetrics");
-+    if (aResp->res.result() == boost::beast::http::status::not_found)
-+    {
-+        // Fake up a synthetic response to replace only the 404 error
-+        aResp->res.jsonValue = fakeMemoryMetrics(dimmId);
-+        aResp->res.result(boost::beast::http::status::ok);
-+    }
-+    if (aResp->res.result() != boost::beast::http::status::ok)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem getting file for " << dimmId;
-+        return false;
-+    }
-+
-+    // Apply invariants, to override whatever might have been in the file
-+    aResp->res.jsonValue["@odata.type"] = "#MemoryMetrics.v1_5_0.MemoryMetrics";
-+
-+    return true;
-+}
-+
-+inline void getMemoryMetrics(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
-+                             const std::string& dimmId)
-+{
-+    BMCWEB_LOG_DEBUG << "Get available memory metrics for DIMM.";
-+
-+    // This is the integration point for ExternalStorer with MemoryMetrics.
-+    // If locally-existing file exists, use ExternalStorer to return it.
-+    if (getExternalStorerMemoryMetrics(aResp, dimmId))
-+    {
-+        BMCWEB_LOG_DEBUG << "Successful local file read for " << dimmId;
-+        return;
-+    }
-+
-+    // No more falling back to D-Bus query, instead, simply give user 404
-+    BMCWEB_LOG_WARNING << "Memory metrics not found for " << dimmId;
-+    messages::resourceNotFound(aResp->res, "MemoryMetrics", dimmId);
-+}
-+
- inline void requestRoutesMemory(App& app)
- {
-     /**
-@@ -1162,4 +1383,28 @@ inline void requestRoutesMemory(App& app)
-         });
- }
- 
-+inline void requestRoutesMemoryMetrics(App& app)
-+{
-+    /**
-+     * Functions triggers appropriate requests on DBus
-+     */
-+    BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/Memory/<str>/MemoryMetrics/")
-+        .privileges(redfish::privileges::getMemoryMetrics)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app](const crow::Request& req,
-+                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                   const std::string& dimmId) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        asyncResp->res.jsonValue["@odata.type"] =
-+            "#MemoryMetrics.v1_5_0.MemoryMetrics";
-+        asyncResp->res.jsonValue["@odata.id"] =
-+            "/redfish/v1/Systems/system/Memory/" + dimmId + "/MemoryMetrics";
-+
-+        getMemoryMetrics(asyncResp, dimmId);
-+        });
-+}
-+
- } // namespace redfish
diff --git a/recipes-phosphor/interfaces/bmcweb/0002-bmcweb-agentless-Fault-Log-CPER-logs-Crashdumps.patch b/recipes-phosphor/interfaces/bmcweb/0002-bmcweb-agentless-Fault-Log-CPER-logs-Crashdumps.patch
deleted file mode 100644
index 9c37020..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0002-bmcweb-agentless-Fault-Log-CPER-logs-Crashdumps.patch
+++ /dev/null
@@ -1,228 +0,0 @@
-From 9db0d5081ef8640313fb7facab5b62368c0700e6 Mon Sep 17 00:00:00 2001
-From: Claire Weinan <cweinan@google.com>
-Date: Wed, 3 Aug 2022 17:59:24 -0700
-Subject: [PATCH] bmcweb: agentless: Fault Log CPER logs/Crashdumps
-
-The fault log manager (in the phosphor-debug-collector
-module) receives D-Bus signals to be notified when new CPER logs
-and Crashdumps are available in BMC. Signals for CPER logs come from
-the bios-bmc-smm-error-logger daemon and signals for crashdumps come
-from Intel's proprietary crashdump daemon.
-
-Here we add the ability to parse CPER log and crashdump information
-received from the fault log manager over D-Bus and output it in fault
-log entries.
-
-Example fault log entry referencing CPER log:
-{
-  "@odata.id": "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/19",
-  "@odata.type": "#LogEntry.v1_8_0.LogEntry",
-  "AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/1E5D8F94-2011-413A-B958-961797D78237/Entries/afe46336-4564-47d5-8df3-bac150122a9d",
-  "Created": "2022-07-25T14:56:36.477336+00:00",
-  "DiagnosticDataType": "OEM",
-  "EntryType": "Oem",
-  "Id": "19",
-  "Name": "FaultLog Dump Entry",
-  "OEMDiagnosticDataType": "OpenBMC Fault Log",
-  "OemRecordFormat": "CPER"
-}
-
-Example fault log entry referencing crashdump:
-{
-  "@odata.id": "/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/21",
-  "@odata.type": "#LogEntry.v1_8_0.LogEntry",
-  "AdditionalDataURI": "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/0",
-  "Created": "2022-07-25T14:59:24.113936+00:00",
-  "DiagnosticDataType": "OEM",
-  "EntryType": "Oem",
-  "Id": "21",
-  "Name": "FaultLog Dump Entry",
-  "OEMDiagnosticDataType": "OpenBMC Fault Log",
-  "OemRecordFormat": "Crashdump"
-}
-
-Tested:
-bmcweb unit tests passed.
-
-Get dump entries individually and as a collection.
-  Example commands:
-  curl -k -H "X-Auth-Token: $token" -X GET http://${bmc}/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries
-
-  curl -k -H "X-Auth-Token: $token" -X GET
-  http://${bmc}/redfish/v1/Managers/bmc/LogServices/FaultLog/Entries/1
-
-Redfish Service Validator succeeded on the following URI tree:
-  /redfish/v1/Managers/bmc/LogServices/FaultLog
-
-Patch Tracking Bug: b/240210399
-Upstream info / review:
-https://gerrit.openbmc.org/c/openbmc/bmcweb/+/55839
-Upstream-Status: Submitted
-Justification: Enable fault log for Fault Management/Agentless error
-logging infrastructure for Izumi MVP while upstream code review is
-still in progress.
-
-Signed-off-by: Claire Weinan <cweinan@google.com>
-Change-Id: I48ba79885ca1ac095606f815c0c3dc599807405d
-Signed-off-by: Willy Tu <wltu@google.com>
----
- redfish-core/lib/log_services.hpp | 96 ++++++++++++++++++++++++++++++-
- 1 file changed, 94 insertions(+), 2 deletions(-)
-
-diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
-index e67fa6cb..832583f2 100644
---- a/redfish-core/lib/log_services.hpp
-+++ b/redfish-core/lib/log_services.hpp
-@@ -337,6 +337,7 @@ inline void parseDumpEntryFromDbusObject(
-     const dbus::utility::ManagedObjectType::value_type& object,
-     std::string& dumpStatus, uint64_t& size, uint64_t& timestampUs,
-     std::string& originatorId, log_entry::OriginatorTypes& originatorType,
-+    std::string& entryType, std::string& primaryLogId,
-     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
- {
-     for (const auto& interfaceMap : object.second)
-@@ -430,6 +431,45 @@ inline void parseDumpEntryFromDbusObject(
-                 }
-             }
-         }
-+        else if (interfaceMap.first ==
-+                 "xyz.openbmc_project.Dump.Entry.FaultLog")
-+        {
-+            for (const auto& propertyMap : interfaceMap.second)
-+            {
-+                if (propertyMap.first == "Type")
-+                {
-+                    const std::string* entryTypePtr =
-+                        std::get_if<std::string>(&propertyMap.second);
-+                    if (entryTypePtr == nullptr)
-+                    {
-+                        messages::internalError(asyncResp->res);
-+                        break;
-+                    }
-+                    if (*entryTypePtr ==
-+                        "xyz.openbmc_project.Dump.Entry.FaultLog.FaultDataType.Crashdump")
-+                    {
-+                        entryType = "Crashdump";
-+                    }
-+                    else if (
-+                        *entryTypePtr ==
-+                        "xyz.openbmc_project.Dump.Entry.FaultLog.FaultDataType.CPER")
-+                    {
-+                        entryType = "CPER";
-+                    }
-+                }
-+                else if (propertyMap.first == "PrimaryLogId")
-+                {
-+                    const std::string* primaryLogIdPtr =
-+                        std::get_if<std::string>(&propertyMap.second);
-+                    if (primaryLogIdPtr == nullptr)
-+                    {
-+                        messages::internalError(asyncResp->res);
-+                        break;
-+                    }
-+                    primaryLogId = *primaryLogIdPtr;
-+                }
-+            }
-+        }
-     }
- }
- 
-@@ -516,6 +556,8 @@ inline void
-             uint64_t size = 0;
-             std::string dumpStatus;
-             std::string originatorId;
-+            std::string primaryLogId;
-+            std::string entryType;
-             log_entry::OriginatorTypes originatorType =
-                 log_entry::OriginatorTypes::Internal;
-             nlohmann::json::object_t thisEntry;
-@@ -528,7 +570,7 @@ inline void
- 
-             parseDumpEntryFromDbusObject(object, dumpStatus, size, timestampUs,
-                                          originatorId, originatorType,
--                                         asyncResp);
-+                                         entryType, primaryLogId, asyncResp);
- 
-             if (dumpStatus !=
-                     "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
-@@ -559,6 +601,29 @@ inline void
-                     entriesPath + entryID + "/attachment";
-                 thisEntry["AdditionalDataSizeBytes"] = size;
-             }
-+            else if (dumpType == "FaultLog")
-+            {
-+                thisEntry["Created"] =
-+                    redfish::time_utils::getDateTimeUintUs(timestampUs);
-+                thisEntry["DiagnosticDataType"] = "OEM";
-+                thisEntry["OEMDiagnosticDataType"] = "OpenBMC Fault Log";
-+                thisEntry["EntryType"] = "Oem";
-+
-+                if (entryType == "CPER")
-+                {
-+                    thisEntry["AdditionalDataURI"] =
-+                        "/redfish/v1/Systems/system/LogServices/" +
-+                        primaryLogId;
-+                    thisEntry["OemRecordFormat"] = "CPER";
-+                }
-+                else if (entryType == "Crashdump")
-+                {
-+                    thisEntry["AdditionalDataURI"] =
-+                        "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
-+                        primaryLogId;
-+                    thisEntry["OemRecordFormat"] = "Crashdump";
-+                }
-+            }
-             else if (dumpType == "System")
-             {
-                 thisEntry["DiagnosticDataType"] = "OEM";
-@@ -614,12 +679,15 @@ inline void
-             uint64_t size = 0;
-             std::string dumpStatus;
-             std::string originatorId;
-+            std::string primaryLogId;
-+            std::string entryType;
-             log_entry::OriginatorTypes originatorType =
-                 log_entry::OriginatorTypes::Internal;
- 
-             parseDumpEntryFromDbusObject(objectPath, dumpStatus, size,
-                                          timestampUs, originatorId,
--                                         originatorType, asyncResp);
-+                                         originatorType, entryType,
-+                                         primaryLogId, asyncResp);
- 
-             if (dumpStatus !=
-                     "xyz.openbmc_project.Common.Progress.OperationStatus.Completed" &&
-@@ -654,6 +722,30 @@ inline void
-                     entriesPath + entryID + "/attachment";
-                 asyncResp->res.jsonValue["AdditionalDataSizeBytes"] = size;
-             }
-+            else if (dumpType == "FaultLog")
-+            {
-+                asyncResp->res.jsonValue["Created"] =
-+                    redfish::time_utils::getDateTimeUintUs(timestampUs);
-+                asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
-+                asyncResp->res.jsonValue["OEMDiagnosticDataType"] =
-+                    "OpenBMC Fault Log";
-+                asyncResp->res.jsonValue["EntryType"] = "Oem";
-+
-+                if (entryType == "CPER")
-+                {
-+                    asyncResp->res.jsonValue["AdditionalDataURI"] =
-+                        "/redfish/v1/Systems/system/LogServices/" +
-+                        primaryLogId;
-+                    asyncResp->res.jsonValue["OemRecordFormat"] = "CPER";
-+                }
-+                else if (entryType == "Crashdump")
-+                {
-+                    asyncResp->res.jsonValue["AdditionalDataURI"] =
-+                        "/redfish/v1/Systems/system/LogServices/Crashdump/Entries/" +
-+                        primaryLogId;
-+                    asyncResp->res.jsonValue["OemRecordFormat"] = "Crashdump";
-+                }
-+            }
-             else if (dumpType == "System")
-             {
-                 asyncResp->res.jsonValue["DiagnosticDataType"] = "OEM";
--- 
-2.40.0.348.gf938b09366-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb/0003-bmcweb-ExternalStorer-for-ProcessorMetrics.patch b/recipes-phosphor/interfaces/bmcweb/0003-bmcweb-ExternalStorer-for-ProcessorMetrics.patch
deleted file mode 100644
index a1fda11..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0003-bmcweb-ExternalStorer-for-ProcessorMetrics.patch
+++ /dev/null
@@ -1,571 +0,0 @@
-From 430d4d5a4272e9ca4d9ac5e96ba8787e72561ade Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Mon, 27 Jun 2022 02:37:01 -0700
-Subject: [PATCH 36/40] bmcweb: ExternalStorer for ProcessorMetrics
-
-This adds support for ProcessorMetrics, using ExternalStorer.
-
-Implementation is similar to what was done for MemoryMetrics,
-except there is no longer any D-Bus fallback, it is a pure filesystem
-approach. Only GET commands are supported, for now. It is expected
-that a local daemon will be creating and updating the files.
-
-Filesystem structure on disk will follow the layout of the mockups,
-similar to how it was done for LogServices and MemoryMetrics.
-
-Known issues:
-No detection of installed quantity of processors, cores, and threads,
-so ProcessorMetrics could allow access to more than actually exist.
-Only GET support (no PATCH or other way for bmcweb user to modify),
-so the impact of this is minor, no way to exploit remotely.
-
-Patch Tracking Bug: b/237316154
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/bmcweb/+/54912/5
-Upstream-Status: Pending
-Justification:
-This is a new feature, and has not been reviewed yet.
-
-Google-Bug-Id: 230802996
-Signed-off-by: Josh Lehan <krellan@google.com>
-Change-Id: Ib4c4fa541fe4c0f857ff2fb0808f8d5e81e8a882
----
- redfish-core/lib/processor.hpp | 483 ++++++++++++++++++++++++++++++++-
- 1 file changed, 481 insertions(+), 2 deletions(-)
-
-diff --git a/redfish-core/lib/processor.hpp b/redfish-core/lib/processor.hpp
-index c2b89358..2b4461e3 100644
---- a/redfish-core/lib/processor.hpp
-+++ b/redfish-core/lib/processor.hpp
-@@ -893,6 +893,13 @@ inline void getProcessorData(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
-         }
-     }
-     getCpuChassisAssociation(aResp, processorId, objectPath, jsonPtr);
-+
-+    // Inserting the "Metrics" JSON stanza
-+    std::string metricsUrl = aResp->res.jsonValue["@odata.id"];
-+    metricsUrl += "/ProcessorMetrics";
-+    nlohmann::json::object_t metricsObj;
-+    metricsObj["@odata.id"] = std::move(metricsUrl);
-+    aResp->res.jsonValue["Metrics"] = std::move(metricsObj);
- }
- 
- template <typename Handler>
-@@ -1100,6 +1107,13 @@ inline void getCoreThreadDataByService(
-     {
-         aResp->res.jsonValue[jsonPtr]["Status"]["Health"] = "Critical";
-     }
-+
-+    // Inserting the "Metrics" JSON stanza
-+    std::string metricsUrl = aResp->res.jsonValue["@odata.id"];
-+    metricsUrl += "/ProcessorMetrics";
-+    nlohmann::json::object_t metricsObj;
-+    metricsObj["@odata.id"] = std::move(metricsUrl);
-+    aResp->res.jsonValue["Metrics"] = std::move(metricsObj);
- }
- 
- inline void getSubProcessorThreadData(
-@@ -1361,6 +1375,8 @@ inline void
-     {
-         aResp->res.jsonValue[jsonPtr]["Status"]["Health"] = "Critical";
-     }
-+
-+    // The "Metrics" stanza is deliberately omitted at the Core layer
- }
- 
- inline void getSubProcessorCoreData(
-@@ -2312,6 +2328,467 @@ inline void requestRoutesOperatingConfig(App& app)
-         });
- }
- 
-+inline int countProcessors(void)
-+{
-+    // TODO(): This is a STUB
-+    // For now, this is OK, since it is only used for syntax validation.
-+    // However, when and if writing (POST, PATCH, etc.) is allowed,
-+    // we will need to have better bounds-checking here,
-+    // otherwise it will be trivial for a writer to DoS all the storage.
-+    return 16;
-+}
-+
-+inline int countCores(int cpuIndex)
-+{
-+    // TODO(): This is a STUB, see above comment
-+    (void)cpuIndex;
-+    return 256;
-+}
-+
-+inline int countThreads(int cpuIndex, int coreIndex)
-+{
-+    // TODO(): This is a STUB, see above comment
-+    (void)cpuIndex;
-+    (void)coreIndex;
-+    return 16;
-+}
-+
-+inline int prefixStrToInt(const std::string& prefix, const std::string& str)
-+{
-+    size_t lenPrefix = prefix.size();
-+    size_t lenStr = str.size();
-+
-+    // Content following prefix must be within 1 to 9 characters in length
-+    if (lenStr < (lenPrefix + 1))
-+    {
-+        BMCWEB_LOG_ERROR << "Index number too short";
-+        return -1;
-+    }
-+    if (lenStr > (lenPrefix + 9))
-+    {
-+        // This length limit prevents 32-bit rollover attacks
-+        BMCWEB_LOG_ERROR << "Index number too long";
-+        return -1;
-+    }
-+
-+    if (str.substr(0, lenPrefix) != prefix)
-+    {
-+        // Prefix does not match
-+        BMCWEB_LOG_ERROR << "Index number wrong prefix";
-+        return -1;
-+    }
-+
-+    std::string numText = str.substr(lenPrefix);
-+    size_t lenText = numText.size();
-+    size_t lenCheck = std::strlen(numText.c_str());
-+    if (lenText != lenCheck)
-+    {
-+        // This comparison prevents 0x00 string truncation attacks
-+        BMCWEB_LOG_ERROR << "Index number wrong length";
-+        return -1;
-+    }
-+
-+    // Cannot use std::stoi because stoi throws
-+    char* endptr = nullptr;
-+    long value = std::strtol(numText.c_str(), &endptr, 10);
-+    if (*endptr != '\0')
-+    {
-+        // The number was followed by extra content
-+        BMCWEB_LOG_ERROR << "Index number wrong content";
-+        return -1;
-+    }
-+
-+    int result = static_cast<int>(value);
-+    return result;
-+}
-+
-+inline int cpuArgToIndex(const std::string& cpuArg)
-+{
-+    int bound = countProcessors();
-+    int num = prefixStrToInt("cpu", cpuArg);
-+    if (num >= bound)
-+    {
-+        BMCWEB_LOG_ERROR << "Processor index out of range";
-+        return -1;
-+    }
-+    return num;
-+}
-+
-+inline int coreArgToIndex(const std::string& coreArg, int cpuIndex)
-+{
-+
-+    int bound = countCores(cpuIndex);
-+    int num = prefixStrToInt("core", coreArg);
-+    if (num >= bound)
-+    {
-+        BMCWEB_LOG_ERROR << "Subprocessor core index out of range";
-+        return -1;
-+    }
-+    return num;
-+}
-+
-+inline int threadArgToIndex(const std::string& threadArg, int cpuIndex,
-+                            int coreIndex)
-+{
-+
-+    int bound = countThreads(cpuIndex, coreIndex);
-+    int num = prefixStrToInt("thread", threadArg);
-+    if (num >= bound)
-+    {
-+        BMCWEB_LOG_ERROR << "Subprocessor thread index out of range";
-+        return -1;
-+    }
-+    return num;
-+}
-+
-+inline int validateCpu(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                       const std::string& cpuArg)
-+{
-+    int cpuIndex = cpuArgToIndex(cpuArg);
-+    if (cpuIndex < 0)
-+    {
-+        messages::resourceNotFound(asyncResp->res, "Processor", cpuArg);
-+    }
-+    return cpuIndex;
-+}
-+
-+inline int validateCpuCore(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                           const std::string& cpuArg,
-+                           const std::string& coreArg)
-+{
-+    int cpuIndex = validateCpu(asyncResp, cpuArg);
-+    if (cpuIndex < 0)
-+    {
-+        return cpuIndex;
-+    }
-+    int coreIndex = coreArgToIndex(coreArg, cpuIndex);
-+    if (coreIndex < 0)
-+    {
-+        messages::resourceNotFound(asyncResp->res, "Core", coreArg);
-+    }
-+    return coreIndex;
-+}
-+
-+inline int
-+    validateCpuCoreThread(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                          const std::string& cpuArg, const std::string& coreArg,
-+                          const std::string& threadArg)
-+{
-+    int cpuIndex = validateCpu(asyncResp, cpuArg);
-+    if (cpuIndex < 0)
-+    {
-+        return cpuIndex;
-+    }
-+    int coreIndex = validateCpuCore(asyncResp, cpuArg, coreArg);
-+    if (coreIndex < 0)
-+    {
-+        return coreIndex;
-+    }
-+    int threadIndex = threadArgToIndex(threadArg, cpuIndex, coreIndex);
-+    if (threadIndex < 0)
-+    {
-+        messages::resourceNotFound(asyncResp->res, "Thread", threadArg);
-+    }
-+    return threadIndex;
-+}
-+
-+// Creates a generic ExternalStorer hook, should be 2 directories above
-+// Each hook contains instances, each instance contains entries
-+// Omit the leading "/redfish/v1" from pathBase, and no trailing slash
-+inline external_storer::Hook makeExternalStorerHook(const std::string& pathBase)
-+{
-+    const std::string emptyString;
-+    const std::vector<std::string> emptyList;
-+
-+    return {pathBase, emptyString, emptyList, emptyList};
-+}
-+
-+// Creates a generic ExternalStorer instance within the given hook
-+// The content will be minimal, just enough to create filename on disk
-+// Returns true if successful
-+inline bool makeExternalStorerInstance(
-+    const std::shared_ptr<external_storer::Hook>& hook, const std::string& id)
-+{
-+    auto respGet = std::make_shared<bmcweb::AsyncResp>();
-+
-+    hook->handleGetInstance(respGet, id);
-+    if (respGet->res.result() == boost::beast::http::status::ok)
-+    {
-+        // Already exists, nothing more needs to be done
-+        return true;
-+    }
-+
-+    boost::beast::http::request<boost::beast::http::string_body> upBody;
-+    std::error_code ec;
-+
-+    // Create instance, with given ID, which will become filename on disk
-+    auto upJson = nlohmann::json::object();
-+    upJson["Id"] = id;
-+
-+    // Must supply 4th argument to avoid throwing exceptions
-+    upBody.body() =
-+        upJson.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
-+    crow::Request reqCreate{upBody, ec};
-+
-+    auto respCreate = std::make_shared<bmcweb::AsyncResp>();
-+
-+    hook->handleCreateInstance(reqCreate, respCreate);
-+    if (respCreate->res.result() != boost::beast::http::status::created)
-+    {
-+        BMCWEB_LOG_ERROR << "Problem creating ExternalStorer instance " << id;
-+        return false;
-+    }
-+
-+    return true;
-+}
-+
-+// Remembers toplevel Processors hook, there is only one
-+// Each instance in it represents a CPU and is named like cpuArg
-+// Each instance contains only one entry, "ProcessorMetrics"
-+inline std::shared_ptr<external_storer::Hook>
-+    rememberProcessorHook(const std::string& cpuArg)
-+{
-+    static std::shared_ptr<external_storer::Hook> hookProcessor = nullptr;
-+
-+    if (!hookProcessor)
-+    {
-+        // If not already remembered by static variable, create hook
-+        hookProcessor = std::make_shared<external_storer::Hook>(
-+            makeExternalStorerHook("Systems/system/Processors"));
-+    }
-+
-+    if (!(makeExternalStorerInstance(hookProcessor, cpuArg)))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem remembering hook for " << cpuArg;
-+        return nullptr;
-+    }
-+
-+    return hookProcessor;
-+}
-+
-+// Remembers lowest-level SubProcessors hook
-+// There is one hook for each unique (CPU, thread) tuple
-+// Each instance in it represents a thread and is named like threadArg
-+// Each instance contains only one entry, "ProcessorMetrics"
-+inline std::shared_ptr<external_storer::Hook>
-+    rememberThreadHook(const std::string& cpuArg, const std::string& coreArg,
-+                       const std::string& threadArg)
-+{
-+    static std::map<
-+        std::string,
-+        std::map<std::string, std::shared_ptr<external_storer::Hook>>>
-+        hookMaps;
-+
-+    auto findCpu = hookMaps.find(cpuArg);
-+    if (findCpu == hookMaps.end())
-+    {
-+        hookMaps[cpuArg] = {};
-+        findCpu = hookMaps.find(cpuArg);
-+    }
-+
-+    auto findCore = findCpu->second.find(coreArg);
-+    if (findCore == findCpu->second.end())
-+    {
-+        (findCpu->second)[coreArg] = {};
-+        findCore = findCpu->second.find(coreArg);
-+    }
-+
-+    std::shared_ptr<external_storer::Hook> hookThread = findCore->second;
-+
-+    if (!hookThread)
-+    {
-+        std::string path = "Systems/system/Processors/" + cpuArg +
-+                           "/SubProcessors/" + coreArg + "/SubProcessors";
-+        hookThread = std::make_shared<external_storer::Hook>(
-+            makeExternalStorerHook(path));
-+        findCore->second = hookThread;
-+    }
-+
-+    if (!(makeExternalStorerInstance(hookThread, threadArg)))
-+    {
-+        BMCWEB_LOG_ERROR << "Problem remembering hook for " << threadArg;
-+        return nullptr;
-+    }
-+
-+    return hookThread;
-+}
-+
-+inline nlohmann::json fakeProcessorMetrics(const std::string& cpu,
-+                                           const std::string& core,
-+                                           const std::string& thread)
-+{
-+    nlohmann::json::object_t metricsObj;
-+
-+    metricsObj["Id"] = "Metrics";
-+    metricsObj["Name"] = "Processor Metrics";
-+
-+    // The different levels have slightly different default content
-+    if (!thread.empty())
-+    {
-+        // Thread level
-+        metricsObj["CorrectableCoreErrorCount"] = 0;
-+        metricsObj["UncorrectableCoreErrorCount"] = 0;
-+    }
-+    else
-+    {
-+        // CPU level
-+        metricsObj["CorrectableOtherErrorCount"] = 0;
-+        metricsObj["UncorrectableOtherErrorCount"] = 0;
-+    }
-+
-+    // ExternalStorer would normally apply this as invariant if successful
-+    // The caller already applies "@odata.type" as invariant no matter what
-+    std::string url = "/redfish/v1/Systems/system/Processors/";
-+    url += cpu;
-+    if (!core.empty())
-+    {
-+        url += "/SubProcessors/";
-+        url += core;
-+    }
-+    if (!thread.empty())
-+    {
-+        url += "/SubProcessors/";
-+        url += thread;
-+    }
-+    url += "/ProcessorMetrics";
-+
-+    metricsObj["@odata.id"] = std::move(url);
-+
-+    return metricsObj;
-+}
-+
-+inline void
-+    handleGetCpuMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                        const std::string& cpuArg)
-+{
-+    if (validateCpu(asyncResp, cpuArg) < 0)
-+    {
-+        // Error reply already composed
-+        return;
-+    }
-+
-+    std::shared_ptr<external_storer::Hook> hook = rememberProcessorHook(cpuArg);
-+    if (!hook)
-+    {
-+        messages::resourceNotFound(asyncResp->res, "Processor", cpuArg);
-+        return;
-+    }
-+
-+    std::string emptyString;
-+    hook->handleGetEntry(asyncResp, cpuArg, emptyString, "ProcessorMetrics");
-+    if (asyncResp->res.result() == boost::beast::http::status::not_found)
-+    {
-+        // Fake up a synthetic response to replace only the 404 error
-+        asyncResp->res.jsonValue = fakeProcessorMetrics(cpuArg, "", "");
-+        asyncResp->res.result(boost::beast::http::status::ok);
-+    }
-+    if (asyncResp->res.result() != boost::beast::http::status::ok)
-+    {
-+        // Error reply already composed
-+        return;
-+    }
-+
-+    // Apply invariants, to override whatever might have been in the file
-+    asyncResp->res.jsonValue["@odata.type"] =
-+        "#ProcessorMetrics.v1_5_0.ProcessorMetrics";
-+}
-+
-+inline void handleGetCpuCoreThreadMetrics(
-+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+    const std::string& cpuArg, const std::string& coreArg,
-+    const std::string& threadArg)
-+{
-+    if (validateCpuCoreThread(asyncResp, cpuArg, coreArg, threadArg) < 0)
-+    {
-+        return;
-+    }
-+
-+    std::shared_ptr<external_storer::Hook> hook =
-+        rememberThreadHook(cpuArg, coreArg, threadArg);
-+    if (!hook)
-+    {
-+        messages::resourceNotFound(asyncResp->res, "Thread", threadArg);
-+        return;
-+    }
-+
-+    std::string emptyString;
-+    hook->handleGetEntry(asyncResp, threadArg, emptyString, "ProcessorMetrics");
-+    if (asyncResp->res.result() == boost::beast::http::status::not_found)
-+    {
-+        // Fake up a synthetic response to replace only the 404 error
-+        asyncResp->res.jsonValue =
-+            fakeProcessorMetrics(cpuArg, coreArg, threadArg);
-+        asyncResp->res.result(boost::beast::http::status::ok);
-+    }
-+    if (asyncResp->res.result() != boost::beast::http::status::ok)
-+    {
-+        // Error reply already composed
-+        return;
-+    }
-+
-+    // Apply invariants, to override whatever might have been in the file
-+    asyncResp->res.jsonValue["@odata.type"] =
-+        "#ProcessorMetrics.v1_5_0.ProcessorMetrics";
-+}
-+
-+// The glue layers are already handled by these functions elsewhere:
-+// Processors = requestRoutesProcessorCollection()
-+// Processors/cpuX = requestRoutesProcessor()
-+// Processors/cpuX/SubProcessors = requestRoutesSubProcessorCoreCollection()
-+// Processors/cpuX/SubProcessors/coreY = requestRoutesSubProcessorCore()
-+// Processors/cpuX/SubProcessors/coreY/SubProcessors =
-+// requestRoutesSubProcessorThreadCollection()
-+// Processors/cpuX/SubProcessors/coreY/SubProcessors/threadZ =
-+// requestRoutesSubProcessorThread()
-+
-+// The "ProcessorMetrics" endpoints are below those glue layers:
-+// Processors/cpuX/ProcessorMetrics = handled here
-+// Processors/cpuX/SubProcessors/coreY/ProcessorMetrics = intentionally
-+// omitted
-+// Processors/cpuX/SubProcessors/coreY/SubProcessors/threadZ/ProcessorMetrics
-+// = handled here
-+inline void requestRoutesProcessorMetrics(App& app)
-+{
-+    BMCWEB_ROUTE(
-+        app, "/redfish/v1/Systems/system/Processors/<str>/ProcessorMetrics/")
-+        .privileges(redfish::privileges::getProcessor)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app](const crow::Request& req,
-+                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                   const std::string& cpuArg) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        handleGetCpuMetrics(asyncResp, cpuArg);
-+        });
-+
-+    BMCWEB_ROUTE(
-+        app,
-+        "/redfish/v1/Systems/system/Processors/<str>/SubProcessors/<str>/SubProcessors/<str>/ProcessorMetrics/")
-+        .privileges(redfish::privileges::getProcessor)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app](const crow::Request& req,
-+                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                   const std::string& cpuArg, const std::string& coreArg,
-+                   const std::string& threadArg) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        handleGetCpuCoreThreadMetrics(asyncResp, cpuArg, coreArg, threadArg);
-+        });
-+
-+    // TODO(): Implement corresponding PATCH methods
-+    // The POST method is not correct to use here,
-+    // as the result location is already well-known name "ProcessorMetrics".
-+    // and from the user point of view it always exists,
-+    // so the creation operation is not the correct operation.
-+    // The PUT method also might not be correct to use here,
-+    // as it would fully replace all content at the result location,
-+    // but the expectation is to only selectively replace some content.
-+}
-+
- inline void requestRoutesProcessorCollection(App& app)
- {
-     /**
-@@ -2448,12 +2925,14 @@ inline void requestRoutesProcessor(App& app)
-                                                appliedConfigUri));
-         }
-         });
-+
-+    requestRoutesProcessorMetrics(app);
- }
- 
- inline void requestRoutesSubProcessorCoreCollection(App& app)
- {
-     BMCWEB_ROUTE(app,
--                 "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors")
-+                 "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/")
-         .privileges(redfish::privileges::headProcessorCollection)
-         .methods(boost::beast::http::verb::head)(std::bind_front(
-             handleSubProcessorCoreCollectionHead, std::ref(app)));
-@@ -2488,7 +2967,7 @@ inline void requestRoutesSubProcessorCoreCollection(App& app)
- inline void requestRoutesSubProcessorCore(App& app)
- {
-     BMCWEB_ROUTE(
--        app, "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>")
-+        app, "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/")
-         .privileges(redfish::privileges::headProcessor)
-         .methods(boost::beast::http::verb::head)(
-             std::bind_front(handleSubProcessorCoreHead, std::ref(app)));
--- 
-2.39.1.581.gbfd45094c4-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb/0004-Processor-Include-Processor-Metrics-in-Efficient-Exp.patch b/recipes-phosphor/interfaces/bmcweb/0004-Processor-Include-Processor-Metrics-in-Efficient-Exp.patch
deleted file mode 100644
index a869665..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0004-Processor-Include-Processor-Metrics-in-Efficient-Exp.patch
+++ /dev/null
@@ -1,421 +0,0 @@
-From 485914ac02133d43ccf21a41a22bd3cea51d9a28 Mon Sep 17 00:00:00 2001
-From: Nikhil Namjoshi <nikhilnamjoshi@google.com>
-Date: Thu, 8 Sep 2022 23:07:43 +0000
-Subject: [PATCH] Processor: Include Processor Metrics in Efficient Expand
-
-Processor and Thread Metric resource falls under Processor
-URI and are generated by external storer. This commit ensures
-that we dump there metrics data as part of Processor level
-Efficient Expand.
-
-Tested:
-
-Redfish Validator passed
-
-Verified that the response data with efficient expand
-matches exactly with general expand on a selected system
-
-wget -qO- 'http://localhost:80/redfish/v1/Systems/system
-    /Processors?$expand=.($levels=6)'
-
-{
-  "@odata.id": "/redfish/v1/Systems/system/Processors",
-  "@odata.type": "#ProcessorCollection.ProcessorCollection",
-  "Members": [
-    {
-      "@odata.id": "/redfish/v1/Systems/system/Processors/cpu0",
-      "@odata.type": "#Processor.v1_11_0.Processor",
-      "Id": "cpu0",
-      "InstructionSet": "....",
-      "Links": {
-        "Chassis": {
-          "@odata.id": "..."
-        }
-      }
-      "Location": {
-        "PartLocation": {
-          "LocationType": "Slot",
-          "ServiceLabel": "CPU0"
-        }
-      },
-      "Manufacturer": ".....",
-      "MaxSpeedMHz": .....,
-      "Metrics": {
-        "@odata.id": "/redfish/v1/Systems/system
-            /Processors/cpu0/ProcessorMetrics",
-        "@odata.type": "#ProcessorMetrics.v1_5_0.ProcessorMetrics",
-        "CorrectableOtherErrorCount": 0,
-        "Id": "Metrics",
-        "Name": "Processor Metrics",
-        "UncorrectableOtherErrorCount": 0
-      },
-      "Name": "Processor",
-      "PartNumber": "...........",
-      "ProcessorArchitecture": ".......",
-      "ProcessorId": {
-        "EffectiveFamily": ".....",
-        "EffectiveModel": ".......",
-        "IdentificationRegisters": "....",
-        "MicrocodeInfo": "....",
-        "ProtectedIdentificationNumber": "....",
-        "Step": "...."
-      },
-      "ProcessorType": "CPU",
-      "SerialNumber": ".............",
-      "Socket": "CPU0",
-      "SparePartNumber": "",
-      "Status": {
-        "Health": "OK",
-        "State": "Enabled"
-      },
-      "SubProcessors": {
-        "@odata.id": "/redfish/v1/Systems/system
-            /Processors/cpu0/SubProcessors",
-        "@odata.type": "#ProcessorCollection.ProcessorCollection",
-        "Members": [
-          {
-            "@odata.id": "/redfish/v1/Systems/system
-                /Processors/cpu0/SubProcessors/cpu0_core1",
-            "@odata.type": "#Processor.v1_11_0.Processor",
-            "Id": "cpu0_core1",
-            "Name": "SubProcessor",
-            "ProcessorId": {
-              "MicrocodeInfo": "..........."
-            },
-            "Status": {
-              "Health": "Critical",
-              "State": "Absent"
-            },
-            "SubProcessors": {
-              "@odata.id": "/redfish/v1/Systems/system
-                  /Processors/cpu0/SubProcessors/cpu0_core1
-                  /SubProcessors",
-              "@odata.type":
-                  "#ProcessorCollection.ProcessorCollection",
-              "Members": [
-                {
-                  "@odata.id": "/redfish/v1/Systems/system
-                      /Processors/cpu0/SubProcessors/cpu0_core1
-                      /SubProcessors/thread0",
-                  "@odata.type": "#Processor.v1_11_0.Processor",
-                  "Id": "thread0",
-                  "Metrics": {
-                    "@odata.id": "/redfish/v1/Systems/system
-                        /Processors/cpu0/SubProcessors/cpu0_core1
-                        /SubProcessors/thread0/ProcessorMetrics",
-                    "@odata.type": "#ProcessorMetrics.v1_5_0.ProcessorMetrics",
-                    "CorrectableCoreErrorCount": 0,
-                    "Id": "Metrics",
-                    "Name": "Processor Metrics",
-                    "UncorrectableCoreErrorCount": 0
-                  },
-                  "Name": "SubProcessor",
-                  "ProcessorId": {
-                    "MicrocodeInfo": "........."
-                  },
-                  "Status": {
-                    "Health": "Critical",
-                    "State": "Absent"
-                  }
-                },
-                {
-                  "@odata.id": "/redfish/v1/Systems/system
-                      /Processors/cpu0/SubProcessors/cpu0_core1
-                      /SubProcessors/thread1",
-                  "@odata.type": "#Processor.v1_11_0.Processor",
-                  "Id": "thread1",
-                  "Metrics": {
-                    "@odata.id": "/redfish/v1/Systems/system
-                        /Processors/cpu0/SubProcessors/cpu0_core1
-                        /SubProcessors/thread1/ProcessorMetrics",
-                    "@odata.type": "#ProcessorMetrics.v1_5_0.ProcessorMetrics",
-                    "CorrectableCoreErrorCount": 0,
-                    "Id": "Metrics",
-                    "Name": "Processor Metrics",
-                    "UncorrectableCoreErrorCount": 0
-                  },
-                  "Name": "SubProcessor",
-                  "ProcessorId": {
-                    "MicrocodeInfo": ".........."
-                  },
-                  "Status": {
-                    "Health": "Critical",
-                    "State": "Absent"
-                  }
-                }
-              ],
-              "Members@odata.count": ...,
-              "Name": "SubProcessor Collection"
-            }
-          }
-        ]
-        "Members@odata.count": ...,
-        "Name": "SubProcessor Collection"
-      }
-    }
-  ]
-  "Members@odata.count": ...,
-  "Name": "Processor Collection"
-}
-
-Patch Tracking Bug: b/245991303
-Upstream info / review:  Yet to create upstream review
-Upstream-Status: Pending
-Justification: This patch has several dependencies on previous patches
-and those need to sorted before creating upstream review.
-
-Signed-off-by: Nikhil Namjoshi <nikhilnamjoshi@google.com>
-Change-Id: I9954cee0a4f3ee89e0394a85c2aaaebfd64e43b7
-Signed-off-by: Willy Tu <wltu@google.com>
-
----
- redfish-core/lib/external_storer.hpp          |  7 +-
- redfish-core/lib/memory.hpp                   |  3 +-
- redfish-core/lib/processor.hpp                | 76 +++++++++++++------
- .../redfish-core/lib/external_storer_test.cpp |  7 +-
- 4 files changed, 65 insertions(+), 28 deletions(-)
-
-diff --git a/redfish-core/lib/external_storer.hpp b/redfish-core/lib/external_storer.hpp
-index 976a7ac5..cb60f567 100644
---- a/redfish-core/lib/external_storer.hpp
-+++ b/redfish-core/lib/external_storer.hpp
-@@ -130,6 +130,7 @@ class Hook
-                          const std::string& instance,
-                          const std::string& middle);
-     void handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                        const nlohmann::json::json_pointer& jsonPtr,
-                         const std::string& instance, const std::string& middle,
-                         const std::string& entry);
- 
-@@ -946,6 +947,7 @@ void Hook::handleGetMiddle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
- }
- 
- void Hook::handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                          const nlohmann::json::json_pointer& jsonPtr,
-                           const std::string& idInstance,
-                           const std::string& keywordMiddle,
-                           const std::string& idEntry)
-@@ -1009,7 +1011,7 @@ void Hook::handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
- 
-     redfish::messages::success(asyncResp->res);
- 
--    asyncResp->res.jsonValue = std::move(content);
-+    asyncResp->res.jsonValue[jsonPtr] = std::move(content);
- }
- 
- void Hook::deleteAll(void)
-@@ -1175,7 +1177,8 @@ inline void requestRoutesExternalStorerLogServices(
-         {
-             return;
-         }
--        hook->handleGetEntry(asyncResp, instance, middle, entry);
-+        hook->handleGetEntry(asyncResp, ""_json_pointer, instance, middle,
-+                             entry);
-         });
- 
-     // The integration point also needs to access the correct hook
-diff --git a/redfish-core/lib/memory.hpp b/redfish-core/lib/memory.hpp
-index 4b139654..af9c3cf6 100644
---- a/redfish-core/lib/memory.hpp
-+++ b/redfish-core/lib/memory.hpp
-@@ -1325,7 +1325,8 @@ inline bool getExternalStorerMemoryMetrics(
-     }
- 
-     std::string emptyString;
--    hook->handleGetEntry(aResp, dimmId, emptyString, "MemoryMetrics");
-+    hook->handleGetEntry(aResp, ""_json_pointer, dimmId, emptyString,
-+                         "MemoryMetrics");
-     if (aResp->res.result() == boost::beast::http::status::not_found)
-     {
-         // Fake up a synthetic response to replace only the 404 error
-diff --git a/redfish-core/lib/processor.hpp b/redfish-core/lib/processor.hpp
-index 840804d3..2b243dd2 100644
---- a/redfish-core/lib/processor.hpp
-+++ b/redfish-core/lib/processor.hpp
-@@ -79,6 +79,16 @@ inline void getProcessorCollectionWithExpand(
-     const dbus::utility::MapperGetSubTreeResponse& subtree,
-     const ManagedObjectByServiceMap& managedObjectsMap, uint8_t expandLevel);
- 
-+inline void handleGetCpuCoreThreadMetrics(
-+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+    const nlohmann::json::json_pointer& jsonPtr, const std::string& cpuArg,
-+    const std::string& coreArg, const std::string& threadArg);
-+
-+inline void
-+    handleGetCpuMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                        const nlohmann::json::json_pointer& jsonPtr,
-+                        const std::string& cpuArg);
-+
- /**
-  * @brief Returns a collection of dbus interfaces applicable to SubProcessor's
-  * subtree.
-@@ -995,12 +1005,20 @@ inline void
-     }
-     getCpuChassisAssociation(aResp, processorId, objectPath, jsonPtr);
- 
--    // Inserting the "Metrics" JSON stanza
--    std::string metricsUrl = aResp->res.jsonValue["@odata.id"];
--    metricsUrl += "/ProcessorMetrics";
--    nlohmann::json::object_t metricsObj;
--    metricsObj["@odata.id"] = std::move(metricsUrl);
--    aResp->res.jsonValue["Metrics"] = std::move(metricsObj);
-+    if (expandLevel > 0)
-+    {
-+        nlohmann::json::json_pointer metricsPtr = jsonPtr / "Metrics";
-+        handleGetCpuMetrics(aResp, metricsPtr, processorId);
-+    }
-+    else
-+    {
-+        // Inserting the "Metrics" JSON stanza
-+        std::string metricsUrl = aResp->res.jsonValue[jsonPtr]["@odata.id"];
-+        metricsUrl += "/ProcessorMetrics";
-+        nlohmann::json::object_t metricsObj;
-+        metricsObj["@odata.id"] = std::move(metricsUrl);
-+        aResp->res.jsonValue[jsonPtr]["Metrics"] = std::move(metricsObj);
-+    }
- }
- 
- template <typename Handler>
-@@ -1235,12 +1253,21 @@ inline void
-         aResp->res.jsonValue[jsonPtr]["Status"]["Health"] = "Critical";
-     }
- 
--    // Inserting the "Metrics" JSON stanza
--    std::string metricsUrl = aResp->res.jsonValue["@odata.id"];
--    metricsUrl += "/ProcessorMetrics";
--    nlohmann::json::object_t metricsObj;
--    metricsObj["@odata.id"] = std::move(metricsUrl);
--    aResp->res.jsonValue["Metrics"] = std::move(metricsObj);
-+    if (expandLevel > 0)
-+    {
-+        nlohmann::json::json_pointer metricsPtr = jsonPtr / "Metrics";
-+        handleGetCpuCoreThreadMetrics(aResp, metricsPtr, processorId, coreId,
-+                                      threadId);
-+    }
-+    else
-+    {
-+        // Inserting the "Metrics" JSON stanza
-+        std::string metricsUrl = aResp->res.jsonValue[jsonPtr]["@odata.id"];
-+        metricsUrl += "/ProcessorMetrics";
-+        nlohmann::json::object_t metricsObj;
-+        metricsObj["@odata.id"] = std::move(metricsUrl);
-+        aResp->res.jsonValue[jsonPtr]["Metrics"] = std::move(metricsObj);
-+    }
- }
- 
- inline void getSubProcessorThreadData(
-@@ -2692,6 +2719,7 @@ inline nlohmann::json fakeProcessorMetrics(const std::string& cpu,
- 
- inline void
-     handleGetCpuMetrics(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                        const nlohmann::json::json_pointer& jsonPtr,
-                         const std::string& cpuArg)
- {
-     if (validateCpu(asyncResp, cpuArg) < 0)
-@@ -2708,11 +2736,13 @@ inline void
-     }
- 
-     std::string emptyString;
--    hook->handleGetEntry(asyncResp, cpuArg, emptyString, "ProcessorMetrics");
-+    hook->handleGetEntry(asyncResp, jsonPtr, cpuArg, emptyString,
-+                         "ProcessorMetrics");
-     if (asyncResp->res.result() == boost::beast::http::status::not_found)
-     {
-         // Fake up a synthetic response to replace only the 404 error
--        asyncResp->res.jsonValue = fakeProcessorMetrics(cpuArg, "", "");
-+        asyncResp->res.jsonValue[jsonPtr] =
-+            fakeProcessorMetrics(cpuArg, "", "");
-         asyncResp->res.result(boost::beast::http::status::ok);
-     }
-     if (asyncResp->res.result() != boost::beast::http::status::ok)
-@@ -2722,14 +2752,14 @@ inline void
-     }
- 
-     // Apply invariants, to override whatever might have been in the file
--    asyncResp->res.jsonValue["@odata.type"] =
-+    asyncResp->res.jsonValue[jsonPtr]["@odata.type"] =
-         "#ProcessorMetrics.v1_5_0.ProcessorMetrics";
- }
- 
- inline void handleGetCpuCoreThreadMetrics(
-     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
--    const std::string& cpuArg, const std::string& coreArg,
--    const std::string& threadArg)
-+    const nlohmann::json::json_pointer& jsonPtr, const std::string& cpuArg,
-+    const std::string& coreArg, const std::string& threadArg)
- {
-     if (validateCpuCoreThread(asyncResp, cpuArg, coreArg, threadArg) < 0)
-     {
-@@ -2745,11 +2775,12 @@ inline void handleGetCpuCoreThreadMetrics(
-     }
- 
-     std::string emptyString;
--    hook->handleGetEntry(asyncResp, threadArg, emptyString, "ProcessorMetrics");
-+    hook->handleGetEntry(asyncResp, jsonPtr, threadArg, emptyString,
-+                         "ProcessorMetrics");
-     if (asyncResp->res.result() == boost::beast::http::status::not_found)
-     {
-         // Fake up a synthetic response to replace only the 404 error
--        asyncResp->res.jsonValue =
-+        asyncResp->res.jsonValue[jsonPtr] =
-             fakeProcessorMetrics(cpuArg, coreArg, threadArg);
-         asyncResp->res.result(boost::beast::http::status::ok);
-     }
-@@ -2760,7 +2791,7 @@ inline void handleGetCpuCoreThreadMetrics(
-     }
- 
-     // Apply invariants, to override whatever might have been in the file
--    asyncResp->res.jsonValue["@odata.type"] =
-+    asyncResp->res.jsonValue[jsonPtr]["@odata.type"] =
-         "#ProcessorMetrics.v1_5_0.ProcessorMetrics";
- }
- 
-@@ -2793,7 +2824,7 @@ inline void requestRoutesProcessorMetrics(App& app)
-         {
-             return;
-         }
--        handleGetCpuMetrics(asyncResp, cpuArg);
-+        handleGetCpuMetrics(asyncResp, ""_json_pointer, cpuArg);
-         });
- 
-     BMCWEB_ROUTE(
-@@ -2809,7 +2840,8 @@ inline void requestRoutesProcessorMetrics(App& app)
-         {
-             return;
-         }
--        handleGetCpuCoreThreadMetrics(asyncResp, cpuArg, coreArg, threadArg);
-+        handleGetCpuCoreThreadMetrics(asyncResp, ""_json_pointer, cpuArg,
-+                                      coreArg, threadArg);
-         });
- 
-     // TODO(): Implement corresponding PATCH methods
-diff --git a/test/redfish-core/lib/external_storer_test.cpp b/test/redfish-core/lib/external_storer_test.cpp
-index 113c36e7..0b1e6e1a 100644
---- a/test/redfish-core/lib/external_storer_test.cpp
-+++ b/test/redfish-core/lib/external_storer_test.cpp
-@@ -126,7 +126,7 @@ TEST_F(ExternalStorerTest, CreateGetMiddle)
- 
-     // Entry must now exist
-     auto resp7 = std::make_shared<bmcweb::AsyncResp>();
--    hookLogs->handleGetEntry(resp7, "MyInstance", "Entries",
-+    hookLogs->handleGetEntry(resp7, ""_json_pointer, "MyInstance", "Entries",
-                              "EntryCreatedFromCreateMiddle");
-     EXPECT_EQ(resp7->res.result(), boost::beast::http::status::ok);
-     EXPECT_EQ(resp7->res.jsonValue["Hello"], "There");
-@@ -195,13 +195,14 @@ TEST_F(ExternalStorerTest, CreateGetEntry)
- 
-     // Both entries must now exist and be distinct from one another
-     auto resp8 = std::make_shared<bmcweb::AsyncResp>();
--    hookLogs->handleGetEntry(resp8, "MyInstance", "Entries",
-+    hookLogs->handleGetEntry(resp8, ""_json_pointer, "MyInstance", "Entries",
-                              "EntryCreatedFromCreateEntry");
-     EXPECT_EQ(resp8->res.result(), boost::beast::http::status::ok);
-     EXPECT_EQ(resp8->res.jsonValue["Hello"], "There");
- 
-     auto resp9 = std::make_shared<bmcweb::AsyncResp>();
--    hookLogs->handleGetEntry(resp9, "MyInstance", "Entries", "AnotherEntry");
-+    hookLogs->handleGetEntry(resp9, ""_json_pointer, "MyInstance", "Entries",
-+                             "AnotherEntry");
-     EXPECT_EQ(resp9->res.result(), boost::beast::http::status::ok);
-     EXPECT_EQ(resp9->res.jsonValue["Hello"], "Again");
- }
diff --git a/recipes-phosphor/interfaces/bmcweb/0007-bmcweb-Add-dump-type-in-dump-LogService-Name.patch b/recipes-phosphor/interfaces/bmcweb/0007-bmcweb-Add-dump-type-in-dump-LogService-Name.patch
deleted file mode 100644
index 56b4028..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0007-bmcweb-Add-dump-type-in-dump-LogService-Name.patch
+++ /dev/null
@@ -1,59 +0,0 @@
-From f54676f68beef7fdfc74c919d28fc8403656262b Mon Sep 17 00:00:00 2001
-From: Claire Weinan <cweinan@google.com>
-Date: Fri, 14 Oct 2022 17:15:04 -0700
-Subject: [PATCH] bmcweb: Add dump type in dump LogService Name
-
-This allows a client to differentiate the dump types of various
-dump LogServices based on the "Name" property.
-
-Example from /redfish/v1/Managers/bmc/LogServices/FaultLog:
-Before: "Name": "Dump LogService"
-After: "Name": "FaultLog Dump LogService"
-
-Example from /redfish/v1/Managers/bmc/LogServices/Dump:
-Before: "Name": "Dump LogService"
-After: "Name": "BMC Dump LogService"
-
-Example from /redfish/v1/Systems/system/LogServices/Dump:
-Before: "Name": "Dump LogService"
-After: "Name": "System Dump LogService"
-
-Tested:
-
-Redfish Service Validator passed on the following URIs:
-/redfish/v1/Managers/bmc/LogServices/FaultLog
-/redfish/v1/Managers/bmc/LogServices/Dump
-/redfish/v1/Systems/system/LogServices/Dump (Name check passed but
-validator failed for an unrelated issue)
-
-Unit tests passed.
-
-Patch Tracking Bug: b/253704450
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/bmcweb/+/57949
-Upstream-Status: Submitted
-Justification: Update fault log LogService name for Redfish client to
-identify fault log as needed by Fault Management/Agentless error
-logging infrastructure (Astoria MVP) while upstream code review is
-still in progress.
-
-Signed-off-by: Claire Weinan <cweinan@google.com>
----
- redfish-core/lib/log_services.hpp | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
-index ca3032ce..81d22c01 100644
---- a/redfish-core/lib/log_services.hpp
-+++ b/redfish-core/lib/log_services.hpp
-@@ -2758,7 +2758,7 @@ inline void
- 
-     asyncResp->res.jsonValue["@odata.id"] = dumpPath;
-     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
--    asyncResp->res.jsonValue["Name"] = "Dump LogService";
-+    asyncResp->res.jsonValue["Name"] = dumpType + " Dump LogService";
-     asyncResp->res.jsonValue["Description"] = dumpType + " Dump LogService";
-     asyncResp->res.jsonValue["Id"] = std::filesystem::path(dumpPath).filename();
-     asyncResp->res.jsonValue["OverWritePolicy"] = std::move(overWritePolicy);
--- 
-2.40.0.348.gf938b09366-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb/0022-Implement-Fan-schema.patch b/recipes-phosphor/interfaces/bmcweb/0022-Implement-Fan-schema.patch
deleted file mode 100644
index 1012fc8..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0022-Implement-Fan-schema.patch
+++ /dev/null
@@ -1,533 +0,0 @@
-From b7ae7f6140dbd99969f154cf3cd7640cb6887885 Mon Sep 17 00:00:00 2001
-From: Zhenwei Chen <zhenweichen0207@gmail.com>
-Date: Mon, 4 Jul 2022 12:53:49 +0000
-Subject: [PATCH] Implement Fan schema
-
-This commit implements Redfish Fan schema on bmcweb.
-The Fan schema is a fan for a Redfish implementation.
-It is used to get the fan information of the chassis,
-such as Location,HotPluggable,SpeedPercent and so on.
-
-The example of Fan config:
-{
-    Exposes: {
-        "HotPluggable": false,
-        "LocationType": "Connector",
-        "ServiceLabel": "FAN0",
-        "TachSensor": "fan0_tach",
-        "PWMSensor": "fan0_pwm",
-        "Name": "Fan0",
-        "Type": "Fan"
-    }
-    ...
-}
-
-ref: https://redfish.dmtf.org/schemas/v1/Fan.v1_2_0.json
-
-Tested: Validator passes
-1.Get Fans Collection
-wget -qO- http://localhost/redfish/v1/Chassis/tray/ThermalSubsystem/Fans
-{
-  "@odata.id": "/redfish/v1/Chassis/tray/ThermalSubsystem/Fans/",
-  "@odata.type": "#FanCollection.FanCollection",
-  "Description": "The collection of Fan resource instances tray",
-  "Members": [
-    {
-      "@odata.id": "/redfish/v1/Chassis/tray/ThermalSubsystem/Fans/Fan0/"
-    }
-  ],
-  "Members@odata.count": 1,
-  "Name": "Fan Collection"
-}
-
-2. Get Fan resource
-wget -qO- http://localhost/redfish/v1/Chassis/tray/ThermalSubsystem/Fans/Fan0
-{
-  "@odata.id": "/redfish/v1/Chassis/tray/ThermalSubsystem/Fans/Fan0/",
-  "@odata.type": "#Fan.v1_2_0.Fan",
-  "HotPluggable": false,
-  "Id": "Fan0",
-  "Location": {
-    "PartLocation": {
-      "LocationType": "Connector",
-      "ServiceLabel": "FAN0"
-    }
-  },
-  "Name": "Fan0",
-  "SpeedPercent": {
-    "DataSourceUri": "/redfish/v1/Chassis/tray/Sensors/fan0_pwm/",
-    "Reading": 100.0,
-    "SpeedRPM": 16948.0
-  }
-}
-
-Patch Tracking Bug: b/262583855
-Upstream info / review: https://gerrit.openbmc.org/c/openbmc/bmcweb/+/54725
-Upstream-Status: Submitted
-Justification: Upsream eview WIP
-
-Change-Id: I7f5cd3ad64fccb6bcda08ef6fecf21019b31112d
-Signed-off-by: Zhenwei Chen <zhenweichen0207@gmail.com>
-Signed-off-by: Tom Tung <shes050117@gmail.com>
-Signed-off-by: Vlad Sytchenko <vsytch@google.com>
----
- redfish-core/include/redfish.hpp       |   3 +
- redfish-core/lib/fan.hpp               | 409 +++++++++++++++++++++++++
- redfish-core/lib/thermal_subsystem.hpp |   4 +
- 3 files changed, 416 insertions(+)
- create mode 100644 redfish-core/lib/fan.hpp
-
-diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
-index f41d1e06..6db444b4 100644
---- a/redfish-core/include/redfish.hpp
-+++ b/redfish-core/include/redfish.hpp
-@@ -27,6 +27,7 @@
- #include "event_service.hpp"
- #include "external_storer.hpp"
- #include "fabric_adapters.hpp"
-+#include "fan.hpp"
- #include "hypervisor_system.hpp"
- #include "log_services.hpp"
- #include "manager_diagnostic_data.hpp"
-@@ -93,6 +94,8 @@ class RedfishService
-         requestRoutesEnvironmentMetrics(app);
-         requestRoutesPowerSubsystem(app);
-         requestRoutesThermalSubsystem(app);
-+        requestRoutesFan(app);
-+        requestRoutesFanCollection(app);
- #endif
-         requestRoutesManagerCollection(app);
-         requestRoutesManager(app);
-diff --git a/redfish-core/lib/fan.hpp b/redfish-core/lib/fan.hpp
-new file mode 100644
-index 00000000..75aecfce
---- /dev/null
-+++ b/redfish-core/lib/fan.hpp
-@@ -0,0 +1,409 @@
-+#pragma once
-+
-+#include <app.hpp>
-+#include <registries/privilege_registry.hpp>
-+#include <utils/chassis_utils.hpp>
-+#include <utils/json_utils.hpp>
-+
-+namespace redfish
-+{
-+
-+static constexpr const std::array<const char*, 1> fanInterfaces = {
-+    "xyz.openbmc_project.Configuration.Fan"};
-+static constexpr const std::array<const char*, 1> sensorInterfaces = {
-+    "xyz.openbmc_project.Sensor.Value"};
-+
-+inline void
-+    getFanSensorValue(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                      const std::string& chassisId,
-+                      const std::string& sensorName,
-+                      const std::string& sensorType)
-+{
-+    if (sensorType != "fan_tach" && sensorType != "fan_pwm")
-+    {
-+        BMCWEB_LOG_DEBUG << "Unsupported sensor type";
-+        messages::internalError(asyncResp->res);
-+        return;
-+    }
-+    auto getFanSensorsHandler =
-+        [asyncResp, chassisId, sensorName, sensorType](
-+            const boost::system::error_code ec,
-+            const std::vector<std::pair<
-+                std::string,
-+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
-+                sensorsubtree) {
-+        if (ec)
-+        {
-+            BMCWEB_LOG_DEBUG << "D-Bus response error on GetSubTree " << ec;
-+            messages::internalError(asyncResp->res);
-+            return;
-+        }
-+        for (const auto& [objectPath, serviceNames] : sensorsubtree)
-+        {
-+            if (objectPath.empty() || serviceNames.size() != 1)
-+            {
-+                BMCWEB_LOG_DEBUG << "Error getting Fan D-Bus object!";
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            sdbusplus::message::object_path path(objectPath);
-+            const std::string& leaf = path.filename();
-+            if (leaf.empty())
-+            {
-+                continue;
-+            }
-+            if (leaf != sensorName)
-+            {
-+                continue;
-+            }
-+
-+            const std::string& connectionName = serviceNames[0].first;
-+            const std::string& tempPath = objectPath;
-+            auto getAssociationHandler =
-+                [asyncResp, tempPath, connectionName, chassisId, sensorName,
-+                 sensorType](const boost::system::error_code ec2,
-+                             const std::vector<std::string>& resp) {
-+                BMCWEB_LOG_DEBUG << "Getting chassis association for sensor "
-+                                 << sensorName;
-+                if (ec2)
-+                {
-+                    BMCWEB_LOG_DEBUG
-+                        << "Error getting chassis association for the sensor!";
-+                    messages::internalError(asyncResp->res);
-+                    return;
-+                }
-+                if (resp.size() != 1)
-+                {
-+                    BMCWEB_LOG_DEBUG
-+                        << "The number of associated chassis should be 1";
-+                    messages::internalError(asyncResp->res);
-+                    return;
-+                }
-+                sdbusplus::message::object_path path2(resp[0]);
-+                std::string leaf2 = path2.filename();
-+                if (leaf2 != chassisId)
-+                {
-+                    BMCWEB_LOG_DEBUG
-+                        << "The sensor doesn't belong to the chassis "
-+                        << chassisId;
-+                    messages::internalError(asyncResp->res);
-+                    return;
-+                }
-+                auto getFanPropertyHandler =
-+                    [asyncResp, chassisId, sensorName,
-+                     sensorType](const boost::system::error_code ec3,
-+                                 const double& resp2) {
-+                    if (ec3)
-+                    {
-+                        BMCWEB_LOG_DEBUG
-+                            << "Error getting sensor value for sensor:"
-+                            << sensorName;
-+                        messages::internalError(asyncResp->res);
-+                        return;
-+                    }
-+                    if (sensorType == "fan_pwm")
-+                    {
-+                        asyncResp->res
-+                            .jsonValue["SpeedPercent"]["DataSourceUri"] =
-+                            std::string("/redfish/v1/Chassis/")
-+                                .append(chassisId)
-+                                .append("/Sensors/")
-+                                .append("fanpwm_" + sensorName)
-+                                .append("/");
-+                        asyncResp->res.jsonValue["SpeedPercent"]["Reading"] =
-+                            resp2;
-+                    }
-+                    else
-+                    {
-+                        asyncResp->res.jsonValue["SpeedPercent"]["SpeedRPM"] =
-+                            resp2;
-+                    }
-+                };
-+                sdbusplus::asio::getProperty<double>(
-+                    *crow::connections::systemBus, connectionName, tempPath,
-+                    "xyz.openbmc_project.Sensor.Value", "Value",
-+                    getFanPropertyHandler);
-+            };
-+            sdbusplus::asio::getProperty<std::vector<std::string>>(
-+                *crow::connections::systemBus,
-+                "xyz.openbmc_project.ObjectMapper", tempPath + "/chassis",
-+                "xyz.openbmc_project.Association", "endpoints",
-+                getAssociationHandler);
-+        }
-+    };
-+    crow::connections::systemBus->async_method_call(
-+        getFanSensorsHandler, "xyz.openbmc_project.ObjectMapper",
-+        "/xyz/openbmc_project/object_mapper",
-+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
-+        "/xyz/openbmc_project/sensors", 0, sensorInterfaces);
-+}
-+
-+inline void
-+    fillFanProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                      const boost::system::error_code ec,
-+                      const dbus::utility::DBusPropertiesMap& properties,
-+                      const std::string& chassisId)
-+{
-+    if (ec)
-+    {
-+        BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
-+        messages::internalError(asyncResp->res);
-+        return;
-+    }
-+    for (const auto& [propKey, propVariant] : properties)
-+    {
-+        if (propKey == "HotPluggable")
-+        {
-+            const bool* hotPluggable = std::get_if<bool>(&propVariant);
-+            if (hotPluggable == nullptr)
-+            {
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            asyncResp->res.jsonValue["HotPluggable"] = *hotPluggable;
-+        }
-+        else if (propKey == "LocationType")
-+        {
-+            const std::string* locationType =
-+                std::get_if<std::string>(&propVariant);
-+            if (locationType == nullptr)
-+            {
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            asyncResp->res
-+                .jsonValue["Location"]["PartLocation"]["LocationType"] =
-+                *locationType;
-+        }
-+        else if (propKey == "ServiceLabel")
-+        {
-+            const std::string* serviceLabel =
-+                std::get_if<std::string>(&propVariant);
-+            if (serviceLabel == nullptr)
-+            {
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            asyncResp->res
-+                .jsonValue["Location"]["PartLocation"]["ServiceLabel"] =
-+                *serviceLabel;
-+        }
-+        else if (propKey == "TachSensor")
-+        {
-+            const std::string* tachSensor =
-+                std::get_if<std::string>(&propVariant);
-+            if (tachSensor == nullptr)
-+            {
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            getFanSensorValue(asyncResp, chassisId, *tachSensor, "fan_tach");
-+        }
-+        else if (propKey == "PWMSensor")
-+        {
-+            const std::string* pwmSensor =
-+                std::get_if<std::string>(&propVariant);
-+            if (pwmSensor == nullptr)
-+            {
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            getFanSensorValue(asyncResp, chassisId, *pwmSensor, "fan_pwm");
-+        }
-+    }
-+}
-+
-+inline void
-+    getFanProperties(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                     const std::string& fanObjectPath,
-+                     const dbus::utility::MapperServiceMap& serviceMap,
-+                     const std::string& chassisId)
-+{
-+    BMCWEB_LOG_DEBUG << "Get Properties for Fan " << fanObjectPath;
-+    for (const auto& [service, interfaces] : serviceMap)
-+    {
-+        for (const auto& interface : interfaces)
-+        {
-+            if (interface != fanInterfaces[0])
-+            {
-+                continue;
-+            }
-+
-+            crow::connections::systemBus->async_method_call(
-+                [asyncResp, chassisId](
-+                    const boost::system::error_code ec,
-+                    const dbus::utility::DBusPropertiesMap& properties) {
-+                fillFanProperties(asyncResp, ec, properties, chassisId);
-+                },
-+                service, fanObjectPath, "org.freedesktop.DBus.Properties",
-+                "GetAll", interface);
-+        }
-+    }
-+}
-+
-+inline void getValidFan(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                        const std::string& chassisId)
-+{
-+    BMCWEB_LOG_DEBUG << "Get fan list associated to chassis = " << chassisId;
-+    asyncResp->res.jsonValue["@odata.type"] = "#FanCollection.FanCollection";
-+    asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
-+        "redfish", "v1", "Chassis", chassisId, "ThermalSubsystem", "Fans");
-+    asyncResp->res.jsonValue["Name"] = "Fan Collection";
-+    asyncResp->res.jsonValue["Description"] =
-+        "The collection of Fan resource instances " + chassisId;
-+    crow::connections::systemBus->async_method_call(
-+        [asyncResp, chassisId](
-+            const boost::system::error_code ec,
-+            const std::vector<std::pair<
-+                std::string,
-+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
-+                fansubtree) {
-+        if (ec)
-+        {
-+            BMCWEB_LOG_DEBUG << "DBUS response error";
-+            messages::internalError(asyncResp->res);
-+            return;
-+        }
-+
-+        nlohmann::json& fanList = asyncResp->res.jsonValue["Members"];
-+        fanList = nlohmann::json::array();
-+
-+        std::string newPath =
-+            "/redfish/v1/Chassis/" + chassisId + "/ThermalSubsystem/Fans/";
-+
-+        for (const auto& [objectPath, serviceName] : fansubtree)
-+        {
-+            if (objectPath.empty() || serviceName.size() != 1)
-+            {
-+                BMCWEB_LOG_DEBUG << "Error getting D-Bus object!";
-+                messages::internalError(asyncResp->res);
-+                return;
-+            }
-+            const std::string& validPath = objectPath;
-+            // const std::string& connectionName = serviceName[0].first;
-+            std::string chassisName;
-+            std::string fanName;
-+
-+            // 5 and 6 below comes from
-+            // /xyz/openbmc_project/inventory/system/chassis/chassisName/fanName
-+            //   0      1             2         3         4         5      6
-+            if (!dbus::utility::getNthStringFromPath(validPath, 5,
-+                                                     chassisName) ||
-+                !dbus::utility::getNthStringFromPath(validPath, 6, fanName))
-+            {
-+                BMCWEB_LOG_ERROR << "Got invalid path " << validPath;
-+                messages::invalidObject(asyncResp->res,
-+                                        crow::utility::urlFromPieces(
-+                                            "xyz", "openbmc_project",
-+                                            "inventory", "system", validPath));
-+                continue;
-+            }
-+            if (chassisName != chassisId)
-+            {
-+                BMCWEB_LOG_ERROR << "The fan obtained at this time "
-+                                    "does not belong to this chassis ";
-+                continue;
-+            }
-+            fanList.push_back({{"@odata.id", newPath + fanName + "/"}});
-+        }
-+
-+        asyncResp->res.jsonValue["Members@odata.count"] = fanList.size();
-+        },
-+        "xyz.openbmc_project.ObjectMapper",
-+        "/xyz/openbmc_project/object_mapper",
-+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
-+        "/xyz/openbmc_project/inventory", 0, fanInterfaces);
-+}
-+
-+inline void requestRoutesFanCollection(App& app)
-+{
-+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/")
-+        .privileges(redfish::privileges::getFanCollection)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app](const crow::Request& req,
-+                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                   const std::string& chassisId) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        auto getChassisId = [asyncResp, chassisId](
-+                                const std::optional<std::string>& chassisPath) {
-+            if (!chassisPath)
-+            {
-+                BMCWEB_LOG_ERROR << "Not a valid chassis ID" << chassisId;
-+                messages::resourceNotFound(asyncResp->res, "Chassis",
-+                                           chassisId);
-+                return;
-+            }
-+            getValidFan(asyncResp, chassisId);
-+        };
-+        redfish::chassis_utils::getValidChassisPath(asyncResp, chassisId,
-+                                                    std::move(getChassisId));
-+        });
-+}
-+
-+inline void requestRoutesFan(App& app)
-+{
-+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/Fans/<str>/")
-+        .privileges(redfish::privileges::getFan)
-+        .methods(boost::beast::http::verb::get)(
-+            [&app](const crow::Request& req,
-+                   const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-+                   const std::string& chassisId, const std::string& fanId) {
-+        if (!redfish::setUpRedfishRoute(app, req, asyncResp))
-+        {
-+            return;
-+        }
-+        auto getChassisId = [asyncResp, chassisId, fanId](
-+                                const std::optional<std::string>& chassisPath) {
-+            if (!chassisPath)
-+            {
-+                BMCWEB_LOG_ERROR << "Not a valid chassis ID" << chassisId;
-+                messages::resourceNotFound(asyncResp->res, "Chassis",
-+                                           chassisId);
-+                return;
-+            }
-+            auto respHandler =
-+                [asyncResp, chassisId, fanId](
-+                    const boost::system::error_code ec,
-+                    const dbus::utility::MapperGetSubTreeResponse& subtree) {
-+                if (ec)
-+                {
-+                    BMCWEB_LOG_DEBUG << "DBUS response error";
-+                    messages::internalError(asyncResp->res);
-+                    return;
-+                }
-+                for (const auto& [objectPath, serviceMap] : subtree)
-+                {
-+                    sdbusplus::message::object_path path(objectPath);
-+                    if (path.filename() != fanId)
-+                    {
-+                        continue;
-+                    }
-+                    std::string newPath = "/redfish/v1/Chassis/" + chassisId +
-+                                          "/ThermalSubsystem/Fans/";
-+                    asyncResp->res.jsonValue["@odata.type"] = "#Fan.v1_2_0.Fan";
-+                    asyncResp->res.jsonValue["Name"] = fanId;
-+                    asyncResp->res.jsonValue["Id"] = fanId;
-+                    asyncResp->res.jsonValue["@odata.id"] =
-+                        crow::utility::urlFromPieces(
-+                            "redfish", "v1", "Chassis", chassisId,
-+                            "ThermalSubsystem", "Fans", fanId);
-+                    getFanProperties(asyncResp, objectPath, serviceMap,
-+                                     chassisId);
-+                    return;
-+                }
-+            };
-+            crow::connections::systemBus->async_method_call(
-+                respHandler, "xyz.openbmc_project.ObjectMapper",
-+                "/xyz/openbmc_project/object_mapper",
-+                "xyz.openbmc_project.ObjectMapper", "GetSubTree",
-+                "/xyz/openbmc_project/inventory", 0, fanInterfaces);
-+        };
-+        redfish::chassis_utils::getValidChassisPath(asyncResp, chassisId,
-+                                                    std::move(getChassisId));
-+        });
-+}
-+
-+} // namespace redfish
-diff --git a/redfish-core/lib/thermal_subsystem.hpp b/redfish-core/lib/thermal_subsystem.hpp
-index 271951ec..8086e729 100644
---- a/redfish-core/lib/thermal_subsystem.hpp
-+++ b/redfish-core/lib/thermal_subsystem.hpp
-@@ -38,6 +38,10 @@ inline void doThermalSubsystemCollection(
- 
-     asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
-     asyncResp->res.jsonValue["Status"]["Health"] = "OK";
-+    asyncResp->res.jsonValue["Fans"]["@odata.id"] =
-+        crow::utility::urlFromPieces("redfish", "v1", "Chassis", chassisId,
-+                                     "ThermalSubsystem", "Fans")
-+            .buffer();
- }
- 
- inline void handleThermalSubsystemCollectionHead(
--- 
-2.42.0.283.g2d96d420d3-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb/0043-bmcweb-patch-0001-Making-readJsonFile-faster.patch b/recipes-phosphor/interfaces/bmcweb/0043-bmcweb-patch-0001-Making-readJsonFile-faster.patch
deleted file mode 100644
index 6678f3e..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0043-bmcweb-patch-0001-Making-readJsonFile-faster.patch
+++ /dev/null
@@ -1,189 +0,0 @@
-From 0597573d0a99c1f7687c1238743411ac1b053a50 Mon Sep 17 00:00:00 2001
-From: Haim Grosman <haimg@google.com>
-Date: Wed, 10 May 2023 18:38:18 +0000
-Subject: [PATCH] bmcweb: patch: 0001-Making-readJsonFile-faster-by.patch
-
-Making readJsonFile faster by:
-
-1. Using the lower level C APIs directly instead of a C++ stream
-2. pre-allocating and using a local buffer to read the content of the
-   file into.
-
-   Future Work To Consider: We can use a pool of these pre-allocated
-   buffers to use here (instead of allocating a buffer per call). This
-   will take the improve the perf for this function a bit more as well.
-
-   As of now, from my measurements - the code is 20-30 faster,
-   especially for larger JSON files.
-
-   It takes about 1ms to read a JSON object with 4-5 fields in it when
-   executing the
-   `/redfish/v1/Systems/system/Processors?$expand=.($levels=6)` and
-   reading 200 files of JSON for the ProcessorMetrics part of the query.
-
-Google-Bug-Id: 282828172
-Change-Id: Ic1649599e7808f92094fcbf7d9c4cfe6baedd365
-Signed-off-by: Haim Grosman <haimg@google.com>
-
----
- redfish-core/lib/external_storer.hpp | 116 ++++++++++++++++++++-------
- 1 file changed, 89 insertions(+), 27 deletions(-)
-
-diff --git a/redfish-core/lib/external_storer.hpp b/redfish-core/lib/external_storer.hpp
-index cb60f567..66fa3709 100644
---- a/redfish-core/lib/external_storer.hpp
-+++ b/redfish-core/lib/external_storer.hpp
-@@ -74,6 +74,8 @@
- #include <boost/uuid/uuid_generators.hpp>
- #include <boost/uuid/uuid_io.hpp>
-
-+#include <cmath>
-+#include <cstddef>
- #include <fstream>
-
- namespace external_storer
-@@ -210,49 +212,108 @@ std::filesystem::path
- std::optional<nlohmann::json>
-     readJsonFile(const std::filesystem::path& filename)
- {
--    nlohmann::json content;
--    std::ifstream input;
--
--    input.open(filename);
-+    bool isOK = true;
-
--    if (!input)
-+    FILE* f = fopen(filename.c_str(), "r");
-+    if (f == nullptr)
-     {
--        int err = errno;
--        BMCWEB_LOG_ERROR << "Error opening " << filename
--                         << " input: " << strerror(err);
-+        const int err = errno;
-+        BMCWEB_LOG_ERROR << "Input " << filename
-+                         << " failed to open err: " << strerror(err);
-         return std::nullopt;
-     }
-
--    // Must supply 3rd argument to avoid throwing exceptions
--    content = nlohmann::json::parse(input, nullptr, false);
-+    if (isOK)
-+    {
-+        const auto ret = fseek(f, 0L, SEEK_END);
-+        isOK = (ret == 0);
-+    }
-+
-+    size_t fileSizeBytes = 0;
-+    if (isOK)
-+    {
-+        const auto ret = ftell(f);
-+        if (ret >= 0)
-+        {
-+            fileSizeBytes = static_cast<size_t>(ret);
-+            isOK = true;
-+        }
-+        else
-+        {
-+            isOK = false;
-+        }
-+    }
-+
-+    // limit the size of the buffer:
-+    const auto kBytesMax = 1024 * 1024;
-+    if (fileSizeBytes > kBytesMax)
-+    {
-+        isOK = false;
-+        BMCWEB_LOG_ERROR << "Input " << filename
-+                         << " is too large: " << fileSizeBytes << "/"
-+                         << kBytesMax;
-+    }
-
--    input.close();
-+    // check file size:
-+    if (isOK)
-+    {
-+        const auto ret = fseek(f, 0L, SEEK_SET);
-+        isOK = (ret == 0);
-+    }
-
--    // Must be good, or if not, must be at EOF, to deem file I/O successful
--    if (!(input.good()))
-+    // preallocate the buffer to fit the file size:
-+    std::string buffer;
-+    if (isOK)
-     {
--        if (!(input.eof()))
-+        buffer.resize(fileSizeBytes);
-+    }
-+
-+    size_t nBytes = 0;
-+    // read the whole file in one go:
-+    if (isOK)
-+    {
-+        const auto ret = fread(buffer.data(), 1, buffer.size(), f);
-+        // if not negative, use it as nBytes
-+        if (ret > 0)
-         {
--            int err = errno;
--            BMCWEB_LOG_ERROR << "Error closing " << filename
--                             << " input: " << strerror(err);
--            return std::nullopt;
-+            nBytes = static_cast<size_t>(ret);
-+            buffer.resize(nBytes);
-         }
-     }
-
--    // Even if file I/O successful, content must be a valid JSON dictionary
--    if (content.is_discarded())
-+    // in any case, always close the fd:
-+    if (f != nullptr)
-     {
--        BMCWEB_LOG_ERROR << "Input " << filename << " not valid JSON";
--        return std::nullopt;
-+        const int fcloseRet = fclose(f);
-+        f = nullptr;
-+
-+        // do not hide the error:
-+        if (isOK && fcloseRet != 0)
-+        {
-+            isOK = false;
-+        }
-     }
--    if (!(content.is_object()))
-+
-+    if (isOK)
-     {
--        BMCWEB_LOG_ERROR << "Input " << filename << " not JSON dictionary";
--        return std::nullopt;
-+        nlohmann::json content = nlohmann::json::parse(buffer, nullptr, false);
-+
-+        if (content.is_discarded())
-+        {
-+            BMCWEB_LOG_ERROR << "Input " << filename << " not valid JSON";
-+            return std::nullopt;
-+        }
-+
-+        if (!(content.is_object()))
-+        {
-+            BMCWEB_LOG_ERROR << "Input " << filename << " not JSON dictionary";
-+            return std::nullopt;
-+        }
-+
-+        return {content};
-     }
-
--    return {content};
-+    return std::nullopt;
- }
-
- std::optional<std::streampos>
-@@ -981,7 +1042,8 @@ void Hook::handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-
-     std::error_code ec;
-
--    if (!(std::filesystem::exists(entryFilename, ec)))
-+    const auto doesExist = (std::filesystem::exists(entryFilename, ec));
-+    if (!doesExist)
-     {
-         BMCWEB_LOG_ERROR << "Entry not found with ID " << idEntry;
-         asyncResp->res.result(boost::beast::http::status::not_found);
diff --git a/recipes-phosphor/interfaces/bmcweb/0044-bmcweb-make-all-std-regex-static.patch b/recipes-phosphor/interfaces/bmcweb/0044-bmcweb-make-all-std-regex-static.patch
deleted file mode 100644
index e1f17ad..0000000
--- a/recipes-phosphor/interfaces/bmcweb/0044-bmcweb-make-all-std-regex-static.patch
+++ /dev/null
@@ -1,108 +0,0 @@
-From c082d761547ce5eb8caaec6d1c5ba30f15b1fa5b Mon Sep 17 00:00:00 2001
-From: Haim Grosman <haimg@google.com>
-Date: Thu, 18 May 2023 15:27:47 +0000
-Subject: [PATCH] bmcweb: make all std::regex static
-
-the regex compile time adds an extra second to level6 expand commands
-since the code is creating and compiling the same regex's 200 times
-to validate some JSON file names before reading them
-
-prior work:
-https://gbmc-private-review.git.corp.google.com/c/gbmcweb/+/2840
-Bug: https://b.corp.google.com/issues/283248170
-
-Upstream info / review: Not created yet
-Upstream-Status: Pending
-Justification: Important for Redfish performance optimization on Izumi (b/283248170)
-Upstream Patch Bug: b/283282367
-Google-Bug-Id: 283248170
-Signed-off-by: Haim Grosman <haimg@google.com>
----
- include/dbus_monitor.hpp      | 4 ++--
- include/dbus_utility.hpp      | 2 +-
- include/openbmc_dbus_rest.hpp | 7 +++----
- redfish-core/lib/ethernet.hpp | 4 ++--
- 4 files changed, 8 insertions(+), 9 deletions(-)
-
-diff --git a/include/dbus_monitor.hpp b/include/dbus_monitor.hpp
-index 094f183e..ef6b21f0 100644
---- a/include/dbus_monitor.hpp
-+++ b/include/dbus_monitor.hpp
-@@ -173,8 +173,8 @@ inline void requestRoutes(App& app)
-
-         // These regexes derived on the rules here:
-         // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
--        std::regex validPath("^/([A-Za-z0-9_]+/?)*$");
--        std::regex validInterface(
-+        const static std::regex validPath("^/([A-Za-z0-9_]+/?)*$");
-+        const static std::regex validInterface(
-             "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)+$");
-
-         for (const auto& thisPath : *paths)
-diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp
-index a89405b3..52584dd6 100644
---- a/include/dbus_utility.hpp
-+++ b/include/dbus_utility.hpp
-@@ -101,7 +101,7 @@ using MapperEndPoints = std::vector<std::string>;
-
- inline void escapePathForDbus(std::string& path)
- {
--    const std::regex reg("[^A-Za-z0-9_/]");
-+    const static std::regex reg("[^A-Za-z0-9_/]");
-     std::regex_replace(path.begin(), path.begin(), path.end(), reg, "_");
- }
-
-diff --git a/include/openbmc_dbus_rest.hpp b/include/openbmc_dbus_rest.hpp
-index b42422d8..66ea9c58 100644
---- a/include/openbmc_dbus_rest.hpp
-+++ b/include/openbmc_dbus_rest.hpp
-@@ -101,8 +101,7 @@ const constexpr char* forbiddenResDesc =
-
- inline bool validateFilename(const std::string& filename)
- {
--    std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)");
--
-+    const static std::regex validFilename(R"(^[\w\- ]+(\.?[\w\- ]*)$)");
-     return std::regex_match(filename, validFilename);
- }
-
-@@ -1698,7 +1697,7 @@ inline void handleEnumerate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-         // Add the data for the path passed in to the results
-         // as if GetSubTree returned it, and continue on enumerating
-         getObjectAndEnumerate(transaction);
--    });
-+        });
- }
-
- inline void handleGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-@@ -2565,7 +2564,7 @@ inline void requestRoutes(App& app)
-             // Filename should be in alphanumeric, dot and underscore
-             // Its based on phosphor-debug-collector application
-             // dumpfile format
--            std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
-+            const static std::regex dumpFileRegex("[a-zA-Z0-9\\._]+");
-             if (!std::regex_match(dumpFileName, dumpFileRegex))
-             {
-                 BMCWEB_LOG_ERROR << "Invalid dump filename " << dumpFileName;
-diff --git a/redfish-core/lib/ethernet.hpp b/redfish-core/lib/ethernet.hpp
-index 95632901..2921d701 100644
---- a/redfish-core/lib/ethernet.hpp
-+++ b/redfish-core/lib/ethernet.hpp
-@@ -963,7 +963,7 @@ inline bool isHostnameValid(const std::string& hostname)
-     // MUST handle host names of up to 63 characters (RFC 1123)
-     // labels cannot start or end with hyphens (RFC 952)
-     // labels can start with numbers (RFC 1123)
--    const std::regex pattern(
-+    const static std::regex pattern(
-         "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
-
-     return std::regex_match(hostname, pattern);
-@@ -973,7 +973,7 @@ inline bool isDomainnameValid(const std::string& domainname)
- {
-     // Can have multiple subdomains
-     // Top Level Domain's min length is 2 character
--    const std::regex pattern(
-+    const static std::regex pattern(
-         "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$");
-
-     return std::regex_match(domainname, pattern);
diff --git a/recipes-phosphor/interfaces/bmcweb/DOWNSTREAM_0045-ExternalStorer-Followup-cleanup-to-readJsonFile.patch b/recipes-phosphor/interfaces/bmcweb/DOWNSTREAM_0045-ExternalStorer-Followup-cleanup-to-readJsonFile.patch
deleted file mode 100644
index 7b6cc9e..0000000
--- a/recipes-phosphor/interfaces/bmcweb/DOWNSTREAM_0045-ExternalStorer-Followup-cleanup-to-readJsonFile.patch
+++ /dev/null
@@ -1,331 +0,0 @@
-From 8cb8c294b3de7701985688e4ab85b0bc35e40d4a Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Fri, 23 Jun 2023 07:09:52 -0700
-Subject: [PATCH] ExternalStorer: Followup cleanup to readJsonFile
-
-As promised in this review, here is a follow-up cleanup:
-https://gbmc-review.git.corp.google.com/c/meta-gbmc-staging/+/6500
-
-Now using helper functions while the file is open, instead of having it
-all in one big function. This allows any error to simply return, while
-still guaranteeing the file will be closed and not leak. This avoids
-complicated logic during error handling.
-
-Also fixed type and signedness issues. Also ran clang-format to clean
-up some formatting inconsistencies that had crept in over time.
-
-Tested: Did 10 queries of Processors with Efficient Expand, time is
-still reasonably fast, between 0.9 and 1.5 seconds.
-
-time curl -s
-localhost:28080/redfish/v1/Systems/system/Processors?\$expand=\*\(\$levels=6\)
-
-Patch Tracking Bug: b/289117596
-Upstream info / review: n/a
-Upstream-Status: Inappropriate [b/230806664]
-Justification: Overall the ExternalStorer feature already upstream rejected
-
-Google-Bug-Id: 282828172
-Signed-off-by: Josh Lehan <krellan@google.com>
----
- redfish-core/lib/external_storer.hpp | 200 ++++++++++++++++-----------
- 1 file changed, 122 insertions(+), 78 deletions(-)
-
-diff --git a/redfish-core/lib/external_storer.hpp b/redfish-core/lib/external_storer.hpp
-index 66fa3709..5ab36752 100644
---- a/redfish-core/lib/external_storer.hpp
-+++ b/redfish-core/lib/external_storer.hpp
-@@ -86,6 +86,9 @@ inline const std::filesystem::path defPathPrefix{"/run/bmcweb"};
- inline const std::filesystem::path redfishPrefix{"/redfish/v1"};
- inline const std::filesystem::path jsonFilename{"index.json"};
- 
-+// Maximum allowed file size, helps to avoid DoS attacks
-+inline const size_t maxFileSize{1024 * 1024};
-+
- // This class only holds configuration and accounting data for the hook.
- // As for user-provided data, it is intentionally not here, as it is always
- // fetched from the filesystem backing store when needed.
-@@ -209,113 +212,154 @@ std::filesystem::path
-     return locToFileDir(loc) / jsonFilename;
- }
- 
--std::optional<nlohmann::json>
--    readJsonFile(const std::filesystem::path& filename)
-+// Helper function to get size of an open file, also rewinds file
-+// Uses the C library (not C++) to be called from fileToString
-+std::optional<size_t> fileToSize(FILE* f)
- {
--    bool isOK = true;
--
--    FILE* f = fopen(filename.c_str(), "r");
--    if (f == nullptr)
-+    int ret = std::fseek(f, 0L, SEEK_END);
-+    if (ret != 0)
-     {
--        const int err = errno;
--        BMCWEB_LOG_ERROR << "Input " << filename
--                         << " failed to open err: " << strerror(err);
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "File failed to seek: " << std::strerror(err);
-         return std::nullopt;
-     }
- 
--    if (isOK)
-+    // Assumes file has been opened in binary mode
-+    long rawSize = ftell(f);
-+    if (rawSize < 0)
-     {
--        const auto ret = fseek(f, 0L, SEEK_END);
--        isOK = (ret == 0);
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "File failed to tell: " << std::strerror(err);
-+        return std::nullopt;
-     }
- 
--    size_t fileSizeBytes = 0;
--    if (isOK)
-+    ret = std::fseek(f, 0L, SEEK_SET);
-+    if (ret != 0)
-     {
--        const auto ret = ftell(f);
--        if (ret >= 0)
--        {
--            fileSizeBytes = static_cast<size_t>(ret);
--            isOK = true;
--        }
--        else
--        {
--            isOK = false;
--        }
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "File failed to seek: " << std::strerror(err);
-+        return std::nullopt;
-     }
- 
--    // limit the size of the buffer:
--    const auto kBytesMax = 1024 * 1024;
--    if (fileSizeBytes > kBytesMax)
-+    // Historical C library API legacy: long versus size_t
-+    auto size = static_cast<size_t>(rawSize);
-+
-+    BMCWEB_LOG_DEBUG << "File open success: " << size << " bytes";
-+    return {size};
-+}
-+
-+// Use the C library (not C++) to read entire file into a C++ string first
-+// Tested earlier to be faster than nlohmann::json::parse() of std::ifstream
-+std::optional<std::string> fileToString(FILE* f)
-+{
-+    auto sizeOpt = fileToSize(f);
-+    if (!sizeOpt.has_value())
-     {
--        isOK = false;
--        BMCWEB_LOG_ERROR << "Input " << filename
--                         << " is too large: " << fileSizeBytes << "/"
--                         << kBytesMax;
-+        // Helper function has already printed an error message
-+        return std::nullopt;
-     }
- 
--    // check file size:
--    if (isOK)
-+    size_t fileSize = *sizeOpt;
-+
-+    if (fileSize == 0)
-     {
--        const auto ret = fseek(f, 0L, SEEK_SET);
--        isOK = (ret == 0);
-+        BMCWEB_LOG_ERROR << "File is empty";
-+        return std::nullopt;
-+    }
-+    if (fileSize > maxFileSize)
-+    {
-+        BMCWEB_LOG_ERROR << "File is too large: " << fileSize << "/"
-+                         << maxFileSize;
-+        return std::nullopt;
-     }
- 
--    // preallocate the buffer to fit the file size:
--    std::string buffer;
--    if (isOK)
-+    std::string empty;
-+    std::optional<std::string> contentOpt{empty};
-+
-+    // Avoid making expensive copies of string after it gets larger
-+    std::string& content = *contentOpt;
-+
-+    // Preallocate the buffer to fit the file size
-+    content.resize(fileSize);
-+
-+    // Read the whole file in one go
-+    // C++11 no longer disallows data() from being written to
-+    size_t readSize = fread(content.data(), 1, fileSize, f);
-+
-+    // Read of 0 must be error, because empty file already excluded earlier
-+    if (readSize == 0)
-     {
--        buffer.resize(fileSizeBytes);
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "File read error: " << std::strerror(err);
-+        return std::nullopt;
-     }
- 
--    size_t nBytes = 0;
--    // read the whole file in one go:
--    if (isOK)
-+    // Consider a short read to also be an error
-+    if (readSize != fileSize)
-     {
--        const auto ret = fread(buffer.data(), 1, buffer.size(), f);
--        // if not negative, use it as nBytes
--        if (ret > 0)
--        {
--            nBytes = static_cast<size_t>(ret);
--            buffer.resize(nBytes);
--        }
-+        BMCWEB_LOG_ERROR << "File read size mismatch: " << readSize << "/"
-+                         << fileSize;
-+        return std::nullopt;
-     }
- 
--    // in any case, always close the fd:
--    if (f != nullptr)
-+    BMCWEB_LOG_DEBUG << "File read success: " << readSize << " bytes";
-+    return contentOpt;
-+}
-+
-+std::optional<nlohmann::json>
-+    readJsonFile(const std::filesystem::path& filename)
-+{
-+    // Open in binary mode to ensure accurate size measurement
-+    FILE* f = std::fopen(filename.c_str(), "rb");
-+    if (!f)
-     {
--        const int fcloseRet = fclose(f);
--        f = nullptr;
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "File " << filename
-+                         << " failed to open: " << std::strerror(err);
-+        return std::nullopt;
-+    }
- 
--        // do not hide the error:
--        if (isOK && fcloseRet != 0)
--        {
--            isOK = false;
--        }
-+    // While we have the file open, use helper function to read it
-+    auto bufferOpt = fileToString(f);
-+
-+    int ret = std::fclose(f);
-+    if (ret != 0)
-+    {
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "File " << filename
-+                         << " failed to close: " << std::strerror(err);
-+        return std::nullopt;
-     }
- 
--    if (isOK)
-+    // All further error checking takes place after the close
-+    if (!bufferOpt.has_value())
-     {
--        nlohmann::json content = nlohmann::json::parse(buffer, nullptr, false);
-+        // Helper function has already printed an error message
-+        return std::nullopt;
-+    }
- 
--        if (content.is_discarded())
--        {
--            BMCWEB_LOG_ERROR << "Input " << filename << " not valid JSON";
--            return std::nullopt;
--        }
-+    const std::string& buffer = *bufferOpt;
- 
--        if (!(content.is_object()))
--        {
--            BMCWEB_LOG_ERROR << "Input " << filename << " not JSON dictionary";
--            return std::nullopt;
--        }
-+    // Must supply 3rd argument to avoid throwing exceptions
-+    nlohmann::json content = nlohmann::json::parse(buffer, nullptr, false);
- 
--        return {content};
-+    if (content.is_discarded())
-+    {
-+        BMCWEB_LOG_ERROR << "File " << filename << " not valid JSON";
-+        return std::nullopt;
-+    }
-+
-+    if (!(content.is_object()))
-+    {
-+        BMCWEB_LOG_ERROR << "File " << filename << " not JSON dictionary";
-+        return std::nullopt;
-     }
- 
--    return std::nullopt;
-+    BMCWEB_LOG_DEBUG << "File JSON success: " << filename;
-+    return {content};
- }
- 
-+// FUTURE: Should preflight first, to also enforce maxFileSize limit
- std::optional<std::streampos>
-     writeJsonFile(const std::filesystem::path& filename,
-                   const nlohmann::json& content)
-@@ -1168,7 +1212,7 @@ inline void requestRoutesExternalStorerLogServices(
-         }
- 
-         hook->handleCreateInstance(req, asyncResp);
--        });
-+    });
- 
-     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/")
-         .privileges(redfish::privileges::postLogService)
-@@ -1181,7 +1225,7 @@ inline void requestRoutesExternalStorerLogServices(
-             return;
-         }
-         hook->handleCreateMiddle(req, asyncResp, instance);
--        });
-+    });
- 
-     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/<str>/")
-         .privileges(redfish::privileges::postLogService)
-@@ -1195,7 +1239,7 @@ inline void requestRoutesExternalStorerLogServices(
-             return;
-         }
-         hook->handleCreateEntry(req, asyncResp, instance, middle);
--        });
-+    });
- 
-     // Only 1-argument, 2-argument, and 3-argument GET routes are here
-     // The 0-argument GET route is already handled by the integration point
-@@ -1211,7 +1255,7 @@ inline void requestRoutesExternalStorerLogServices(
-             return;
-         }
-         hook->handleGetInstance(asyncResp, instance);
--        });
-+    });
- 
-     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/LogServices/<str>/<str>/")
-         .privileges(redfish::privileges::getLogService)
-@@ -1225,7 +1269,7 @@ inline void requestRoutesExternalStorerLogServices(
-             return;
-         }
-         hook->handleGetMiddle(asyncResp, instance, middle);
--        });
-+    });
- 
-     BMCWEB_ROUTE(app,
-                  "/redfish/v1/Systems/system/LogServices/<str>/<str>/<str>/")
-@@ -1241,7 +1285,7 @@ inline void requestRoutesExternalStorerLogServices(
-         }
-         hook->handleGetEntry(asyncResp, ""_json_pointer, instance, middle,
-                              entry);
--        });
-+    });
- 
-     // The integration point also needs to access the correct hook
-     external_storer::rememberLogServices(hook);
--- 
-2.41.0.162.gfafddb0af9-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb/DOWNSTREAM_0046-ExternalStorer-Cleanups-and-fix-existence-check.patch b/recipes-phosphor/interfaces/bmcweb/DOWNSTREAM_0046-ExternalStorer-Cleanups-and-fix-existence-check.patch
deleted file mode 100644
index 528f18f..0000000
--- a/recipes-phosphor/interfaces/bmcweb/DOWNSTREAM_0046-ExternalStorer-Cleanups-and-fix-existence-check.patch
+++ /dev/null
@@ -1,517 +0,0 @@
-From f463b55c020326f6b66d8364f7ac56f0b767950e Mon Sep 17 00:00:00 2001
-From: Josh Lehan <krellan@google.com>
-Date: Sat, 24 Jun 2023 22:27:37 -0700
-Subject: [PATCH] ExternalStorer: Cleanups and fix existence check
-
-An ExternalStorer instance (directory representing a Redfish container)
-is a directory with an index.json file in it, and optionally also
-contains a middle, another subdirectory with another index.json file in
-it. Now checking for all of these to be in existence first, before
-declaring a duplicate creation error. If any of these are missing, the
-creation request will now be allowed to go through, so partials are now
-eligible for creation again (overwriting any previous content) instead
-of falsely declaring an error preventing further creation. This
-resolves the situation in which a directory could be in an invalid
-state, somehow created but missing the index.json file within.
-
-Added more debugging statements regarding files and directories. These
-will only show up when debugging is enabled.
-
-Refactored the writeJsonFile function similar to readJsonFile earlier,
-cleaning it up. Now checks for maximum size before writing file, and
-writes the entire file all at once.
-
-Making sure all BMCWEB_ROUTE() paths have trailing slashes, so that
-they work regardless of whether the user includes a trailing slash, or
-not, in their query.
-
-Ran through clang-format again, as some formatting errors had crept
-into processor.hpp during previous patches.
-
-Tested: Did a deep query by itself, and did a shallow query with expand
-level 6, with the /run/bmcweb directory in each of these 3 states:
-completely empty, containing files and directories, and containing only
-empty directories (no files). All now give correct results.
-
-/redfish/v1/Systems/system/Processors/cpu0/SubProcessors/core0/SubProcessors/thread0/ProcessorMetrics
-/redfish/v1/Systems/system/Processors?\$expand=\*\(\$levels=6\)
-
-Patch Tracking Bug: b/289119203
-Upstream info / review: n/a
-Upstream-Status: Inappropriate [b/230806664]
-Justification: Overall the ExternalStorer feature already upstream rejected
-
-Google-Bug-Id: 283698917
-Signed-off-by: Josh Lehan <krellan@google.com>
----
- redfish-core/lib/external_storer.hpp | 140 ++++++++++++++++++++-------
- redfish-core/lib/processor.hpp       |  56 +++++------
- 2 files changed, 131 insertions(+), 65 deletions(-)
-
-diff --git a/redfish-core/lib/external_storer.hpp b/redfish-core/lib/external_storer.hpp
-index 5ab36752..46bb27b5 100644
---- a/redfish-core/lib/external_storer.hpp
-+++ b/redfish-core/lib/external_storer.hpp
-@@ -77,6 +77,7 @@
- #include <cmath>
- #include <cstddef>
- #include <fstream>
-+#include <sstream>
-
- namespace external_storer
- {
-@@ -359,16 +360,29 @@ std::optional<nlohmann::json>
-     return {content};
- }
-
--// FUTURE: Should preflight first, to also enforce maxFileSize limit
--std::optional<std::streampos>
--    writeJsonFile(const std::filesystem::path& filename,
--                  const nlohmann::json& content)
-+std::optional<std::size_t> writeJsonFile(const std::filesystem::path& filename,
-+                                         const nlohmann::json& content)
- {
--    std::ofstream output;
--    std::streampos size;
-+    std::stringstream stream;
-
--    output.open(filename, std::ofstream::trunc);
-+    // Must supply 4th argument to avoid throwing exceptions
-+    stream << content.dump(-1, ' ', false,
-+                           nlohmann::json::error_handler_t::replace);
-+
-+    std::string buffer = stream.str();
-+
-+    size_t fileSize = buffer.size();
-+    if (fileSize > maxFileSize)
-+    {
-+        BMCWEB_LOG_ERROR << "File is too large: " << fileSize << "/"
-+                         << maxFileSize;
-+        return std::nullopt;
-+    }
-+
-+    BMCWEB_LOG_DEBUG << "Output write size: " << fileSize << " bytes";
-
-+    std::ofstream output;
-+    output.open(filename, std::ofstream::trunc);
-     if (!output)
-     {
-         int err = errno;
-@@ -377,15 +391,31 @@ std::optional<std::streampos>
-         return std::nullopt;
-     }
-
--    // Must supply 4th argument to avoid throwing exceptions
--    output << content.dump(-1, ' ', false,
--                           nlohmann::json::error_handler_t::replace);
-+    output.write(buffer.data(), static_cast<std::streamsize>(fileSize));
-
--    size = output.tellp();
-+    bool writeGood = output.good();
-
-+    if (!writeGood)
-+    {
-+        // Do not return here, need to defer until after the close
-+        int err = errno;
-+        BMCWEB_LOG_ERROR << "Error writing " << filename
-+                         << " output: " << strerror(err);
-+    }
-+
-+    // Always do this, no matter what, even if write failed
-     output.close();
-
--    if (!(output.good()))
-+    bool closeGood = output.good();
-+
-+    // This is the deferred error return from write() above
-+    if (!writeGood)
-+    {
-+        // The errno from write() already printed
-+        return std::nullopt;
-+    }
-+
-+    if (!closeGood)
-     {
-         int err = errno;
-         BMCWEB_LOG_ERROR << "Error closing " << filename
-@@ -393,7 +423,8 @@ std::optional<std::streampos>
-         return std::nullopt;
-     }
-
--    return {size};
-+    BMCWEB_LOG_DEBUG << "Output write success: " << filename;
-+    return {fileSize};
- }
-
- // The "proposedName" should be a basename, with no directory separators
-@@ -613,34 +644,60 @@ void Hook::handleCreateInstance(
-     std::filesystem::path outerUrl = locInstance(idInstance);
-     std::filesystem::path outerDir = locToFileDir(outerUrl);
-
--    std::error_code ec;
-+    std::filesystem::path outerFilename = locToFileJson(outerUrl);
-
--    if (std::filesystem::exists(outerDir, ec))
-+    std::filesystem::path innerUrl = locMiddle(idInstance);
-+    std::filesystem::path innerDir = locToFileDir(innerUrl);
-+
-+    std::filesystem::path innerFilename = locToFileJson(innerUrl);
-+
-+    // If no middle keyword, then no need to create multiple layers
-+    if (pathMiddle.empty())
-     {
--        BMCWEB_LOG_ERROR << "Uploaded instance ID already exists on system";
--        redfish::messages::resourceAlreadyExists(asyncResp->res, "String", "Id",
--                                                 idInstance);
--        return;
-+        innerDir = outerDir;
-     }
-+
-+    std::error_code ec;
-+
-+    BMCWEB_LOG_DEBUG << "Create instance " << idInstance << " checking "
-+                     << outerDir;
-+
-+    bool outerExists = std::filesystem::exists(outerFilename, ec);
-     if (ec)
-     {
--        BMCWEB_LOG_ERROR << "Problem checking for " << outerDir
-+        BMCWEB_LOG_ERROR << "Problem checking for " << outerFilename
-                          << " duplicate: " << ec.message();
-         redfish::messages::operationFailed(asyncResp->res);
-         return;
-     }
-
--    std::filesystem::path innerUrl = locMiddle(idInstance);
--    std::filesystem::path innerDir = locToFileDir(innerUrl);
-+    // If no middle keyword, only outer file necessary to declare dupe
-+    bool innerExists = true;
-+    if (!pathMiddle.empty())
-+    {
-+        innerExists = std::filesystem::exists(innerFilename, ec);
-+        if (ec)
-+        {
-+            BMCWEB_LOG_ERROR << "Problem checking for " << innerFilename
-+                             << " duplicate: " << ec.message();
-+            redfish::messages::operationFailed(asyncResp->res);
-+            return;
-+        }
-+    }
-
--    // If no middle keyword, then no need to create multiple layers
--    if (pathMiddle.empty())
-+    // It is only considered a dupe error if both files already exist
-+    if (outerExists && innerExists)
-     {
--        innerDir = outerDir;
-+        BMCWEB_LOG_ERROR << "Uploaded instance ID already exists on system";
-+        redfish::messages::resourceAlreadyExists(asyncResp->res, "String", "Id",
-+                                                 idInstance);
-+        return;
-     }
-
--    std::filesystem::create_directories(innerDir, ec);
-+    BMCWEB_LOG_DEBUG << "Create instance " << idInstance << " making "
-+                     << innerDir;
-
-+    std::filesystem::create_directories(innerDir, ec);
-     if (ec)
-     {
-         BMCWEB_LOG_ERROR << "Problem making " << innerDir
-@@ -649,8 +706,8 @@ void Hook::handleCreateInstance(
-         return;
-     }
-
--    std::filesystem::path outerFilename = locToFileJson(outerUrl);
--
-+    BMCWEB_LOG_DEBUG << "Create instance " << idInstance << " writing "
-+                     << outerFilename;
-     if (!(writeJsonFile(outerFilename, content).has_value()))
-     {
-         BMCWEB_LOG_ERROR << "Problem writing file " << outerFilename;
-@@ -660,8 +717,8 @@ void Hook::handleCreateInstance(
-
-     if (!(pathMiddle.empty()))
-     {
--        std::filesystem::path innerFilename = locToFileJson(innerUrl);
--
-+        BMCWEB_LOG_DEBUG << "Create instance " << idInstance << "/"
-+                         << pathMiddle << " writing " << innerFilename;
-         if (!(writeJsonFile(innerFilename, innerContent).has_value()))
-         {
-             BMCWEB_LOG_ERROR << "Problem writing file " << innerFilename;
-@@ -722,9 +779,16 @@ void Hook::handleCreateMiddle(
-     std::filesystem::path outerUrl = locInstance(idInstance);
-     std::filesystem::path outerDir = locToFileDir(outerUrl);
-
-+    std::filesystem::path entryUrl = locEntry(idInstance, idEntry);
-+    std::filesystem::path entryDir = locToFileDir(entryUrl);
-+
-+    std::filesystem::path entryFilename = locToFileJson(entryUrl);
-+
-     std::error_code ec;
-
-     // The instance must already have been created earlier
-+    BMCWEB_LOG_DEBUG << "Create entry " << idInstance << " checking "
-+                     << outerDir;
-     if (!(std::filesystem::exists(outerDir, ec)))
-     {
-         BMCWEB_LOG_ERROR << "Cannot add entry to nonexistent instance "
-@@ -740,9 +804,7 @@ void Hook::handleCreateMiddle(
-         return;
-     }
-
--    std::filesystem::path entryUrl = locEntry(idInstance, idEntry);
--    std::filesystem::path entryDir = locToFileDir(entryUrl);
--
-+    BMCWEB_LOG_DEBUG << "Create entry " << idInstance << " making " << entryDir;
-     std::filesystem::create_directories(entryDir, ec);
-
-     if (ec)
-@@ -753,8 +815,8 @@ void Hook::handleCreateMiddle(
-         return;
-     }
-
--    std::filesystem::path entryFilename = locToFileJson(entryUrl);
--
-+    BMCWEB_LOG_DEBUG << "Create entry " << idInstance << " writing "
-+                     << entryFilename;
-     if (std::filesystem::exists(entryFilename, ec))
-     {
-         BMCWEB_LOG_ERROR << "Uploaded entry ID already exists within instance";
-@@ -911,6 +973,8 @@ void Hook::handleGetInstance(
-
-     std::error_code ec;
-
-+    BMCWEB_LOG_DEBUG << "Get instance " << idInstance << " checking "
-+                     << outerFilename;
-     if (!(std::filesystem::exists(outerFilename, ec)))
-     {
-         BMCWEB_LOG_ERROR << "Instance not found with ID " << idInstance;
-@@ -977,6 +1041,9 @@ void Hook::handleGetMiddle(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-
-     std::error_code ec;
-
-+    BMCWEB_LOG_DEBUG << "Get middle " << idInstance
-+                     << (keywordMiddle.empty() ? "" : "/") << keywordMiddle
-+                     << " checking " << innerFilename;
-     if (!(std::filesystem::exists(innerFilename, ec)))
-     {
-         BMCWEB_LOG_ERROR << "Instance not found with ID " << idInstance;
-@@ -1086,6 +1153,9 @@ void Hook::handleGetEntry(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
-
-     std::error_code ec;
-
-+    BMCWEB_LOG_DEBUG << "Get entry " << idInstance
-+                     << (keywordMiddle.empty() ? "" : "/") << keywordMiddle
-+                     << "/" << idEntry << " checking " << entryFilename;
-     const auto doesExist = (std::filesystem::exists(entryFilename, ec));
-     if (!doesExist)
-     {
-diff --git a/redfish-core/lib/processor.hpp b/redfish-core/lib/processor.hpp
-index 94b601a5..c88291ae 100644
---- a/redfish-core/lib/processor.hpp
-+++ b/redfish-core/lib/processor.hpp
-@@ -1384,13 +1384,11 @@ inline void getSubProcessorThreadMembers(
-         "redfish", "v1", "Systems", "system", "Processors", processorId,
-         "SubProcessors", coreId, "SubProcessors");
-
--    constexpr std::array<std::string_view, 1> interfaces {
--                    "xyz.openbmc_project.Inventory.Item.CpuThread"
--    };
-+    constexpr std::array<std::string_view, 1> interfaces{
-+        "xyz.openbmc_project.Inventory.Item.CpuThread"};
-
--    collection_util::getCollectionMembers(
--        aResp, subProcessorsPath,
--        interfaces, corePath.c_str());
-+    collection_util::getCollectionMembers(aResp, subProcessorsPath, interfaces,
-+                                          corePath.c_str());
- }
-
- inline void
-@@ -1652,9 +1650,8 @@ inline void
-         "redfish", "v1", "Systems", "system", "Processors", processorId,
-         "SubProcessors");
-
--    constexpr std::array<std::string_view, 1> interfaces {
--        "xyz.openbmc_project.Inventory.Item.CpuCore"
--    };
-+    constexpr std::array<std::string_view, 1> interfaces{
-+        "xyz.openbmc_project.Inventory.Item.CpuCore"};
-
-     collection_util::getCollectionMembers(aResp, subProcessorsPath, interfaces,
-                                           cpuPath.c_str());
-@@ -1938,7 +1935,7 @@ inline void
-                 baseSpeedArray.push_back(std::move(speed));
-             }
-         }
--        });
-+    });
- }
-
- /**
-@@ -2074,7 +2071,7 @@ inline void patchAppliedOperatingConfig(
-         [resp, appliedConfigUri](const boost::system::error_code& ec,
-                                  const sdbusplus::message_t& msg) {
-         handleAppliedConfigResponse(resp, appliedConfigUri, ec, msg);
--        },
-+    },
-         *controlService, cpuObjectPath, "org.freedesktop.DBus.Properties",
-         "Set", "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig",
-         "AppliedConfig", dbus::utility::DbusVariantType(std::move(configPath)));
-@@ -2316,9 +2313,8 @@ inline void requestRoutesOperatingConfigCollection(App& app)
-
-                 // Use the common search routine to construct the
-                 // Collection of all Config objects under this CPU.
--                constexpr std::array<std::string_view, 1> interface {
--                    "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig"
--                };
-+                constexpr std::array<std::string_view, 1> interface{
-+                    "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig"};
-                 collection_util::getCollectionMembers(
-                     asyncResp,
-                     crow::utility::urlFromPieces("redfish", "v1", "Systems",
-@@ -2327,8 +2323,8 @@ inline void requestRoutesOperatingConfigCollection(App& app)
-                     interface, object.c_str());
-                 return;
-             }
--            });
-         });
-+    });
- }
-
- inline void requestRoutesOperatingConfig(App& app)
-@@ -2387,8 +2383,8 @@ inline void requestRoutesOperatingConfig(App& app)
-             }
-             messages::resourceNotFound(asyncResp->res, "OperatingConfig",
-                                        configName);
--            });
-         });
-+    });
- }
-
- inline int countProcessors(void)
-@@ -2828,7 +2824,7 @@ inline void requestRoutesProcessorMetrics(App& app)
-             return;
-         }
-         handleGetCpuMetrics(asyncResp, ""_json_pointer, cpuArg);
--        });
-+    });
-
-     BMCWEB_ROUTE(
-         app,
-@@ -2845,7 +2841,7 @@ inline void requestRoutesProcessorMetrics(App& app)
-         }
-         handleGetCpuCoreThreadMetrics(asyncResp, ""_json_pointer, cpuArg,
-                                       coreArg, threadArg);
--        });
-+    });
-
-     // TODO(): Implement corresponding PATCH methods
-     // The POST method is not correct to use here,
-@@ -2924,7 +2920,7 @@ inline void requestRoutesProcessor(App& app)
-         getProcessorObject(asyncResp, processorId,
-                            std::bind_front(getProcessorData, asyncResp,
-                                            ""_json_pointer, 0, processorId));
--        });
-+    });
-
-     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/Processors/<str>/")
-         .privileges(redfish::privileges::patchProcessor)
-@@ -2967,7 +2963,7 @@ inline void requestRoutesProcessor(App& app)
-                                                asyncResp, processorId,
-                                                appliedConfigUri));
-         }
--        });
-+    });
-
-     requestRoutesProcessorMetrics(app);
- }
-@@ -2981,7 +2977,7 @@ inline void requestRoutesSubProcessorCoreCollection(App& app)
-             handleSubProcessorCoreCollectionHead, std::ref(app)));
-
-     BMCWEB_ROUTE(app,
--                 "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors")
-+                 "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/")
-         .privileges(redfish::privileges::getProcessorCollection)
-         .methods(boost::beast::http::verb::get)(
-             [&app](const crow::Request& req,
-@@ -3004,7 +3000,7 @@ inline void requestRoutesSubProcessorCoreCollection(App& app)
-         getProcessorPaths(asyncResp, processorId,
-                           std::bind_front(getSubProcessorCoreMembers, asyncResp,
-                                           processorId));
--        });
-+    });
- }
-
- inline void requestRoutesSubProcessorCore(App& app)
-@@ -3016,7 +3012,7 @@ inline void requestRoutesSubProcessorCore(App& app)
-             std::bind_front(handleSubProcessorCoreHead, std::ref(app)));
-
-     BMCWEB_ROUTE(
--        app, "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>")
-+        app, "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/")
-         .privileges(redfish::privileges::getProcessor)
-         .methods(boost::beast::http::verb::get)(
-             [&app](const crow::Request& req,
-@@ -3040,20 +3036,20 @@ inline void requestRoutesSubProcessorCore(App& app)
-         getProcessorPaths(asyncResp, processorId,
-                           std::bind_front(getSubProcessorCoreData, asyncResp,
-                                           processorId, coreId));
--        });
-+    });
- }
-
- inline void requestRoutesSubProcessorThreadCollection(App& app)
- {
-     BMCWEB_ROUTE(
-         app,
--        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors")
-+        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors/")
-         .privileges(redfish::privileges::headProcessorCollection)
-         .methods(boost::beast::http::verb::head)(std::bind_front(
-             handleSubProcessorThreadCollectionHead, std::ref(app)));
-     BMCWEB_ROUTE(
-         app,
--        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors")
-+        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors/")
-         .privileges(redfish::privileges::getProcessorCollection)
-         .methods(boost::beast::http::verb::get)(
-             [&app](const crow::Request& req,
-@@ -3075,20 +3071,20 @@ inline void requestRoutesSubProcessorThreadCollection(App& app)
-                                  std::bind_front(getSubProcessorThreadMembers,
-                                                  asyncResp, processorId,
-                                                  coreId));
--        });
-+    });
- }
-
- inline void requestRoutesSubProcessorThread(App& app)
- {
-     BMCWEB_ROUTE(
-         app,
--        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors/<str>")
-+        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors/<str>/")
-         .privileges(redfish::privileges::headProcessor)
-         .methods(boost::beast::http::verb::head)(
-             std::bind_front(handleSubProcessorThreadHead, std::ref(app)));
-     BMCWEB_ROUTE(
-         app,
--        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors/<str>")
-+        "/redfish/v1/Systems/<str>/Processors/<str>/SubProcessors/<str>/SubProcessors/<str>/")
-         .privileges(redfish::privileges::getProcessor)
-         .methods(boost::beast::http::verb::get)(
-             [&app](const crow::Request& req,
-@@ -3111,7 +3107,7 @@ inline void requestRoutesSubProcessorThread(App& app)
-                                  std::bind_front(getSubProcessorThreadData,
-                                                  asyncResp, processorId, coreId,
-                                                  threadId));
--        });
-+    });
- }
-
- } // namespace redfish
---
-2.41.0.162.gfafddb0af9-goog
-
diff --git a/recipes-phosphor/interfaces/bmcweb_%.bbappend b/recipes-phosphor/interfaces/bmcweb_%.bbappend
index 773d6ed..2468642 100644
--- a/recipes-phosphor/interfaces/bmcweb_%.bbappend
+++ b/recipes-phosphor/interfaces/bmcweb_%.bbappend
@@ -97,23 +97,6 @@
 agentless_redfish_options = " \
   -Dredfish-dump-log=enabled \
 "
-agentless_patches = " \
-  file://0001-bmcweb-New-LogServices-feature-ExternalStorer.patch \
-  file://0002-bmcweb-ExternalStorer-for-MemoryMetrics.patch \
-  file://0003-bmcweb-ExternalStorer-for-ProcessorMetrics.patch \
-  file://0043-bmcweb-patch-0001-Making-readJsonFile-faster.patch \
-  file://0044-bmcweb-make-all-std-regex-static.patch \
-  file://0004-Processor-Include-Processor-Metrics-in-Efficient-Exp.patch \
-  file://0002-bmcweb-agentless-Fault-Log-CPER-logs-Crashdumps.patch \
-  file://0007-bmcweb-Add-dump-type-in-dump-LogService-Name.patch \
-  file://0022-Implement-Fan-schema.patch \
-  file://DOWNSTREAM_0045-ExternalStorer-Followup-cleanup-to-readJsonFile.patch \
-  file://DOWNSTREAM_0046-ExternalStorer-Cleanups-and-fix-existence-check.patch \
-"
-agentless_patches:gbmcfork = ""
-SRC_URI:append:gbmc = " \
-  ${@bb.utils.contains('DISTRO_FEATURES', 'agentless', '${agentless_patches}', '', d)} \
-"
 EXTRA_OEMESON:append:gbmc = " \
   ${@bb.utils.contains('DISTRO_FEATURES', 'agentless', '${agentless_redfish_options}', '', d)} \
 "