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