blob: b6ece2b2fd83c9194487d11b9acdca3455e2b457 [file] [log] [blame]
Christopher Meisbdaa6b22025-04-02 10:49:02 +02001#include "configuration.hpp"
2
3#include "perform_probe.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +02004#include "utils.hpp"
Christopher Meisbdaa6b22025-04-02 10:49:02 +02005
6#include <nlohmann/json.hpp>
Alexander Hansen8feb0452025-09-15 14:29:20 +02007#include <phosphor-logging/lg2.hpp>
Christopher Meisbdaa6b22025-04-02 10:49:02 +02008#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>
Christopher Meisbdaa6b22025-04-02 10:49:02 +020015#include <string>
16#include <vector>
17
Christopher Meisf7252572025-06-11 13:22:05 +020018Configuration::Configuration()
Christopher Meisbdaa6b22025-04-02 10:49:02 +020019{
Christopher Meisf7252572025-06-11 13:22:05 +020020 loadConfigurations();
21 filterProbeInterfaces();
Christopher Meisbdaa6b22025-04-02 10:49:02 +020022}
23
Christopher Meisf7252572025-06-11 13:22:05 +020024void Configuration::loadConfigurations()
Christopher Meisbdaa6b22025-04-02 10:49:02 +020025{
Alexander Hansenee9a3872025-06-13 13:48:03 +020026 const auto start = std::chrono::steady_clock::now();
27
Christopher Meisbdaa6b22025-04-02 10:49:02 +020028 // find configuration files
29 std::vector<std::filesystem::path> jsonPaths;
30 if (!findFiles(
31 std::vector<std::filesystem::path>{configurationDirectory,
32 hostConfigurationDirectory},
33 R"(.*\.json)", jsonPaths))
34 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020035 lg2::error("Unable to find any configuration files in {DIR}", "DIR",
36 configurationDirectory);
Christopher Meisf7252572025-06-11 13:22:05 +020037 return;
Christopher Meisbdaa6b22025-04-02 10:49:02 +020038 }
39
40 std::ifstream schemaStream(
41 std::string(schemaDirectory) + "/" + globalSchema);
42 if (!schemaStream.good())
43 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020044 lg2::error("Cannot open schema file, cannot validate JSON, exiting");
Christopher Meisbdaa6b22025-04-02 10:49:02 +020045 std::exit(EXIT_FAILURE);
Christopher Meisf7252572025-06-11 13:22:05 +020046 return;
Christopher Meisbdaa6b22025-04-02 10:49:02 +020047 }
48 nlohmann::json schema =
49 nlohmann::json::parse(schemaStream, nullptr, false, true);
50 if (schema.is_discarded())
51 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020052 lg2::error(
53 "Illegal schema file detected, cannot validate JSON, exiting");
Christopher Meisbdaa6b22025-04-02 10:49:02 +020054 std::exit(EXIT_FAILURE);
Christopher Meisf7252572025-06-11 13:22:05 +020055 return;
Christopher Meisbdaa6b22025-04-02 10:49:02 +020056 }
57
58 for (auto& jsonPath : jsonPaths)
59 {
60 std::ifstream jsonStream(jsonPath.c_str());
61 if (!jsonStream.good())
62 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020063 lg2::error("unable to open {PATH}", "PATH", jsonPath.string());
Christopher Meisbdaa6b22025-04-02 10:49:02 +020064 continue;
65 }
66 auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
67 if (data.is_discarded())
68 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020069 lg2::error("syntax error in {PATH}", "PATH", jsonPath.string());
Christopher Meisbdaa6b22025-04-02 10:49:02 +020070 continue;
71 }
Alexander Hansen7f51d322025-06-25 12:26:09 +020072
73 if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data))
Christopher Meisbdaa6b22025-04-02 10:49:02 +020074 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020075 lg2::error("Error validating {PATH}", "PATH", jsonPath.string());
Christopher Meisbdaa6b22025-04-02 10:49:02 +020076 continue;
77 }
Christopher Meisbdaa6b22025-04-02 10:49:02 +020078
79 if (data.type() == nlohmann::json::value_t::array)
80 {
81 for (auto& d : data)
82 {
83 configurations.emplace_back(d);
84 }
85 }
86 else
87 {
88 configurations.emplace_back(data);
89 }
90 }
Alexander Hansenee9a3872025-06-13 13:48:03 +020091
92 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
93 std::chrono::steady_clock::now() - start)
94 .count();
95
96 lg2::debug("Finished loading json configuration in {MILLIS}ms", "MILLIS",
97 duration);
Christopher Meisbdaa6b22025-04-02 10:49:02 +020098}
99
100// Iterate over new configuration and erase items from old configuration.
101void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
102 nlohmann::json& newConfiguration)
103{
104 for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
105 {
106 auto findKey = oldConfiguration.find(it.key());
107 if (findKey != oldConfiguration.end())
108 {
109 it = newConfiguration.erase(it);
110 }
111 else
112 {
113 it++;
114 }
115 }
116}
117
118// validates a given input(configuration) with a given json schema file.
119bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
120{
121 valijson::Schema schema;
122 valijson::SchemaParser parser;
123 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
124 parser.populateSchema(schemaAdapter, schema);
125 valijson::Validator validator;
126 valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
127 return validator.validate(schema, targetAdapter, nullptr);
128}
129
130// Extract the D-Bus interfaces to probe from the JSON config files.
Christopher Meisf7252572025-06-11 13:22:05 +0200131void Configuration::filterProbeInterfaces()
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200132{
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200133 for (auto it = configurations.begin(); it != configurations.end();)
134 {
135 auto findProbe = it->find("Probe");
136 if (findProbe == it->end())
137 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200138 lg2::error("configuration file missing probe: {PROBE}", "PROBE",
139 *it);
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200140 it++;
141 continue;
142 }
143
144 nlohmann::json probeCommand;
145 if ((*findProbe).type() != nlohmann::json::value_t::array)
146 {
147 probeCommand = nlohmann::json::array();
148 probeCommand.push_back(*findProbe);
149 }
150 else
151 {
152 probeCommand = *findProbe;
153 }
154
155 for (const nlohmann::json& probeJson : probeCommand)
156 {
157 const std::string* probe = probeJson.get_ptr<const std::string*>();
158 if (probe == nullptr)
159 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200160 lg2::error("Probe statement wasn't a string, can't parse");
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200161 continue;
162 }
163 // Skip it if the probe cmd doesn't contain an interface.
164 if (probe::findProbeType(*probe))
165 {
166 continue;
167 }
168
169 // syntax requires probe before first open brace
170 auto findStart = probe->find('(');
171 if (findStart != std::string::npos)
172 {
173 std::string interface = probe->substr(0, findStart);
Christopher Meisf7252572025-06-11 13:22:05 +0200174 probeInterfaces.emplace(interface);
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200175 }
176 }
177 it++;
178 }
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200179}
180
Christopher Meisf7252572025-06-11 13:22:05 +0200181bool writeJsonFiles(const nlohmann::json& systemConfiguration)
182{
Alexander Hansene6651852025-01-21 16:22:05 +0100183 if (!EM_CACHE_CONFIGURATION)
184 {
185 return true;
186 }
187
Alexander Hansenb3609912025-08-04 16:38:52 +0200188 std::error_code ec;
189 std::filesystem::create_directory(configurationOutDir, ec);
190 if (ec)
191 {
192 return false;
193 }
Christopher Meisf7252572025-06-11 13:22:05 +0200194 std::ofstream output(currentConfiguration);
195 if (!output.good())
196 {
197 return false;
198 }
199 output << systemConfiguration.dump(4);
200 output.close();
201 return true;
202}