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