1 #include "configuration.hpp" 2 3 #include "perform_probe.hpp" 4 #include "utils.hpp" 5 6 #include <nlohmann/json.hpp> 7 #include <phosphor-logging/lg2.hpp> 8 #include <valijson/adapters/nlohmann_json_adapter.hpp> 9 #include <valijson/schema.hpp> 10 #include <valijson/schema_parser.hpp> 11 #include <valijson/validator.hpp> 12 13 #include <filesystem> 14 #include <fstream> 15 #include <string> 16 #include <vector> 17 18 Configuration::Configuration( 19 const std::vector<std::filesystem::path>& configurationDirectories) : 20 configurationDirectories(configurationDirectories) 21 { 22 loadConfigurations(); 23 filterProbeInterfaces(); 24 } 25 26 void Configuration::loadConfigurations() 27 { 28 const auto start = std::chrono::steady_clock::now(); 29 30 // find configuration files 31 std::vector<std::filesystem::path> jsonPaths; 32 if (!findFiles(configurationDirectories, R"(.*\.json)", jsonPaths)) 33 { 34 for (const auto& configurationDirectory : configurationDirectories) 35 { 36 lg2::error("Unable to find any configuration files in {DIR}", "DIR", 37 configurationDirectory); 38 } 39 return; 40 } 41 42 std::ifstream schemaStream( 43 std::string(schemaDirectory) + "/" + globalSchema); 44 if (!schemaStream.good()) 45 { 46 lg2::error("Cannot open schema file, cannot validate JSON, exiting"); 47 std::exit(EXIT_FAILURE); 48 return; 49 } 50 nlohmann::json schema = 51 nlohmann::json::parse(schemaStream, nullptr, false, true); 52 if (schema.is_discarded()) 53 { 54 lg2::error( 55 "Illegal schema file detected, cannot validate JSON, exiting"); 56 std::exit(EXIT_FAILURE); 57 return; 58 } 59 60 for (auto& jsonPath : jsonPaths) 61 { 62 std::ifstream jsonStream(jsonPath.c_str()); 63 if (!jsonStream.good()) 64 { 65 lg2::error("unable to open {PATH}", "PATH", jsonPath.string()); 66 continue; 67 } 68 auto data = nlohmann::json::parse(jsonStream, nullptr, false, true); 69 if (data.is_discarded()) 70 { 71 lg2::error("syntax error in {PATH}", "PATH", jsonPath.string()); 72 continue; 73 } 74 75 if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data)) 76 { 77 lg2::error("Error validating {PATH}", "PATH", jsonPath.string()); 78 continue; 79 } 80 81 if (data.type() == nlohmann::json::value_t::array) 82 { 83 for (auto& d : data) 84 { 85 configurations.emplace_back(d); 86 } 87 } 88 else 89 { 90 configurations.emplace_back(data); 91 } 92 } 93 94 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( 95 std::chrono::steady_clock::now() - start) 96 .count(); 97 98 lg2::debug("Finished loading json configuration in {MILLIS}ms", "MILLIS", 99 duration); 100 } 101 102 // Iterate over new configuration and erase items from old configuration. 103 void deriveNewConfiguration(const nlohmann::json& oldConfiguration, 104 nlohmann::json& newConfiguration) 105 { 106 for (auto it = newConfiguration.begin(); it != newConfiguration.end();) 107 { 108 auto findKey = oldConfiguration.find(it.key()); 109 if (findKey != oldConfiguration.end()) 110 { 111 it = newConfiguration.erase(it); 112 } 113 else 114 { 115 it++; 116 } 117 } 118 } 119 120 // validates a given input(configuration) with a given json schema file. 121 bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input) 122 { 123 valijson::Schema schema; 124 valijson::SchemaParser parser; 125 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile); 126 parser.populateSchema(schemaAdapter, schema); 127 valijson::Validator validator; 128 valijson::adapters::NlohmannJsonAdapter targetAdapter(input); 129 return validator.validate(schema, targetAdapter, nullptr); 130 } 131 132 // Extract the D-Bus interfaces to probe from the JSON config files. 133 void Configuration::filterProbeInterfaces() 134 { 135 for (auto it = configurations.begin(); it != configurations.end();) 136 { 137 auto findProbe = it->find("Probe"); 138 if (findProbe == it->end()) 139 { 140 lg2::error("configuration file missing probe: {PROBE}", "PROBE", 141 *it); 142 it++; 143 continue; 144 } 145 146 nlohmann::json probeCommand; 147 if ((*findProbe).type() != nlohmann::json::value_t::array) 148 { 149 probeCommand = nlohmann::json::array(); 150 probeCommand.push_back(*findProbe); 151 } 152 else 153 { 154 probeCommand = *findProbe; 155 } 156 157 for (const nlohmann::json& probeJson : probeCommand) 158 { 159 const std::string* probe = probeJson.get_ptr<const std::string*>(); 160 if (probe == nullptr) 161 { 162 lg2::error("Probe statement wasn't a string, can't parse"); 163 continue; 164 } 165 // Skip it if the probe cmd doesn't contain an interface. 166 if (probe::findProbeType(*probe)) 167 { 168 continue; 169 } 170 171 // syntax requires probe before first open brace 172 auto findStart = probe->find('('); 173 if (findStart != std::string::npos) 174 { 175 std::string interface = probe->substr(0, findStart); 176 probeInterfaces.emplace(interface); 177 } 178 } 179 it++; 180 } 181 } 182 183 bool writeJsonFiles(const nlohmann::json& systemConfiguration) 184 { 185 if (!EM_CACHE_CONFIGURATION) 186 { 187 return true; 188 } 189 190 std::error_code ec; 191 std::filesystem::create_directory(configurationOutDir, ec); 192 if (ec) 193 { 194 return false; 195 } 196 std::ofstream output(currentConfiguration); 197 if (!output.good()) 198 { 199 return false; 200 } 201 output << systemConfiguration.dump(4); 202 output.close(); 203 return true; 204 } 205