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