EntityManager: Split out PerformScan

Start breaking down the code so regular people can comprehend it.

Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
Change-Id: I82fec105fbe29bd09145feaaa48fbcb875ca930f
diff --git a/include/EntityManager.hpp b/include/EntityManager.hpp
index 084174d..a79e7fe 100644
--- a/include/EntityManager.hpp
+++ b/include/EntityManager.hpp
@@ -40,6 +40,25 @@
 using FoundDeviceT = std::vector<std::tuple<
     boost::container::flat_map<std::string, BasicVariantType>, std::string>>;
 
+struct CmpStr
+{
+    bool operator()(const char* a, const char* b) const
+    {
+        return std::strcmp(a, b) < 0;
+    }
+};
+
+// underscore T for collison with dbus c api
+enum class probe_type_codes
+{
+    FALSE_T,
+    TRUE_T,
+    AND,
+    OR,
+    FOUND,
+    MATCH_ONE
+};
+
 struct PerformScan : std::enable_shared_from_this<PerformScan>
 {
 
diff --git a/src/EntityManager.cpp b/src/EntityManager.cpp
index 490c0f5..725f2bf 100644
--- a/src/EntityManager.cpp
+++ b/src/EntityManager.cpp
@@ -47,29 +47,9 @@
 constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
 constexpr const char* currentConfiguration = "/var/configuration/system.json";
 constexpr const char* globalSchema = "global.json";
-constexpr const int32_t maxMapperDepth = 0;
-
 constexpr const bool debug = false;
 
-struct CmpStr
-{
-    bool operator()(const char* a, const char* b) const
-    {
-        return std::strcmp(a, b) < 0;
-    }
-};
-
-// underscore T for collison with dbus c api
-enum class probe_type_codes
-{
-    FALSE_T,
-    TRUE_T,
-    AND,
-    OR,
-    FOUND,
-    MATCH_ONE
-};
-const static boost::container::flat_map<const char*, probe_type_codes, CmpStr>
+static const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
     probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
                 {"TRUE", probe_type_codes::TRUE_T},
                 {"AND", probe_type_codes::AND},
@@ -83,9 +63,6 @@
     std::variant<std::vector<std::string>, std::vector<double>, std::string,
                  int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
                  uint16_t, uint8_t, bool>;
-using GetSubTreeType = std::vector<
-    std::pair<std::string,
-              std::vector<std::pair<std::string, std::vector<std::string>>>>>;
 
 using ManagedObjectType = boost::container::flat_map<
     sdbusplus::message::object_path,
@@ -100,17 +77,13 @@
 
 // todo: pass this through nicer
 std::shared_ptr<sdbusplus::asio::connection> systemBus;
-static nlohmann::json lastJson;
+nlohmann::json lastJson;
 
 boost::asio::io_context io;
 
 const std::regex illegalDbusPathRegex("[^A-Za-z0-9_.]");
 const std::regex illegalDbusMemberRegex("[^A-Za-z0-9_]");
 
-void registerCallback(nlohmann::json& systemConfiguration,
-                      sdbusplus::asio::object_server& objServer,
-                      const std::string& path);
-
 static std::shared_ptr<sdbusplus::asio::dbus_interface>
     createInterface(sdbusplus::asio::object_server& objServer,
                     const std::string& path, const std::string& interface,
@@ -136,153 +109,6 @@
     return ptr;
 }
 
-void getInterfaces(
-    const std::tuple<std::string, std::string, std::string>& call,
-    const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
-    const std::shared_ptr<PerformScan>& scan, size_t retries = 5)
-{
-    if (!retries)
-    {
-        std::cerr << "retries exhausted on " << std::get<0>(call) << " "
-                  << std::get<1>(call) << " " << std::get<2>(call) << "\n";
-        return;
-    }
-
-    systemBus->async_method_call(
-        [call, scan, probeVector, retries](
-            boost::system::error_code& errc,
-            const boost::container::flat_map<std::string, BasicVariantType>&
-                resp) {
-            if (errc)
-            {
-                std::cerr << "error calling getall on  " << std::get<0>(call)
-                          << " " << std::get<1>(call) << " "
-                          << std::get<2>(call) << "\n";
-
-                std::shared_ptr<boost::asio::steady_timer> timer =
-                    std::make_shared<boost::asio::steady_timer>(io);
-                timer->expires_after(std::chrono::seconds(2));
-
-                timer->async_wait([timer, call, scan, probeVector,
-                                   retries](const boost::system::error_code&) {
-                    getInterfaces(call, probeVector, scan, retries - 1);
-                });
-                return;
-            }
-
-            scan->dbusProbeObjects[std::get<1>(call)][std::get<2>(call)] = resp;
-        },
-        std::get<0>(call), std::get<1>(call), "org.freedesktop.DBus.Properties",
-        "GetAll", std::get<2>(call));
-
-    if constexpr (debug)
-    {
-        std::cerr << __func__ << " " << __LINE__ << "\n";
-    }
-}
-
-// Populates scan->dbusProbeObjects with all interfaces and properties
-// for the paths that own the interfaces passed in.
-void findDbusObjects(std::vector<std::shared_ptr<PerformProbe>>&& probeVector,
-                     boost::container::flat_set<std::string>&& interfaces,
-                     const std::shared_ptr<PerformScan>& scan,
-                     size_t retries = 5)
-{
-    // Filter out interfaces already obtained.
-    for (const auto& [path, probeInterfaces] : scan->dbusProbeObjects)
-    {
-        for (const auto& [interface, _] : probeInterfaces)
-        {
-            interfaces.erase(interface);
-        }
-    }
-    if (interfaces.empty())
-    {
-        return;
-    }
-
-    // find all connections in the mapper that expose a specific type
-    systemBus->async_method_call(
-        [interfaces, probeVector{std::move(probeVector)}, scan,
-         retries](boost::system::error_code& ec,
-                  const GetSubTreeType& interfaceSubtree) mutable {
-            boost::container::flat_set<
-                std::tuple<std::string, std::string, std::string>>
-                interfaceConnections;
-            if (ec)
-            {
-                if (ec.value() == ENOENT)
-                {
-                    return; // wasn't found by mapper
-                }
-                std::cerr << "Error communicating to mapper.\n";
-
-                if (!retries)
-                {
-                    // if we can't communicate to the mapper something is very
-                    // wrong
-                    std::exit(EXIT_FAILURE);
-                }
-                std::shared_ptr<boost::asio::steady_timer> timer =
-                    std::make_shared<boost::asio::steady_timer>(io);
-                timer->expires_after(std::chrono::seconds(10));
-
-                timer->async_wait(
-                    [timer, interfaces{std::move(interfaces)}, scan,
-                     probeVector{std::move(probeVector)},
-                     retries](const boost::system::error_code&) mutable {
-                        findDbusObjects(std::move(probeVector),
-                                        std::move(interfaces), scan,
-                                        retries - 1);
-                    });
-                return;
-            }
-
-            for (const auto& [path, object] : interfaceSubtree)
-            {
-                for (const auto& [busname, ifaces] : object)
-                {
-                    for (const std::string& iface : ifaces)
-                    {
-                        // The 3 default org.freedeskstop interfaces (Peer,
-                        // Introspectable, and Properties) are returned by
-                        // the mapper but don't have properties, so don't bother
-                        // with the GetAll call to save some cycles.
-                        if (!boost::algorithm::starts_with(iface,
-                                                           "org.freedesktop"))
-                        {
-                            interfaceConnections.emplace(busname, path, iface);
-                        }
-                    }
-                }
-
-                // Get a PropertiesChanged callback for all
-                // interfaces on this path.
-                registerCallback(scan->_systemConfiguration, scan->objServer,
-                                 path);
-            }
-
-            if (interfaceConnections.empty())
-            {
-                return;
-            }
-
-            for (const auto& call : interfaceConnections)
-            {
-                getInterfaces(call, probeVector, scan);
-            }
-        },
-        "xyz.openbmc_project.ObjectMapper",
-        "/xyz/openbmc_project/object_mapper",
-        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
-        interfaces);
-
-    if constexpr (debug)
-    {
-        std::cerr << __func__ << " " << __LINE__ << "\n";
-    }
-}
-
 // probes dbus interface dictionary for a key with a value that matches a regex
 // When an interface passes a probe, also save its D-Bus path with it.
 bool probeDbus(const std::string& interface,
@@ -1213,455 +1039,6 @@
     return true;
 }
 
-std::string getRecordName(
-    const boost::container::flat_map<std::string, BasicVariantType>& probe,
-    const std::string& probeName)
-{
-    if (probe.empty())
-    {
-        return probeName;
-    }
-
-    // use an array so alphabetical order from the
-    // flat_map is maintained
-    auto device = nlohmann::json::array();
-    for (auto& devPair : probe)
-    {
-        device.push_back(devPair.first);
-        std::visit([&device](auto&& v) { device.push_back(v); },
-                   devPair.second);
-    }
-    size_t hash = std::hash<std::string>{}(probeName + device.dump());
-    // hashes are hard to distinguish, use the
-    // non-hashed version if we want debug
-    if constexpr (debug)
-    {
-        return probeName + device.dump();
-    }
-    else
-    {
-        return std::to_string(hash);
-    }
-}
-
-PerformScan::PerformScan(nlohmann::json& systemConfiguration,
-                         nlohmann::json& missingConfigurations,
-                         std::list<nlohmann::json>& configurations,
-                         sdbusplus::asio::object_server& objServerIn,
-                         std::function<void()>&& callback) :
-    _systemConfiguration(systemConfiguration),
-    _missingConfigurations(missingConfigurations),
-    _configurations(configurations), objServer(objServerIn),
-    _callback(std::move(callback))
-{}
-void PerformScan::run()
-{
-    boost::container::flat_set<std::string> dbusProbeInterfaces;
-    std::vector<std::shared_ptr<PerformProbe>> dbusProbePointers;
-
-    for (auto it = _configurations.begin(); it != _configurations.end();)
-    {
-        auto findProbe = it->find("Probe");
-        auto findName = it->find("Name");
-
-        nlohmann::json probeCommand;
-        // check for poorly formatted fields, probe must be an array
-        if (findProbe == it->end())
-        {
-            std::cerr << "configuration file missing probe:\n " << *it << "\n";
-            it = _configurations.erase(it);
-            continue;
-        }
-        if ((*findProbe).type() != nlohmann::json::value_t::array)
-        {
-            probeCommand = nlohmann::json::array();
-            probeCommand.push_back(*findProbe);
-        }
-        else
-        {
-            probeCommand = *findProbe;
-        }
-
-        if (findName == it->end())
-        {
-            std::cerr << "configuration file missing name:\n " << *it << "\n";
-            it = _configurations.erase(it);
-            continue;
-        }
-        std::string probeName = *findName;
-
-        if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
-            passedProbes.end())
-        {
-            it = _configurations.erase(it);
-            continue;
-        }
-        nlohmann::json* recordPtr = &(*it);
-
-        // store reference to this to children to makes sure we don't get
-        // destroyed too early
-        auto thisRef = shared_from_this();
-        auto probePointer = std::make_shared<PerformProbe>(
-            probeCommand, thisRef,
-            [&, recordPtr, probeName](FoundDeviceT& foundDevices,
-                                      const DBusProbeObjectT& allInterfaces) {
-                _passed = true;
-                std::set<nlohmann::json> usedNames;
-                passedProbes.push_back(probeName);
-                std::list<size_t> indexes(foundDevices.size());
-                std::iota(indexes.begin(), indexes.end(), 1);
-
-                size_t indexIdx = probeName.find('$');
-                bool hasTemplateName = (indexIdx != std::string::npos);
-
-                // copy over persisted configurations and make sure we remove
-                // indexes that are already used
-                for (auto itr = foundDevices.begin();
-                     itr != foundDevices.end();)
-                {
-                    std::string recordName =
-                        getRecordName(std::get<0>(*itr), probeName);
-
-                    auto fromLastJson = lastJson.find(recordName);
-                    if (fromLastJson != lastJson.end())
-                    {
-                        auto findExposes = fromLastJson->find("Exposes");
-                        // delete nulls from any updates
-                        if (findExposes != fromLastJson->end())
-                        {
-                            auto copy = nlohmann::json::array();
-                            for (auto& expose : *findExposes)
-                            {
-                                if (expose.is_null())
-                                {
-                                    continue;
-                                }
-                                copy.emplace_back(expose);
-                            }
-                            *findExposes = copy;
-                        }
-
-                        // keep user changes
-                        _systemConfiguration[recordName] = *fromLastJson;
-                        _missingConfigurations.erase(recordName);
-                        itr = foundDevices.erase(itr);
-                        if (hasTemplateName)
-                        {
-                            auto nameIt = fromLastJson->find("Name");
-                            if (nameIt == fromLastJson->end())
-                            {
-                                std::cerr << "Last JSON Illegal\n";
-                                continue;
-                            }
-                            int index = 0;
-                            auto str =
-                                nameIt->get<std::string>().substr(indexIdx);
-                            auto [p, ec] = std::from_chars(
-                                str.data(), str.data() + str.size(), index);
-                            if (ec != std::errc())
-                            {
-                                continue; // non-numeric replacement
-                            }
-                            usedNames.insert(nameIt.value());
-                            auto usedIt = std::find(indexes.begin(),
-                                                    indexes.end(), index);
-
-                            if (usedIt == indexes.end())
-                            {
-                                continue; // less items now
-                            }
-                            indexes.erase(usedIt);
-                        }
-
-                        continue;
-                    }
-                    itr++;
-                }
-
-                std::optional<std::string> replaceStr;
-
-                DBusProbeObjectT::mapped_type emptyInterfaces;
-                boost::container::flat_map<std::string, BasicVariantType>
-                    emptyProps;
-                emptyInterfaces.emplace(std::string{}, emptyProps);
-
-                for (auto& foundDeviceAndPath : foundDevices)
-                {
-                    const boost::container::flat_map<
-                        std::string, BasicVariantType>& foundDevice =
-                        std::get<0>(foundDeviceAndPath);
-                    const std::string& path = std::get<1>(foundDeviceAndPath);
-
-                    // Need all interfaces on this path so that template
-                    // substitutions can be done with any of the contained
-                    // properties.  If the probe that passed didn't use an
-                    // interface, such as if it was just TRUE, then
-                    // templateCharReplace will just get passed in an empty
-                    // map.
-                    const DBusProbeObjectT::mapped_type* allInterfacesOnPath =
-                        &emptyInterfaces;
-
-                    auto ifacesIt = allInterfaces.find(path);
-                    if (ifacesIt != allInterfaces.end())
-                    {
-                        allInterfacesOnPath = &ifacesIt->second;
-                    }
-
-                    nlohmann::json record = *recordPtr;
-                    std::string recordName =
-                        getRecordName(foundDevice, probeName);
-                    size_t foundDeviceIdx = indexes.front();
-                    indexes.pop_front();
-
-                    // check name first so we have no duplicate names
-                    auto getName = record.find("Name");
-                    if (getName == record.end())
-                    {
-                        std::cerr << "Record Missing Name! " << record.dump();
-                        continue; // this should be impossible at this level
-                    }
-
-                    nlohmann::json copyForName = {{"Name", getName.value()}};
-                    nlohmann::json::iterator copyIt = copyForName.begin();
-                    std::optional<std::string> replaceVal =
-                        templateCharReplace(copyIt, *allInterfacesOnPath,
-                                            foundDeviceIdx, replaceStr);
-
-                    if (!replaceStr && replaceVal)
-                    {
-                        if (usedNames.find(copyIt.value()) != usedNames.end())
-                        {
-                            replaceStr = replaceVal;
-                            copyForName = {{"Name", getName.value()}};
-                            copyIt = copyForName.begin();
-                            templateCharReplace(copyIt, *allInterfacesOnPath,
-                                                foundDeviceIdx, replaceStr);
-                        }
-                    }
-
-                    if (replaceStr)
-                    {
-                        std::cerr << "Duplicates found, replacing "
-                                  << *replaceStr
-                                  << " with found device index.\n Consider "
-                                     "fixing template to not have duplicates\n";
-                    }
-
-                    for (auto keyPair = record.begin(); keyPair != record.end();
-                         keyPair++)
-                    {
-                        if (keyPair.key() == "Name")
-                        {
-                            keyPair.value() = copyIt.value();
-                            usedNames.insert(copyIt.value());
-
-                            continue; // already covered above
-                        }
-                        templateCharReplace(keyPair, *allInterfacesOnPath,
-                                            foundDeviceIdx, replaceStr);
-                    }
-
-                    // insert into configuration temporarily to be able to
-                    // reference ourselves
-
-                    _systemConfiguration[recordName] = record;
-
-                    auto findExpose = record.find("Exposes");
-                    if (findExpose == record.end())
-                    {
-                        _systemConfiguration[recordName] = record;
-                        continue;
-                    }
-
-                    for (auto& expose : *findExpose)
-                    {
-                        for (auto keyPair = expose.begin();
-                             keyPair != expose.end(); keyPair++)
-                        {
-
-                            templateCharReplace(keyPair, *allInterfacesOnPath,
-                                                foundDeviceIdx, replaceStr);
-
-                            bool isBind =
-                                boost::starts_with(keyPair.key(), "Bind");
-                            bool isDisable = keyPair.key() == "DisableNode";
-
-                            // special cases
-                            if (!(isBind || isDisable))
-                            {
-                                continue;
-                            }
-
-                            if (keyPair.value().type() !=
-                                    nlohmann::json::value_t::string &&
-                                keyPair.value().type() !=
-                                    nlohmann::json::value_t::array)
-                            {
-                                std::cerr << "Value is invalid type "
-                                          << keyPair.key() << "\n";
-                                continue;
-                            }
-
-                            std::vector<std::string> matches;
-                            if (keyPair.value().type() ==
-                                nlohmann::json::value_t::string)
-                            {
-                                matches.emplace_back(keyPair.value());
-                            }
-                            else
-                            {
-                                for (const auto& value : keyPair.value())
-                                {
-                                    if (value.type() !=
-                                        nlohmann::json::value_t::string)
-                                    {
-                                        std::cerr << "Value is invalid type "
-                                                  << value << "\n";
-                                        break;
-                                    }
-                                    matches.emplace_back(value);
-                                }
-                            }
-
-                            std::set<std::string> foundMatches;
-                            for (auto& configurationPair :
-                                 _systemConfiguration.items())
-                            {
-                                if (isDisable)
-                                {
-                                    // don't disable ourselves
-                                    if (configurationPair.key() == recordName)
-                                    {
-                                        continue;
-                                    }
-                                }
-                                auto configListFind =
-                                    configurationPair.value().find("Exposes");
-
-                                if (configListFind ==
-                                        configurationPair.value().end() ||
-                                    configListFind->type() !=
-                                        nlohmann::json::value_t::array)
-                                {
-                                    continue;
-                                }
-                                for (auto& exposedObject : *configListFind)
-                                {
-                                    auto matchIt = std::find_if(
-                                        matches.begin(), matches.end(),
-                                        [name = (exposedObject)["Name"]
-                                                    .get<std::string>()](
-                                            const std::string& s) {
-                                            return s == name;
-                                        });
-                                    if (matchIt == matches.end())
-                                    {
-                                        continue;
-                                    }
-                                    foundMatches.insert(*matchIt);
-
-                                    if (isBind)
-                                    {
-                                        std::string bind = keyPair.key().substr(
-                                            sizeof("Bind") - 1);
-
-                                        exposedObject["Status"] = "okay";
-                                        expose[bind] = exposedObject;
-                                    }
-                                    else if (isDisable)
-                                    {
-                                        exposedObject["Status"] = "disabled";
-                                    }
-                                }
-                            }
-                            if (foundMatches.size() != matches.size())
-                            {
-                                std::cerr << "configuration file "
-                                             "dependency error, "
-                                             "could not find "
-                                          << keyPair.key() << " "
-                                          << keyPair.value() << "\n";
-                            }
-                        }
-                    }
-                    // overwrite ourselves with cleaned up version
-                    _systemConfiguration[recordName] = record;
-                    _missingConfigurations.erase(recordName);
-                }
-            });
-
-        // parse out dbus probes by discarding other probe types, store in a
-        // map
-        for (const nlohmann::json& probeJson : probeCommand)
-        {
-            const std::string* probe = probeJson.get_ptr<const std::string*>();
-            if (probe == nullptr)
-            {
-                std::cerr << "Probe statement wasn't a string, can't parse";
-                continue;
-            }
-            bool found = false;
-            boost::container::flat_map<const char*, probe_type_codes,
-                                       CmpStr>::const_iterator probeType;
-            for (probeType = probeTypes.begin(); probeType != probeTypes.end();
-                 ++probeType)
-            {
-                if (probe->find(probeType->first) != std::string::npos)
-                {
-                    found = true;
-                    break;
-                }
-            }
-            if (found)
-            {
-                continue;
-            }
-            // syntax requires probe before first open brace
-            auto findStart = probe->find('(');
-            std::string interface = probe->substr(0, findStart);
-            dbusProbeInterfaces.emplace(interface);
-            dbusProbePointers.emplace_back(probePointer);
-        }
-        it++;
-    }
-
-    // probe vector stores a shared_ptr to each PerformProbe that cares
-    // about a dbus interface
-    findDbusObjects(std::move(dbusProbePointers),
-                    std::move(dbusProbeInterfaces), shared_from_this());
-    if constexpr (debug)
-    {
-        std::cerr << __func__ << " " << __LINE__ << "\n";
-    }
-}
-
-PerformScan::~PerformScan()
-{
-    if (_passed)
-    {
-        auto nextScan = std::make_shared<PerformScan>(
-            _systemConfiguration, _missingConfigurations, _configurations,
-            objServer, std::move(_callback));
-        nextScan->passedProbes = std::move(passedProbes);
-        nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
-        nextScan->run();
-
-        if constexpr (debug)
-        {
-            std::cerr << __func__ << " " << __LINE__ << "\n";
-        }
-    }
-    else
-    {
-        _callback();
-
-        if constexpr (debug)
-        {
-            std::cerr << __func__ << " " << __LINE__ << "\n";
-        }
-    }
-}
-
 void startRemovedTimer(boost::asio::steady_timer& timer,
                        nlohmann::json& systemConfiguration)
 {
@@ -1869,31 +1246,6 @@
     });
 }
 
-void registerCallback(nlohmann::json& systemConfiguration,
-                      sdbusplus::asio::object_server& objServer,
-                      const std::string& path)
-{
-    static boost::container::flat_map<std::string, sdbusplus::bus::match::match>
-        dbusMatches;
-
-    auto find = dbusMatches.find(path);
-    if (find != dbusMatches.end())
-    {
-        return;
-    }
-    std::function<void(sdbusplus::message::message & message)> eventHandler =
-
-        [&](sdbusplus::message::message&) {
-            propertiesChangedCallback(systemConfiguration, objServer);
-        };
-
-    sdbusplus::bus::match::match match(
-        static_cast<sdbusplus::bus::bus&>(*systemBus),
-        "type='signal',member='PropertiesChanged',path='" + path + "'",
-        eventHandler);
-    dbusMatches.emplace(path, std::move(match));
-}
-
 int main()
 {
     // setup connection to dbus
diff --git a/src/PerformScan.cpp b/src/PerformScan.cpp
new file mode 100644
index 0000000..70a4e47
--- /dev/null
+++ b/src/PerformScan.cpp
@@ -0,0 +1,669 @@
+/*
+// Copyright (c) 2018 Intel Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+/// \file PerformScan.cpp
+#include "EntityManager.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+
+#include <charconv>
+
+/* Hacks from splitting EntityManager.cpp */
+extern std::shared_ptr<sdbusplus::asio::connection> systemBus;
+extern nlohmann::json lastJson;
+extern void
+    propertiesChangedCallback(nlohmann::json& systemConfiguration,
+                              sdbusplus::asio::object_server& objServer);
+
+/* Keep this in sync with EntityManager.cpp */
+static const boost::container::flat_map<const char*, probe_type_codes, CmpStr>
+    probeTypes{{{"FALSE", probe_type_codes::FALSE_T},
+                {"TRUE", probe_type_codes::TRUE_T},
+                {"AND", probe_type_codes::AND},
+                {"OR", probe_type_codes::OR},
+                {"FOUND", probe_type_codes::FOUND},
+                {"MATCH_ONE", probe_type_codes::MATCH_ONE}}};
+
+using GetSubTreeType = std::vector<
+    std::pair<std::string,
+              std::vector<std::pair<std::string, std::vector<std::string>>>>>;
+
+constexpr const int32_t maxMapperDepth = 0;
+
+constexpr const bool debug = false;
+
+void getInterfaces(
+    const std::tuple<std::string, std::string, std::string>& call,
+    const std::vector<std::shared_ptr<PerformProbe>>& probeVector,
+    const std::shared_ptr<PerformScan>& scan, size_t retries = 5)
+{
+    if (!retries)
+    {
+        std::cerr << "retries exhausted on " << std::get<0>(call) << " "
+                  << std::get<1>(call) << " " << std::get<2>(call) << "\n";
+        return;
+    }
+
+    systemBus->async_method_call(
+        [call, scan, probeVector, retries](
+            boost::system::error_code& errc,
+            const boost::container::flat_map<std::string, BasicVariantType>&
+                resp) {
+            if (errc)
+            {
+                std::cerr << "error calling getall on  " << std::get<0>(call)
+                          << " " << std::get<1>(call) << " "
+                          << std::get<2>(call) << "\n";
+
+                std::shared_ptr<boost::asio::steady_timer> timer =
+                    std::make_shared<boost::asio::steady_timer>(io);
+                timer->expires_after(std::chrono::seconds(2));
+
+                timer->async_wait([timer, call, scan, probeVector,
+                                   retries](const boost::system::error_code&) {
+                    getInterfaces(call, probeVector, scan, retries - 1);
+                });
+                return;
+            }
+
+            scan->dbusProbeObjects[std::get<1>(call)][std::get<2>(call)] = resp;
+        },
+        std::get<0>(call), std::get<1>(call), "org.freedesktop.DBus.Properties",
+        "GetAll", std::get<2>(call));
+
+    if constexpr (debug)
+    {
+        std::cerr << __func__ << " " << __LINE__ << "\n";
+    }
+}
+
+void registerCallback(nlohmann::json& systemConfiguration,
+                      sdbusplus::asio::object_server& objServer,
+                      const std::string& path)
+{
+    static boost::container::flat_map<std::string, sdbusplus::bus::match::match>
+        dbusMatches;
+
+    auto find = dbusMatches.find(path);
+    if (find != dbusMatches.end())
+    {
+        return;
+    }
+    std::function<void(sdbusplus::message::message & message)> eventHandler =
+
+        [&](sdbusplus::message::message&) {
+            propertiesChangedCallback(systemConfiguration, objServer);
+        };
+
+    sdbusplus::bus::match::match match(
+        static_cast<sdbusplus::bus::bus&>(*systemBus),
+        "type='signal',member='PropertiesChanged',path='" + path + "'",
+        eventHandler);
+    dbusMatches.emplace(path, std::move(match));
+}
+
+// Populates scan->dbusProbeObjects with all interfaces and properties
+// for the paths that own the interfaces passed in.
+void findDbusObjects(std::vector<std::shared_ptr<PerformProbe>>&& probeVector,
+                     boost::container::flat_set<std::string>&& interfaces,
+                     const std::shared_ptr<PerformScan>& scan,
+                     size_t retries = 5)
+{
+    // Filter out interfaces already obtained.
+    for (const auto& [path, probeInterfaces] : scan->dbusProbeObjects)
+    {
+        for (const auto& [interface, _] : probeInterfaces)
+        {
+            interfaces.erase(interface);
+        }
+    }
+    if (interfaces.empty())
+    {
+        return;
+    }
+
+    // find all connections in the mapper that expose a specific type
+    systemBus->async_method_call(
+        [interfaces, probeVector{std::move(probeVector)}, scan,
+         retries](boost::system::error_code& ec,
+                  const GetSubTreeType& interfaceSubtree) mutable {
+            boost::container::flat_set<
+                std::tuple<std::string, std::string, std::string>>
+                interfaceConnections;
+            if (ec)
+            {
+                if (ec.value() == ENOENT)
+                {
+                    return; // wasn't found by mapper
+                }
+                std::cerr << "Error communicating to mapper.\n";
+
+                if (!retries)
+                {
+                    // if we can't communicate to the mapper something is very
+                    // wrong
+                    std::exit(EXIT_FAILURE);
+                }
+                std::shared_ptr<boost::asio::steady_timer> timer =
+                    std::make_shared<boost::asio::steady_timer>(io);
+                timer->expires_after(std::chrono::seconds(10));
+
+                timer->async_wait(
+                    [timer, interfaces{std::move(interfaces)}, scan,
+                     probeVector{std::move(probeVector)},
+                     retries](const boost::system::error_code&) mutable {
+                        findDbusObjects(std::move(probeVector),
+                                        std::move(interfaces), scan,
+                                        retries - 1);
+                    });
+                return;
+            }
+
+            for (const auto& [path, object] : interfaceSubtree)
+            {
+                for (const auto& [busname, ifaces] : object)
+                {
+                    for (const std::string& iface : ifaces)
+                    {
+                        // The 3 default org.freedeskstop interfaces (Peer,
+                        // Introspectable, and Properties) are returned by
+                        // the mapper but don't have properties, so don't bother
+                        // with the GetAll call to save some cycles.
+                        if (!boost::algorithm::starts_with(iface,
+                                                           "org.freedesktop"))
+                        {
+                            interfaceConnections.emplace(busname, path, iface);
+                        }
+                    }
+                }
+
+                // Get a PropertiesChanged callback for all
+                // interfaces on this path.
+                registerCallback(scan->_systemConfiguration, scan->objServer,
+                                 path);
+            }
+
+            if (interfaceConnections.empty())
+            {
+                return;
+            }
+
+            for (const auto& call : interfaceConnections)
+            {
+                getInterfaces(call, probeVector, scan);
+            }
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
+        interfaces);
+
+    if constexpr (debug)
+    {
+        std::cerr << __func__ << " " << __LINE__ << "\n";
+    }
+}
+
+std::string getRecordName(
+    const boost::container::flat_map<std::string, BasicVariantType>& probe,
+    const std::string& probeName)
+{
+    if (probe.empty())
+    {
+        return probeName;
+    }
+
+    // use an array so alphabetical order from the
+    // flat_map is maintained
+    auto device = nlohmann::json::array();
+    for (auto& devPair : probe)
+    {
+        device.push_back(devPair.first);
+        std::visit([&device](auto&& v) { device.push_back(v); },
+                   devPair.second);
+    }
+    size_t hash = std::hash<std::string>{}(probeName + device.dump());
+    // hashes are hard to distinguish, use the
+    // non-hashed version if we want debug
+    if constexpr (debug)
+    {
+        return probeName + device.dump();
+    }
+    else
+    {
+        return std::to_string(hash);
+    }
+}
+
+PerformScan::PerformScan(nlohmann::json& systemConfiguration,
+                         nlohmann::json& missingConfigurations,
+                         std::list<nlohmann::json>& configurations,
+                         sdbusplus::asio::object_server& objServerIn,
+                         std::function<void()>&& callback) :
+    _systemConfiguration(systemConfiguration),
+    _missingConfigurations(missingConfigurations),
+    _configurations(configurations), objServer(objServerIn),
+    _callback(std::move(callback))
+{}
+void PerformScan::run()
+{
+    boost::container::flat_set<std::string> dbusProbeInterfaces;
+    std::vector<std::shared_ptr<PerformProbe>> dbusProbePointers;
+
+    for (auto it = _configurations.begin(); it != _configurations.end();)
+    {
+        auto findProbe = it->find("Probe");
+        auto findName = it->find("Name");
+
+        nlohmann::json probeCommand;
+        // check for poorly formatted fields, probe must be an array
+        if (findProbe == it->end())
+        {
+            std::cerr << "configuration file missing probe:\n " << *it << "\n";
+            it = _configurations.erase(it);
+            continue;
+        }
+        if ((*findProbe).type() != nlohmann::json::value_t::array)
+        {
+            probeCommand = nlohmann::json::array();
+            probeCommand.push_back(*findProbe);
+        }
+        else
+        {
+            probeCommand = *findProbe;
+        }
+
+        if (findName == it->end())
+        {
+            std::cerr << "configuration file missing name:\n " << *it << "\n";
+            it = _configurations.erase(it);
+            continue;
+        }
+        std::string probeName = *findName;
+
+        if (std::find(passedProbes.begin(), passedProbes.end(), probeName) !=
+            passedProbes.end())
+        {
+            it = _configurations.erase(it);
+            continue;
+        }
+        nlohmann::json* recordPtr = &(*it);
+
+        // store reference to this to children to makes sure we don't get
+        // destroyed too early
+        auto thisRef = shared_from_this();
+        auto probePointer = std::make_shared<PerformProbe>(
+            probeCommand, thisRef,
+            [&, recordPtr, probeName](FoundDeviceT& foundDevices,
+                                      const DBusProbeObjectT& allInterfaces) {
+                _passed = true;
+                std::set<nlohmann::json> usedNames;
+                passedProbes.push_back(probeName);
+                std::list<size_t> indexes(foundDevices.size());
+                std::iota(indexes.begin(), indexes.end(), 1);
+
+                size_t indexIdx = probeName.find('$');
+                bool hasTemplateName = (indexIdx != std::string::npos);
+
+                // copy over persisted configurations and make sure we remove
+                // indexes that are already used
+                for (auto itr = foundDevices.begin();
+                     itr != foundDevices.end();)
+                {
+                    std::string recordName =
+                        getRecordName(std::get<0>(*itr), probeName);
+
+                    auto fromLastJson = lastJson.find(recordName);
+                    if (fromLastJson != lastJson.end())
+                    {
+                        auto findExposes = fromLastJson->find("Exposes");
+                        // delete nulls from any updates
+                        if (findExposes != fromLastJson->end())
+                        {
+                            auto copy = nlohmann::json::array();
+                            for (auto& expose : *findExposes)
+                            {
+                                if (expose.is_null())
+                                {
+                                    continue;
+                                }
+                                copy.emplace_back(expose);
+                            }
+                            *findExposes = copy;
+                        }
+
+                        // keep user changes
+                        _systemConfiguration[recordName] = *fromLastJson;
+                        _missingConfigurations.erase(recordName);
+                        itr = foundDevices.erase(itr);
+                        if (hasTemplateName)
+                        {
+                            auto nameIt = fromLastJson->find("Name");
+                            if (nameIt == fromLastJson->end())
+                            {
+                                std::cerr << "Last JSON Illegal\n";
+                                continue;
+                            }
+                            int index = 0;
+                            auto str =
+                                nameIt->get<std::string>().substr(indexIdx);
+                            auto [p, ec] = std::from_chars(
+                                str.data(), str.data() + str.size(), index);
+                            if (ec != std::errc())
+                            {
+                                continue; // non-numeric replacement
+                            }
+                            usedNames.insert(nameIt.value());
+                            auto usedIt = std::find(indexes.begin(),
+                                                    indexes.end(), index);
+
+                            if (usedIt == indexes.end())
+                            {
+                                continue; // less items now
+                            }
+                            indexes.erase(usedIt);
+                        }
+
+                        continue;
+                    }
+                    itr++;
+                }
+
+                std::optional<std::string> replaceStr;
+
+                DBusProbeObjectT::mapped_type emptyInterfaces;
+                boost::container::flat_map<std::string, BasicVariantType>
+                    emptyProps;
+                emptyInterfaces.emplace(std::string{}, emptyProps);
+
+                for (auto& foundDeviceAndPath : foundDevices)
+                {
+                    const boost::container::flat_map<
+                        std::string, BasicVariantType>& foundDevice =
+                        std::get<0>(foundDeviceAndPath);
+                    const std::string& path = std::get<1>(foundDeviceAndPath);
+
+                    // Need all interfaces on this path so that template
+                    // substitutions can be done with any of the contained
+                    // properties.  If the probe that passed didn't use an
+                    // interface, such as if it was just TRUE, then
+                    // templateCharReplace will just get passed in an empty
+                    // map.
+                    const DBusProbeObjectT::mapped_type* allInterfacesOnPath =
+                        &emptyInterfaces;
+
+                    auto ifacesIt = allInterfaces.find(path);
+                    if (ifacesIt != allInterfaces.end())
+                    {
+                        allInterfacesOnPath = &ifacesIt->second;
+                    }
+
+                    nlohmann::json record = *recordPtr;
+                    std::string recordName =
+                        getRecordName(foundDevice, probeName);
+                    size_t foundDeviceIdx = indexes.front();
+                    indexes.pop_front();
+
+                    // check name first so we have no duplicate names
+                    auto getName = record.find("Name");
+                    if (getName == record.end())
+                    {
+                        std::cerr << "Record Missing Name! " << record.dump();
+                        continue; // this should be impossible at this level
+                    }
+
+                    nlohmann::json copyForName = {{"Name", getName.value()}};
+                    nlohmann::json::iterator copyIt = copyForName.begin();
+                    std::optional<std::string> replaceVal =
+                        templateCharReplace(copyIt, *allInterfacesOnPath,
+                                            foundDeviceIdx, replaceStr);
+
+                    if (!replaceStr && replaceVal)
+                    {
+                        if (usedNames.find(copyIt.value()) != usedNames.end())
+                        {
+                            replaceStr = replaceVal;
+                            copyForName = {{"Name", getName.value()}};
+                            copyIt = copyForName.begin();
+                            templateCharReplace(copyIt, *allInterfacesOnPath,
+                                                foundDeviceIdx, replaceStr);
+                        }
+                    }
+
+                    if (replaceStr)
+                    {
+                        std::cerr << "Duplicates found, replacing "
+                                  << *replaceStr
+                                  << " with found device index.\n Consider "
+                                     "fixing template to not have duplicates\n";
+                    }
+
+                    for (auto keyPair = record.begin(); keyPair != record.end();
+                         keyPair++)
+                    {
+                        if (keyPair.key() == "Name")
+                        {
+                            keyPair.value() = copyIt.value();
+                            usedNames.insert(copyIt.value());
+
+                            continue; // already covered above
+                        }
+                        templateCharReplace(keyPair, *allInterfacesOnPath,
+                                            foundDeviceIdx, replaceStr);
+                    }
+
+                    // insert into configuration temporarily to be able to
+                    // reference ourselves
+
+                    _systemConfiguration[recordName] = record;
+
+                    auto findExpose = record.find("Exposes");
+                    if (findExpose == record.end())
+                    {
+                        _systemConfiguration[recordName] = record;
+                        continue;
+                    }
+
+                    for (auto& expose : *findExpose)
+                    {
+                        for (auto keyPair = expose.begin();
+                             keyPair != expose.end(); keyPair++)
+                        {
+
+                            templateCharReplace(keyPair, *allInterfacesOnPath,
+                                                foundDeviceIdx, replaceStr);
+
+                            bool isBind =
+                                boost::starts_with(keyPair.key(), "Bind");
+                            bool isDisable = keyPair.key() == "DisableNode";
+
+                            // special cases
+                            if (!(isBind || isDisable))
+                            {
+                                continue;
+                            }
+
+                            if (keyPair.value().type() !=
+                                    nlohmann::json::value_t::string &&
+                                keyPair.value().type() !=
+                                    nlohmann::json::value_t::array)
+                            {
+                                std::cerr << "Value is invalid type "
+                                          << keyPair.key() << "\n";
+                                continue;
+                            }
+
+                            std::vector<std::string> matches;
+                            if (keyPair.value().type() ==
+                                nlohmann::json::value_t::string)
+                            {
+                                matches.emplace_back(keyPair.value());
+                            }
+                            else
+                            {
+                                for (const auto& value : keyPair.value())
+                                {
+                                    if (value.type() !=
+                                        nlohmann::json::value_t::string)
+                                    {
+                                        std::cerr << "Value is invalid type "
+                                                  << value << "\n";
+                                        break;
+                                    }
+                                    matches.emplace_back(value);
+                                }
+                            }
+
+                            std::set<std::string> foundMatches;
+                            for (auto& configurationPair :
+                                 _systemConfiguration.items())
+                            {
+                                if (isDisable)
+                                {
+                                    // don't disable ourselves
+                                    if (configurationPair.key() == recordName)
+                                    {
+                                        continue;
+                                    }
+                                }
+                                auto configListFind =
+                                    configurationPair.value().find("Exposes");
+
+                                if (configListFind ==
+                                        configurationPair.value().end() ||
+                                    configListFind->type() !=
+                                        nlohmann::json::value_t::array)
+                                {
+                                    continue;
+                                }
+                                for (auto& exposedObject : *configListFind)
+                                {
+                                    auto matchIt = std::find_if(
+                                        matches.begin(), matches.end(),
+                                        [name = (exposedObject)["Name"]
+                                                    .get<std::string>()](
+                                            const std::string& s) {
+                                            return s == name;
+                                        });
+                                    if (matchIt == matches.end())
+                                    {
+                                        continue;
+                                    }
+                                    foundMatches.insert(*matchIt);
+
+                                    if (isBind)
+                                    {
+                                        std::string bind = keyPair.key().substr(
+                                            sizeof("Bind") - 1);
+
+                                        exposedObject["Status"] = "okay";
+                                        expose[bind] = exposedObject;
+                                    }
+                                    else if (isDisable)
+                                    {
+                                        exposedObject["Status"] = "disabled";
+                                    }
+                                }
+                            }
+                            if (foundMatches.size() != matches.size())
+                            {
+                                std::cerr << "configuration file "
+                                             "dependency error, "
+                                             "could not find "
+                                          << keyPair.key() << " "
+                                          << keyPair.value() << "\n";
+                            }
+                        }
+                    }
+                    // overwrite ourselves with cleaned up version
+                    _systemConfiguration[recordName] = record;
+                    _missingConfigurations.erase(recordName);
+                }
+            });
+
+        // parse out dbus probes by discarding other probe types, store in a
+        // map
+        for (const nlohmann::json& probeJson : probeCommand)
+        {
+            const std::string* probe = probeJson.get_ptr<const std::string*>();
+            if (probe == nullptr)
+            {
+                std::cerr << "Probe statement wasn't a string, can't parse";
+                continue;
+            }
+            bool found = false;
+            boost::container::flat_map<const char*, probe_type_codes,
+                                       CmpStr>::const_iterator probeType;
+            for (probeType = probeTypes.begin(); probeType != probeTypes.end();
+                 ++probeType)
+            {
+                if (probe->find(probeType->first) != std::string::npos)
+                {
+                    found = true;
+                    break;
+                }
+            }
+            if (found)
+            {
+                continue;
+            }
+            // syntax requires probe before first open brace
+            auto findStart = probe->find('(');
+            std::string interface = probe->substr(0, findStart);
+            dbusProbeInterfaces.emplace(interface);
+            dbusProbePointers.emplace_back(probePointer);
+        }
+        it++;
+    }
+
+    // probe vector stores a shared_ptr to each PerformProbe that cares
+    // about a dbus interface
+    findDbusObjects(std::move(dbusProbePointers),
+                    std::move(dbusProbeInterfaces), shared_from_this());
+    if constexpr (debug)
+    {
+        std::cerr << __func__ << " " << __LINE__ << "\n";
+    }
+}
+
+PerformScan::~PerformScan()
+{
+    if (_passed)
+    {
+        auto nextScan = std::make_shared<PerformScan>(
+            _systemConfiguration, _missingConfigurations, _configurations,
+            objServer, std::move(_callback));
+        nextScan->passedProbes = std::move(passedProbes);
+        nextScan->dbusProbeObjects = std::move(dbusProbeObjects);
+        nextScan->run();
+
+        if constexpr (debug)
+        {
+            std::cerr << __func__ << " " << __LINE__ << "\n";
+        }
+    }
+    else
+    {
+        _callback();
+
+        if constexpr (debug)
+        {
+            std::cerr << __func__ << " " << __LINE__ << "\n";
+        }
+    }
+}
diff --git a/src/meson.build b/src/meson.build
index 8a9811b..bab416c 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,6 +3,7 @@
 executable(
     'entity-manager',
     'EntityManager.cpp',
+    'PerformScan.cpp',
     'Overlay.cpp',
     'Utils.cpp',
     cpp_args: cpp_args + ['-DBOOST_ASIO_DISABLE_THREADS'],