cleanup: move json configuration functions in their own files

Improving maintainability by increasing 'separation of concern'.

Change-Id: I80355abf741c037f8834527e3304c80b83c8166e
Signed-off-by: Christopher Meis <christopher.meis@9elements.com>
diff --git a/src/entity_manager.cpp b/src/entity_manager.cpp
index 187373a..e4e458e 100644
--- a/src/entity_manager.cpp
+++ b/src/entity_manager.cpp
@@ -17,8 +17,8 @@
 
 #include "entity_manager.hpp"
 
+#include "configuration.hpp"
 #include "overlay.hpp"
-#include "perform_probe.hpp"
 #include "perform_scan.hpp"
 #include "topology.hpp"
 #include "utils.hpp"
@@ -47,13 +47,8 @@
 #include <map>
 #include <regex>
 #include <variant>
-constexpr const char* hostConfigurationDirectory = SYSCONF_DIR "configurations";
-constexpr const char* configurationDirectory = PACKAGE_DIR "configurations";
-constexpr const char* schemaDirectory = PACKAGE_DIR "configurations/schemas";
 constexpr const char* tempConfigDir = "/tmp/configuration/";
 constexpr const char* lastConfiguration = "/tmp/configuration/last.json";
-constexpr const char* currentConfiguration = "/var/configuration/system.json";
-constexpr const char* globalSchema = "global.json";
 
 static constexpr std::array<const char*, 6> settableInterfaces = {
     "FanProfile", "Pid", "Pid.Zone", "Stepwise", "Thresholds", "Polling"};
@@ -119,37 +114,6 @@
     return ptr;
 }
 
-// writes output files to persist data
-bool writeJsonFiles(const nlohmann::json& systemConfiguration)
-{
-    std::filesystem::create_directory(configurationOutDir);
-    std::ofstream output(currentConfiguration);
-    if (!output.good())
-    {
-        return false;
-    }
-    output << systemConfiguration.dump(4);
-    output.close();
-    return true;
-}
-
-template <typename JsonType>
-bool setJsonFromPointer(const std::string& ptrStr, const JsonType& value,
-                        nlohmann::json& systemConfiguration)
-{
-    try
-    {
-        nlohmann::json::json_pointer ptr(ptrStr);
-        nlohmann::json& ref = systemConfiguration[ptr];
-        ref = value;
-        return true;
-    }
-    catch (const std::out_of_range&)
-    {
-        return false;
-    }
-}
-
 // template function to add array as dbus property
 template <typename PropertyType>
 void addArrayToDbus(const std::string& name, const nlohmann::json& array,
@@ -181,13 +145,13 @@
                 const std::vector<PropertyType>& newVal,
                 std::vector<PropertyType>& val) {
                 val = newVal;
-                if (!setJsonFromPointer(jsonPointerString, val,
-                                        systemConfiguration))
+                if (!configuration::setJsonFromPointer(jsonPointerString, val,
+                                                       systemConfiguration))
                 {
                     std::cerr << "error setting json field\n";
                     return -1;
                 }
-                if (!writeJsonFiles(systemConfiguration))
+                if (!configuration::writeJsonFiles(systemConfiguration))
                 {
                     std::cerr << "error setting json file\n";
                     return -1;
@@ -215,13 +179,13 @@
          jsonPointerString{std::string(jsonPointerString)}](
             const PropertyType& newVal, PropertyType& val) {
             val = newVal;
-            if (!setJsonFromPointer(jsonPointerString, val,
-                                    systemConfiguration))
+            if (!configuration::setJsonFromPointer(jsonPointerString, val,
+                                                   systemConfiguration))
             {
                 std::cerr << "error setting json field\n";
                 return -1;
             }
-            if (!writeJsonFiles(systemConfiguration))
+            if (!configuration::writeJsonFiles(systemConfiguration))
             {
                 std::cerr << "error setting json file\n";
                 return -1;
@@ -257,7 +221,7 @@
                 objServer.remove_interface(dbusInterface);
             });
 
-            if (!writeJsonFiles(systemConfiguration))
+            if (!configuration::writeJsonFiles(systemConfiguration))
             {
                 std::cerr << "error setting json file\n";
                 throw DBusInternalError();
@@ -503,8 +467,9 @@
                 lastIndex++;
             }
 
-            std::ifstream schemaFile(std::string(schemaDirectory) + "/" +
-                                     boost::to_lower_copy(*type) + ".json");
+            std::ifstream schemaFile(
+                std::string(configuration::schemaDirectory) + "/" +
+                boost::to_lower_copy(*type) + ".json");
             // todo(james) we might want to also make a list of 'can add'
             // interfaces but for now I think the assumption if there is a
             // schema avaliable that it is allowed to update is fine
@@ -520,7 +485,7 @@
                 std::cerr << "Schema not legal" << *type << ".json\n";
                 throw DBusInternalError();
             }
-            if (!validateJson(schema, newData))
+            if (!configuration::validateJson(schema, newData))
             {
                 throw std::invalid_argument("Data does not match schema");
             }
@@ -532,7 +497,7 @@
             {
                 findExposes->push_back(newData);
             }
-            if (!writeJsonFiles(systemConfiguration))
+            if (!configuration::writeJsonFiles(systemConfiguration))
             {
                 std::cerr << "Error writing json files\n";
                 throw DBusInternalError();
@@ -802,79 +767,6 @@
     }
 }
 
-// reads json files out of the filesystem
-bool loadConfigurations(std::list<nlohmann::json>& configurations)
-{
-    // find configuration files
-    std::vector<std::filesystem::path> jsonPaths;
-    if (!findFiles(
-            std::vector<std::filesystem::path>{configurationDirectory,
-                                               hostConfigurationDirectory},
-            R"(.*\.json)", jsonPaths))
-    {
-        std::cerr << "Unable to find any configuration files in "
-                  << configurationDirectory << "\n";
-        return false;
-    }
-
-    std::ifstream schemaStream(
-        std::string(schemaDirectory) + "/" + globalSchema);
-    if (!schemaStream.good())
-    {
-        std::cerr
-            << "Cannot open schema file,  cannot validate JSON, exiting\n\n";
-        std::exit(EXIT_FAILURE);
-        return false;
-    }
-    nlohmann::json schema =
-        nlohmann::json::parse(schemaStream, nullptr, false, true);
-    if (schema.is_discarded())
-    {
-        std::cerr
-            << "Illegal schema file detected, cannot validate JSON, exiting\n";
-        std::exit(EXIT_FAILURE);
-        return false;
-    }
-
-    for (auto& jsonPath : jsonPaths)
-    {
-        std::ifstream jsonStream(jsonPath.c_str());
-        if (!jsonStream.good())
-        {
-            std::cerr << "unable to open " << jsonPath.string() << "\n";
-            continue;
-        }
-        auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
-        if (data.is_discarded())
-        {
-            std::cerr << "syntax error in " << jsonPath.string() << "\n";
-            continue;
-        }
-        /*
-         * todo(james): reenable this once less things are in flight
-         *
-        if (!validateJson(schema, data))
-        {
-            std::cerr << "Error validating " << jsonPath.string() << "\n";
-            continue;
-        }
-        */
-
-        if (data.type() == nlohmann::json::value_t::array)
-        {
-            for (auto& d : data)
-            {
-                configurations.emplace_back(d);
-            }
-        }
-        else
-        {
-            configurations.emplace_back(data);
-        }
-    }
-    return true;
-}
-
 static bool deviceRequiresPowerOn(const nlohmann::json& entity)
 {
     auto powerState = entity.find("PowerState");
@@ -985,23 +877,6 @@
     logDeviceRemoved(device);
 }
 
-static void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
-                                   nlohmann::json& newConfiguration)
-{
-    for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
-    {
-        auto findKey = oldConfiguration.find(it.key());
-        if (findKey != oldConfiguration.end())
-        {
-            it = newConfiguration.erase(it);
-        }
-        else
-        {
-            it++;
-        }
-    }
-}
-
 static void publishNewConfiguration(
     const size_t& instance, const size_t count,
     boost::asio::steady_timer& timer, nlohmann::json& systemConfiguration,
@@ -1018,7 +893,7 @@
     loadOverlays(newConfiguration);
 
     boost::asio::post(io, [systemConfiguration]() {
-        if (!writeJsonFiles(systemConfiguration))
+        if (!configuration::writeJsonFiles(systemConfiguration))
         {
             std::cerr << "Error writing json files\n";
         }
@@ -1072,7 +947,7 @@
         *missingConfigurations = systemConfiguration;
 
         std::list<nlohmann::json> configurations;
-        if (!loadConfigurations(configurations))
+        if (!configuration::loadConfigurations(configurations))
         {
             std::cerr << "Could not load configurations\n";
             inProgress = false;
@@ -1096,7 +971,8 @@
 
                 nlohmann::json newConfiguration = systemConfiguration;
 
-                deriveNewConfiguration(oldConfiguration, newConfiguration);
+                configuration::deriveNewConfiguration(oldConfiguration,
+                                                      newConfiguration);
 
                 for (const auto& [_, device] : newConfiguration.items())
                 {
@@ -1115,65 +991,6 @@
     });
 }
 
-// Extract the D-Bus interfaces to probe from the JSON config files.
-static std::set<std::string> getProbeInterfaces()
-{
-    std::set<std::string> interfaces;
-    std::list<nlohmann::json> configurations;
-    if (!loadConfigurations(configurations))
-    {
-        return interfaces;
-    }
-
-    for (auto it = configurations.begin(); it != configurations.end();)
-    {
-        auto findProbe = it->find("Probe");
-        if (findProbe == it->end())
-        {
-            std::cerr << "configuration file missing probe:\n " << *it << "\n";
-            it++;
-            continue;
-        }
-
-        nlohmann::json probeCommand;
-        if ((*findProbe).type() != nlohmann::json::value_t::array)
-        {
-            probeCommand = nlohmann::json::array();
-            probeCommand.push_back(*findProbe);
-        }
-        else
-        {
-            probeCommand = *findProbe;
-        }
-
-        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;
-            }
-            // Skip it if the probe cmd doesn't contain an interface.
-            if (probe::findProbeType(*probe))
-            {
-                continue;
-            }
-
-            // syntax requires probe before first open brace
-            auto findStart = probe->find('(');
-            if (findStart != std::string::npos)
-            {
-                std::string interface = probe->substr(0, findStart);
-                interfaces.emplace(interface);
-            }
-        }
-        it++;
-    }
-
-    return interfaces;
-}
-
 // Check if InterfacesAdded payload contains an iface that needs probing.
 static bool iaContainsProbeInterface(
     sdbusplus::message_t& msg, const std::set<std::string>& probeInterfaces)
@@ -1237,7 +1054,7 @@
 
     nlohmann::json systemConfiguration = nlohmann::json::object();
 
-    std::set<std::string> probeInterfaces = getProbeInterfaces();
+    std::set<std::string> probeInterfaces = configuration::getProbeInterfaces();
 
     // We need a poke from DBus for static providers that create all their
     // objects prior to claiming a well-known name, and thus don't emit any
@@ -1291,13 +1108,15 @@
 
     if (fwVersionIsSame())
     {
-        if (std::filesystem::is_regular_file(currentConfiguration))
+        if (std::filesystem::is_regular_file(
+                configuration::currentConfiguration))
         {
             // this file could just be deleted, but it's nice for debug
             std::filesystem::create_directory(tempConfigDir);
             std::filesystem::remove(lastConfiguration);
-            std::filesystem::copy(currentConfiguration, lastConfiguration);
-            std::filesystem::remove(currentConfiguration);
+            std::filesystem::copy(configuration::currentConfiguration,
+                                  lastConfiguration);
+            std::filesystem::remove(configuration::currentConfiguration);
 
             std::ifstream jsonStream(lastConfiguration);
             if (jsonStream.good())
@@ -1323,7 +1142,7 @@
     {
         // not an error, just logging at this level to make it in the journal
         std::cerr << "Clearing previous configuration\n";
-        std::filesystem::remove(currentConfiguration);
+        std::filesystem::remove(configuration::currentConfiguration);
     }
 
     // some boards only show up after power is on, we want to not say they are