treewide: comply with the OpenBMC style guidelines

The guidelines say cpp source code filenames should be lower_snake_case:
https://github.com/openbmc/docs/blob/master/cpp-style-and-conventions.md#files

Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: Ia04017b0eb9a65ce1303af5b6dc36e730410fd91
diff --git a/src/perform_scan.cpp b/src/perform_scan.cpp
new file mode 100644
index 0000000..959a819
--- /dev/null
+++ b/src/perform_scan.cpp
@@ -0,0 +1,689 @@
+/*
+// 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 perform_scan.cpp
+#include "entity_manager.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 entity_manager.cpp */
+extern std::shared_ptr<sdbusplus::asio::connection> systemBus;
+extern nlohmann::json lastJson;
+extern void
+    propertiesChangedCallback(nlohmann::json& systemConfiguration,
+                              sdbusplus::asio::object_server& objServer);
+
+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;
+
+struct DBusInterfaceInstance
+{
+    std::string busName;
+    std::string path;
+    std::string interface;
+};
+
+void getInterfaces(
+    const DBusInterfaceInstance& instance,
+    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 " << instance.busName << " "
+                  << instance.path << " " << instance.interface << "\n";
+        return;
+    }
+
+    systemBus->async_method_call(
+        [instance, scan, probeVector, retries](boost::system::error_code& errc,
+                                               const DBusInterface& resp) {
+            if (errc)
+            {
+                std::cerr << "error calling getall on  " << instance.busName
+                          << " " << instance.path << " "
+                          << instance.interface << "\n";
+
+                auto timer = std::make_shared<boost::asio::steady_timer>(io);
+                timer->expires_after(std::chrono::seconds(2));
+
+                timer->async_wait([timer, instance, scan, probeVector,
+                                   retries](const boost::system::error_code&) {
+                    getInterfaces(instance, probeVector, scan, retries - 1);
+                });
+                return;
+            }
+
+            scan->dbusProbeObjects[instance.path][instance.interface] = resp;
+        },
+        instance.busName, instance.path, "org.freedesktop.DBus.Properties",
+        "GetAll", instance.interface);
+
+    if constexpr (debug)
+    {
+        std::cerr << __func__ << " " << __LINE__ << "\n";
+    }
+}
+
+static 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));
+}
+
+static void
+    processDbusObjects(std::vector<std::shared_ptr<PerformProbe>>& probeVector,
+                       const std::shared_ptr<PerformScan>& scan,
+                       const GetSubTreeType& interfaceSubtree)
+{
+    for (const auto& [path, object] : interfaceSubtree)
+    {
+        // Get a PropertiesChanged callback for all interfaces on this path.
+        registerCallback(scan->_systemConfiguration, scan->objServer, path);
+
+        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"))
+                {
+                    getInterfaces({busname, path, iface}, probeVector, scan);
+                }
+            }
+        }
+    }
+}
+
+// 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 {
+            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);
+                }
+
+                auto 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;
+            }
+
+            processDbusObjects(probeVector, scan, interfaceSubtree);
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", maxMapperDepth,
+        interfaces);
+
+    if constexpr (debug)
+    {
+        std::cerr << __func__ << " " << __LINE__ << "\n";
+    }
+}
+
+static std::string getRecordName(const DBusInterface& 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);
+    }
+
+    // hashes are hard to distinguish, use the non-hashed version if we want
+    // debug
+    if constexpr (debug)
+    {
+        return probeName + device.dump();
+    }
+
+    return std::to_string(std::hash<std::string>{}(probeName + device.dump()));
+}
+
+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))
+{}
+
+static void pruneRecordExposes(nlohmann::json& record)
+{
+    auto findExposes = record.find("Exposes");
+    if (findExposes == record.end())
+    {
+        return;
+    }
+
+    auto copy = nlohmann::json::array();
+    for (auto& expose : *findExposes)
+    {
+        if (!expose.is_null())
+        {
+            copy.emplace_back(expose);
+        }
+    }
+    *findExposes = copy;
+}
+
+static void recordDiscoveredIdentifiers(std::set<nlohmann::json>& usedNames,
+                                        std::list<size_t>& indexes,
+                                        const std::string& probeName,
+                                        const nlohmann::json& record)
+{
+    size_t indexIdx = probeName.find('$');
+    if (indexIdx == std::string::npos)
+    {
+        return;
+    }
+
+    auto nameIt = record.find("Name");
+    if (nameIt == record.end())
+    {
+        std::cerr << "Last JSON Illegal\n";
+        return;
+    }
+
+    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())
+    {
+        return; // non-numeric replacement
+    }
+
+    usedNames.insert(nameIt.value());
+
+    auto usedIt = std::find(indexes.begin(), indexes.end(), index);
+    if (usedIt != indexes.end())
+    {
+        indexes.erase(usedIt);
+    }
+}
+
+static bool extractExposeActionRecordNames(std::vector<std::string>& matches,
+                                           nlohmann::json::iterator& keyPair)
+{
+    if (keyPair.value().is_string())
+    {
+        matches.emplace_back(keyPair.value());
+        return true;
+    }
+
+    if (keyPair.value().is_array())
+    {
+        for (const auto& value : keyPair.value())
+        {
+            if (!value.is_string())
+            {
+                std::cerr << "Value is invalid type " << value << "\n";
+                break;
+            }
+            matches.emplace_back(value);
+        }
+
+        return true;
+    }
+
+    std::cerr << "Value is invalid type " << keyPair.key() << "\n";
+
+    return false;
+}
+
+static std::optional<std::vector<std::string>::iterator>
+    findExposeActionRecord(std::vector<std::string>& matches,
+                           const nlohmann::json& record)
+{
+    const auto& name = (record)["Name"].get_ref<const std::string&>();
+    auto compare = [&name](const std::string& s) { return s == name; };
+    auto matchIt = std::find_if(matches.begin(), matches.end(), compare);
+
+    if (matchIt == matches.end())
+    {
+        return std::nullopt;
+    }
+
+    return matchIt;
+}
+
+static void applyBindExposeAction(nlohmann::json& exposedObject,
+                                  nlohmann::json& expose,
+                                  const std::string& propertyName)
+{
+    if (boost::starts_with(propertyName, "Bind"))
+    {
+        std::string bind = propertyName.substr(sizeof("Bind") - 1);
+        exposedObject["Status"] = "okay";
+        expose[bind] = exposedObject;
+    }
+}
+
+static void applyDisableExposeAction(nlohmann::json& exposedObject,
+                                     const std::string& propertyName)
+{
+    if (propertyName == "DisableNode")
+    {
+        exposedObject["Status"] = "disabled";
+    }
+}
+
+static void applyConfigExposeActions(std::vector<std::string>& matches,
+                                     nlohmann::json& expose,
+                                     const std::string& propertyName,
+                                     nlohmann::json& configExposes)
+{
+    for (auto& exposedObject : configExposes)
+    {
+        auto match = findExposeActionRecord(matches, exposedObject);
+        if (match)
+        {
+            matches.erase(*match);
+            applyBindExposeAction(exposedObject, expose, propertyName);
+            applyDisableExposeAction(exposedObject, propertyName);
+        }
+    }
+}
+
+static void applyExposeActions(nlohmann::json& systemConfiguration,
+                               const std::string& recordName,
+                               nlohmann::json& expose,
+                               nlohmann::json::iterator& keyPair)
+{
+    bool isBind = boost::starts_with(keyPair.key(), "Bind");
+    bool isDisable = keyPair.key() == "DisableNode";
+    bool isExposeAction = isBind || isDisable;
+
+    if (!isExposeAction)
+    {
+        return;
+    }
+
+    std::vector<std::string> matches;
+
+    if (!extractExposeActionRecordNames(matches, keyPair))
+    {
+        return;
+    }
+
+    for (auto& [configId, config] : systemConfiguration.items())
+    {
+        // don't disable ourselves
+        if (isDisable && configId == recordName)
+        {
+            continue;
+        }
+
+        auto configListFind = config.find("Exposes");
+        if (configListFind == config.end())
+        {
+            continue;
+        }
+
+        if (!configListFind->is_array())
+        {
+            continue;
+        }
+
+        applyConfigExposeActions(matches, expose, keyPair.key(),
+                                 *configListFind);
+    }
+
+    if (!matches.empty())
+    {
+        std::cerr << "configuration file dependency error, could not find "
+                  << keyPair.key() << " " << keyPair.value() << "\n";
+    }
+}
+
+static std::string generateDeviceName(const std::set<nlohmann::json>& usedNames,
+                                      const DBusObject& dbusObject,
+                                      size_t foundDeviceIdx,
+                                      const std::string& nameTemplate,
+                                      std::optional<std::string>& replaceStr)
+{
+    nlohmann::json copyForName = {{"Name", nameTemplate}};
+    nlohmann::json::iterator copyIt = copyForName.begin();
+    std::optional<std::string> replaceVal =
+        templateCharReplace(copyIt, dbusObject, foundDeviceIdx, replaceStr);
+
+    if (!replaceStr && replaceVal)
+    {
+        if (usedNames.find(copyIt.value()) != usedNames.end())
+        {
+            replaceStr = replaceVal;
+            copyForName = {{"Name", nameTemplate}};
+            copyIt = copyForName.begin();
+            templateCharReplace(copyIt, dbusObject, foundDeviceIdx, replaceStr);
+        }
+    }
+
+    if (replaceStr)
+    {
+        std::cerr << "Duplicates found, replacing " << *replaceStr
+                  << " with found device index.\n Consider "
+                     "fixing template to not have duplicates\n";
+    }
+
+    return copyIt.value();
+}
+
+void PerformScan::updateSystemConfiguration(const nlohmann::json& recordRef,
+                                            const std::string& probeName,
+                                            FoundDevices& foundDevices)
+{
+    _passed = true;
+    passedProbes.push_back(probeName);
+
+    std::set<nlohmann::json> usedNames;
+    std::list<size_t> indexes(foundDevices.size());
+    std::iota(indexes.begin(), indexes.end(), 1);
+
+    // 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(itr->interface, probeName);
+
+        auto record = lastJson.find(recordName);
+        if (record == lastJson.end())
+        {
+            itr++;
+            continue;
+        }
+
+        pruneRecordExposes(*record);
+
+        recordDiscoveredIdentifiers(usedNames, indexes, probeName, *record);
+
+        // keep user changes
+        _systemConfiguration[recordName] = *record;
+        _missingConfigurations.erase(recordName);
+
+        // We've processed the device, remove it and advance the
+        // iterator
+        itr = foundDevices.erase(itr);
+    }
+
+    std::optional<std::string> replaceStr;
+
+    DBusObject emptyObject;
+    DBusInterface emptyInterface;
+    emptyObject.emplace(std::string{}, emptyInterface);
+
+    for (const auto& [foundDevice, path] : foundDevices)
+    {
+        // 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.
+        auto objectIt = dbusProbeObjects.find(path);
+        const DBusObject& dbusObject = (objectIt == dbusProbeObjects.end())
+                                           ? emptyObject
+                                           : objectIt->second;
+
+        nlohmann::json record = recordRef;
+        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
+        }
+
+        std::string deviceName = generateDeviceName(
+            usedNames, dbusObject, foundDeviceIdx, getName.value(), replaceStr);
+        getName.value() = deviceName;
+        usedNames.insert(deviceName);
+
+        for (auto keyPair = record.begin(); keyPair != record.end(); keyPair++)
+        {
+            if (keyPair.key() != "Name")
+            {
+                templateCharReplace(keyPair, dbusObject, foundDeviceIdx,
+                                    replaceStr);
+            }
+        }
+
+        // insert into configuration temporarily to be able to
+        // reference ourselves
+
+        _systemConfiguration[recordName] = record;
+
+        auto findExpose = record.find("Exposes");
+        if (findExpose == record.end())
+        {
+            continue;
+        }
+
+        for (auto& expose : *findExpose)
+        {
+            for (auto keyPair = expose.begin(); keyPair != expose.end();
+                 keyPair++)
+            {
+
+                templateCharReplace(keyPair, dbusObject, foundDeviceIdx,
+                                    replaceStr);
+
+                applyExposeActions(_systemConfiguration, recordName, expose,
+                                   keyPair);
+            }
+        }
+
+        // overwrite ourselves with cleaned up version
+        _systemConfiguration[recordName] = record;
+        _missingConfigurations.erase(recordName);
+    }
+}
+
+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();)
+    {
+        // check for poorly formatted fields, probe must be an array
+        auto findProbe = it->find("Probe");
+        if (findProbe == it->end())
+        {
+            std::cerr << "configuration file missing probe:\n " << *it << "\n";
+            it = _configurations.erase(it);
+            continue;
+        }
+
+        auto findName = it->find("Name");
+        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& recordRef = *it;
+        nlohmann::json probeCommand;
+        if ((*findProbe).type() != nlohmann::json::value_t::array)
+        {
+            probeCommand = nlohmann::json::array();
+            probeCommand.push_back(*findProbe);
+        }
+        else
+        {
+            probeCommand = *findProbe;
+        }
+
+        // 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>(
+            recordRef, probeCommand, probeName, thisRef);
+
+        // 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;
+            }
+            if (findProbeType(probe->c_str()))
+            {
+                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";
+        }
+    }
+}