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