blob: 137f4e30aa81368b3fa0b0988e42fc0e0fc0303d [file] [log] [blame]
Christopher Meisbdaa6b22025-04-02 10:49:02 +02001#include "configuration.hpp"
2
3#include "perform_probe.hpp"
Alexander Hansenee9a3872025-06-13 13:48:03 +02004#include "phosphor-logging/lg2.hpp"
Christopher Meis59ef1e72025-04-16 08:53:25 +02005#include "utils.hpp"
Christopher Meisbdaa6b22025-04-02 10:49:02 +02006
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
20namespace configuration
21{
22// writes output files to persist data
23bool 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
37bool loadConfigurations(std::list<nlohmann::json>& configurations)
38{
Alexander Hansenee9a3872025-06-13 13:48:03 +020039 const auto start = std::chrono::steady_clock::now();
40
Christopher Meisbdaa6b22025-04-02 10:49:02 +020041 // 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 }
Alexander Hansen7f51d322025-06-25 12:26:09 +020086
87 if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data))
Christopher Meisbdaa6b22025-04-02 10:49:02 +020088 {
89 std::cerr << "Error validating " << jsonPath.string() << "\n";
90 continue;
91 }
Christopher Meisbdaa6b22025-04-02 10:49:02 +020092
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 }
Alexander Hansenee9a3872025-06-13 13:48:03 +0200105
106 const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
107 std::chrono::steady_clock::now() - start)
108 .count();
109
110 lg2::debug("Finished loading json configuration in {MILLIS}ms", "MILLIS",
111 duration);
112
Christopher Meisbdaa6b22025-04-02 10:49:02 +0200113 return true;
114}
115
116// Iterate over new configuration and erase items from old configuration.
117void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
118 nlohmann::json& newConfiguration)
119{
120 for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
121 {
122 auto findKey = oldConfiguration.find(it.key());
123 if (findKey != oldConfiguration.end())
124 {
125 it = newConfiguration.erase(it);
126 }
127 else
128 {
129 it++;
130 }
131 }
132}
133
134// validates a given input(configuration) with a given json schema file.
135bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
136{
137 valijson::Schema schema;
138 valijson::SchemaParser parser;
139 valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
140 parser.populateSchema(schemaAdapter, schema);
141 valijson::Validator validator;
142 valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
143 return validator.validate(schema, targetAdapter, nullptr);
144}
145
146// Extract the D-Bus interfaces to probe from the JSON config files.
147std::set<std::string> getProbeInterfaces()
148{
149 std::set<std::string> interfaces;
150 std::list<nlohmann::json> configurations;
151 if (!configuration::loadConfigurations(configurations))
152 {
153 return interfaces;
154 }
155
156 for (auto it = configurations.begin(); it != configurations.end();)
157 {
158 auto findProbe = it->find("Probe");
159 if (findProbe == it->end())
160 {
161 std::cerr << "configuration file missing probe:\n " << *it << "\n";
162 it++;
163 continue;
164 }
165
166 nlohmann::json probeCommand;
167 if ((*findProbe).type() != nlohmann::json::value_t::array)
168 {
169 probeCommand = nlohmann::json::array();
170 probeCommand.push_back(*findProbe);
171 }
172 else
173 {
174 probeCommand = *findProbe;
175 }
176
177 for (const nlohmann::json& probeJson : probeCommand)
178 {
179 const std::string* probe = probeJson.get_ptr<const std::string*>();
180 if (probe == nullptr)
181 {
182 std::cerr << "Probe statement wasn't a string, can't parse";
183 continue;
184 }
185 // Skip it if the probe cmd doesn't contain an interface.
186 if (probe::findProbeType(*probe))
187 {
188 continue;
189 }
190
191 // syntax requires probe before first open brace
192 auto findStart = probe->find('(');
193 if (findStart != std::string::npos)
194 {
195 std::string interface = probe->substr(0, findStart);
196 interfaces.emplace(interface);
197 }
198 }
199 it++;
200 }
201
202 return interfaces;
203}
204
205} // namespace configuration