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