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