blob: 99f7f666360a1503f9d200b361de268b69c47b58 [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
Alexander Hansen8290ca42025-08-04 15:27:22 +020018Configuration::Configuration(
19 const std::vector<std::filesystem::path>& configurationDirectories) :
20 configurationDirectories(configurationDirectories)
Christopher Meisbdaa6b22025-04-02 10:49:02 +020021{
Christopher Meisf7252572025-06-11 13:22:05 +020022 loadConfigurations();
23 filterProbeInterfaces();
Christopher Meisbdaa6b22025-04-02 10:49:02 +020024}
25
Christopher Meisf7252572025-06-11 13:22:05 +020026void Configuration::loadConfigurations()
Christopher Meisbdaa6b22025-04-02 10:49:02 +020027{
Alexander Hansenee9a3872025-06-13 13:48:03 +020028 const auto start = std::chrono::steady_clock::now();
29
Christopher Meisbdaa6b22025-04-02 10:49:02 +020030 // find configuration files
31 std::vector<std::filesystem::path> jsonPaths;
Alexander Hansen8290ca42025-08-04 15:27:22 +020032 if (!findFiles(configurationDirectories, R"(.*\.json)", jsonPaths))
Christopher Meisbdaa6b22025-04-02 10:49:02 +020033 {
Alexander Hansen8290ca42025-08-04 15:27:22 +020034 for (const auto& configurationDirectory : configurationDirectories)
35 {
36 lg2::error("Unable to find any configuration files in {DIR}", "DIR",
37 configurationDirectory);
38 }
Christopher Meisf7252572025-06-11 13:22:05 +020039 return;
Christopher Meisbdaa6b22025-04-02 10:49:02 +020040 }
41
42 std::ifstream schemaStream(
43 std::string(schemaDirectory) + "/" + globalSchema);
44 if (!schemaStream.good())
45 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020046 lg2::error("Cannot open schema file, cannot validate JSON, exiting");
Christopher Meisbdaa6b22025-04-02 10:49:02 +020047 std::exit(EXIT_FAILURE);
Christopher Meisf7252572025-06-11 13:22:05 +020048 return;
Christopher Meisbdaa6b22025-04-02 10:49:02 +020049 }
50 nlohmann::json schema =
51 nlohmann::json::parse(schemaStream, nullptr, false, true);
52 if (schema.is_discarded())
53 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020054 lg2::error(
55 "Illegal schema file detected, cannot validate JSON, exiting");
Christopher Meisbdaa6b22025-04-02 10:49:02 +020056 std::exit(EXIT_FAILURE);
Christopher Meisf7252572025-06-11 13:22:05 +020057 return;
Christopher Meisbdaa6b22025-04-02 10:49:02 +020058 }
59
60 for (auto& jsonPath : jsonPaths)
61 {
62 std::ifstream jsonStream(jsonPath.c_str());
63 if (!jsonStream.good())
64 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020065 lg2::error("unable to open {PATH}", "PATH", jsonPath.string());
Christopher Meisbdaa6b22025-04-02 10:49:02 +020066 continue;
67 }
68 auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
69 if (data.is_discarded())
70 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020071 lg2::error("syntax error in {PATH}", "PATH", jsonPath.string());
Christopher Meisbdaa6b22025-04-02 10:49:02 +020072 continue;
73 }
Alexander Hansen7f51d322025-06-25 12:26:09 +020074
75 if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data))
Christopher Meisbdaa6b22025-04-02 10:49:02 +020076 {
Alexander Hansen8feb0452025-09-15 14:29:20 +020077 lg2::error("Error validating {PATH}", "PATH", jsonPath.string());
Christopher Meisbdaa6b22025-04-02 10:49:02 +020078 continue;
79 }
Christopher Meisbdaa6b22025-04-02 10:49:02 +020080
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 }
Alexander Hansenee9a3872025-06-13 13:48:03 +020093
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);
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200100}
101
102// Iterate over new configuration and erase items from old configuration.
103void 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.
121bool 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.
Christopher Meisf7252572025-06-11 13:22:05 +0200133void Configuration::filterProbeInterfaces()
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200134{
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200135 for (auto it = configurations.begin(); it != configurations.end();)
136 {
137 auto findProbe = it->find("Probe");
138 if (findProbe == it->end())
139 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200140 lg2::error("configuration file missing probe: {PROBE}", "PROBE",
141 *it);
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200142 it++;
143 continue;
144 }
145
146 nlohmann::json probeCommand;
147 if ((*findProbe).type() != nlohmann::json::value_t::array)
148 {
149 probeCommand = nlohmann::json::array();
150 probeCommand.push_back(*findProbe);
151 }
152 else
153 {
154 probeCommand = *findProbe;
155 }
156
157 for (const nlohmann::json& probeJson : probeCommand)
158 {
159 const std::string* probe = probeJson.get_ptr<const std::string*>();
160 if (probe == nullptr)
161 {
Alexander Hansen8feb0452025-09-15 14:29:20 +0200162 lg2::error("Probe statement wasn't a string, can't parse");
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200163 continue;
164 }
165 // Skip it if the probe cmd doesn't contain an interface.
166 if (probe::findProbeType(*probe))
167 {
168 continue;
169 }
170
171 // syntax requires probe before first open brace
172 auto findStart = probe->find('(');
173 if (findStart != std::string::npos)
174 {
175 std::string interface = probe->substr(0, findStart);
Christopher Meisf7252572025-06-11 13:22:05 +0200176 probeInterfaces.emplace(interface);
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200177 }
178 }
179 it++;
180 }
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200181}
182
Christopher Meisf7252572025-06-11 13:22:05 +0200183bool writeJsonFiles(const nlohmann::json& systemConfiguration)
184{
Alexander Hansene6651852025-01-21 16:22:05 +0100185 if (!EM_CACHE_CONFIGURATION)
186 {
187 return true;
188 }
189
Alexander Hansenb3609912025-08-04 16:38:52 +0200190 std::error_code ec;
191 std::filesystem::create_directory(configurationOutDir, ec);
192 if (ec)
193 {
194 return false;
195 }
Christopher Meisf7252572025-06-11 13:22:05 +0200196 std::ofstream output(currentConfiguration);
197 if (!output.good())
198 {
199 return false;
200 }
201 output << systemConfiguration.dump(4);
202 output.close();
203 return true;
204}