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
Configuration()19 Configuration::Configuration()
20 {
21 loadConfigurations();
22 filterProbeInterfaces();
23 }
24
loadConfigurations()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.
deriveNewConfiguration(const nlohmann::json & oldConfiguration,nlohmann::json & newConfiguration)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.
validateJson(const nlohmann::json & schemaFile,const nlohmann::json & input)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.
filterProbeInterfaces()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
writeJsonFiles(const nlohmann::json & systemConfiguration)182 bool writeJsonFiles(const nlohmann::json& systemConfiguration)
183 {
184 if (!EM_CACHE_CONFIGURATION)
185 {
186 return true;
187 }
188
189 std::error_code ec;
190 std::filesystem::create_directory(configurationOutDir, ec);
191 if (ec)
192 {
193 return false;
194 }
195 std::ofstream output(currentConfiguration);
196 if (!output.good())
197 {
198 return false;
199 }
200 output << systemConfiguration.dump(4);
201 output.close();
202 return true;
203 }
204