1 #include "configuration.hpp" 2 3 #include "perform_probe.hpp" 4 #include "phosphor-logging/lg2.hpp" 5 #include "utils.hpp" 6 7 #include <nlohmann/json.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 <iostream> 16 #include <string> 17 #include <vector> 18 19 Configuration::Configuration() 20 { 21 loadConfigurations(); 22 filterProbeInterfaces(); 23 } 24 25 void Configuration::loadConfigurations() 26 { 27 const auto start = std::chrono::steady_clock::now(); 28 29 // find configuration files 30 std::vector<std::filesystem::path> jsonPaths; 31 if (!findFiles( 32 std::vector<std::filesystem::path>{configurationDirectory, 33 hostConfigurationDirectory}, 34 R"(.*\.json)", jsonPaths)) 35 { 36 std::cerr << "Unable to find any configuration files in " 37 << configurationDirectory << "\n"; 38 return; 39 } 40 41 std::ifstream schemaStream( 42 std::string(schemaDirectory) + "/" + globalSchema); 43 if (!schemaStream.good()) 44 { 45 std::cerr 46 << "Cannot open schema file, cannot validate JSON, exiting\n\n"; 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 std::cerr 55 << "Illegal schema file detected, cannot validate JSON, exiting\n"; 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 std::cerr << "unable to open " << jsonPath.string() << "\n"; 66 continue; 67 } 68 auto data = nlohmann::json::parse(jsonStream, nullptr, false, true); 69 if (data.is_discarded()) 70 { 71 std::cerr << "syntax error in " << jsonPath.string() << "\n"; 72 continue; 73 } 74 75 if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data)) 76 { 77 std::cerr << "Error validating " << jsonPath.string() << "\n"; 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 std::cerr << "configuration file missing probe:\n " << *it << "\n"; 141 it++; 142 continue; 143 } 144 145 nlohmann::json probeCommand; 146 if ((*findProbe).type() != nlohmann::json::value_t::array) 147 { 148 probeCommand = nlohmann::json::array(); 149 probeCommand.push_back(*findProbe); 150 } 151 else 152 { 153 probeCommand = *findProbe; 154 } 155 156 for (const nlohmann::json& probeJson : probeCommand) 157 { 158 const std::string* probe = probeJson.get_ptr<const std::string*>(); 159 if (probe == nullptr) 160 { 161 std::cerr << "Probe statement wasn't a string, can't parse"; 162 continue; 163 } 164 // Skip it if the probe cmd doesn't contain an interface. 165 if (probe::findProbeType(*probe)) 166 { 167 continue; 168 } 169 170 // syntax requires probe before first open brace 171 auto findStart = probe->find('('); 172 if (findStart != std::string::npos) 173 { 174 std::string interface = probe->substr(0, findStart); 175 probeInterfaces.emplace(interface); 176 } 177 } 178 it++; 179 } 180 } 181 182 bool writeJsonFiles(const nlohmann::json& systemConfiguration) 183 { 184 std::filesystem::create_directory(configurationOutDir); 185 std::ofstream output(currentConfiguration); 186 if (!output.good()) 187 { 188 return false; 189 } 190 output << systemConfiguration.dump(4); 191 output.close(); 192 return true; 193 } 194