xref: /openbmc/entity-manager/src/entity_manager/configuration.cpp (revision 064d8aff8ea9c499be27679cb5c79c6ae25d261b)
1fc9e7fdaSChristopher Meis #include "configuration.hpp"
2fc9e7fdaSChristopher Meis 
3fc9e7fdaSChristopher Meis #include "perform_probe.hpp"
459ef1e72SChristopher Meis #include "utils.hpp"
5fc9e7fdaSChristopher Meis 
6fc9e7fdaSChristopher Meis #include <nlohmann/json.hpp>
78feb0454SAlexander Hansen #include <phosphor-logging/lg2.hpp>
8fc9e7fdaSChristopher Meis #include <valijson/adapters/nlohmann_json_adapter.hpp>
9fc9e7fdaSChristopher Meis #include <valijson/schema.hpp>
10fc9e7fdaSChristopher Meis #include <valijson/schema_parser.hpp>
11fc9e7fdaSChristopher Meis #include <valijson/validator.hpp>
12fc9e7fdaSChristopher Meis 
13fc9e7fdaSChristopher Meis #include <filesystem>
14fc9e7fdaSChristopher Meis #include <fstream>
15fc9e7fdaSChristopher Meis #include <string>
16fc9e7fdaSChristopher Meis #include <vector>
17fc9e7fdaSChristopher Meis 
Configuration(const std::vector<std::filesystem::path> & configurationDirectories,const std::filesystem::path & schemaDirectory)188290ca42SAlexander Hansen Configuration::Configuration(
19bc0b05beSAlexander Hansen     const std::vector<std::filesystem::path>& configurationDirectories,
20bc0b05beSAlexander Hansen     const std::filesystem::path& schemaDirectory) :
21bc0b05beSAlexander Hansen     schemaDirectory(schemaDirectory),
228290ca42SAlexander Hansen     configurationDirectories(configurationDirectories)
23fc9e7fdaSChristopher Meis {
24f7252577SChristopher Meis     loadConfigurations();
25f7252577SChristopher Meis     filterProbeInterfaces();
26fc9e7fdaSChristopher Meis }
27fc9e7fdaSChristopher Meis 
loadConfigurations()28f7252577SChristopher Meis void Configuration::loadConfigurations()
29fc9e7fdaSChristopher Meis {
30ee9a387fSAlexander Hansen     const auto start = std::chrono::steady_clock::now();
31ee9a387fSAlexander Hansen 
32fc9e7fdaSChristopher Meis     // find configuration files
33fc9e7fdaSChristopher Meis     std::vector<std::filesystem::path> jsonPaths;
348290ca42SAlexander Hansen     if (!findFiles(configurationDirectories, R"(.*\.json)", jsonPaths))
358290ca42SAlexander Hansen     {
368290ca42SAlexander Hansen         for (const auto& configurationDirectory : configurationDirectories)
37fc9e7fdaSChristopher Meis         {
388feb0454SAlexander Hansen             lg2::error("Unable to find any configuration files in {DIR}", "DIR",
398feb0454SAlexander Hansen                        configurationDirectory);
408290ca42SAlexander Hansen         }
41f7252577SChristopher Meis         return;
42fc9e7fdaSChristopher Meis     }
43fc9e7fdaSChristopher Meis 
4499f17c08SAlexander Hansen     nlohmann::json schema;
4599f17c08SAlexander Hansen 
4699f17c08SAlexander Hansen     if constexpr (ENABLE_RUNTIME_VALIDATE_JSON)
4799f17c08SAlexander Hansen     {
48bc0b05beSAlexander Hansen         std::ifstream schemaStream(schemaDirectory / "global.json");
49fc9e7fdaSChristopher Meis         if (!schemaStream.good())
50fc9e7fdaSChristopher Meis         {
5199f17c08SAlexander Hansen             lg2::error(
5299f17c08SAlexander Hansen                 "Cannot open schema file,  cannot validate JSON, exiting");
53fc9e7fdaSChristopher Meis             std::exit(EXIT_FAILURE);
54f7252577SChristopher Meis             return;
55fc9e7fdaSChristopher Meis         }
5699f17c08SAlexander Hansen         schema = nlohmann::json::parse(schemaStream, nullptr, false, true);
57fc9e7fdaSChristopher Meis         if (schema.is_discarded())
58fc9e7fdaSChristopher Meis         {
598feb0454SAlexander Hansen             lg2::error(
608feb0454SAlexander Hansen                 "Illegal schema file detected, cannot validate JSON, exiting");
61fc9e7fdaSChristopher Meis             std::exit(EXIT_FAILURE);
62f7252577SChristopher Meis             return;
63fc9e7fdaSChristopher Meis         }
6499f17c08SAlexander Hansen     }
65fc9e7fdaSChristopher Meis 
66fc9e7fdaSChristopher Meis     for (auto& jsonPath : jsonPaths)
67fc9e7fdaSChristopher Meis     {
68fc9e7fdaSChristopher Meis         std::ifstream jsonStream(jsonPath.c_str());
69fc9e7fdaSChristopher Meis         if (!jsonStream.good())
70fc9e7fdaSChristopher Meis         {
718feb0454SAlexander Hansen             lg2::error("unable to open {PATH}", "PATH", jsonPath.string());
72fc9e7fdaSChristopher Meis             continue;
73fc9e7fdaSChristopher Meis         }
74fc9e7fdaSChristopher Meis         auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
75fc9e7fdaSChristopher Meis         if (data.is_discarded())
76fc9e7fdaSChristopher Meis         {
778feb0454SAlexander Hansen             lg2::error("syntax error in {PATH}", "PATH", jsonPath.string());
78fc9e7fdaSChristopher Meis             continue;
79fc9e7fdaSChristopher Meis         }
807f51d32fSAlexander Hansen 
817f51d32fSAlexander Hansen         if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data))
82fc9e7fdaSChristopher Meis         {
838feb0454SAlexander Hansen             lg2::error("Error validating {PATH}", "PATH", jsonPath.string());
84fc9e7fdaSChristopher Meis             continue;
85fc9e7fdaSChristopher Meis         }
86fc9e7fdaSChristopher Meis 
87fc9e7fdaSChristopher Meis         if (data.type() == nlohmann::json::value_t::array)
88fc9e7fdaSChristopher Meis         {
89fc9e7fdaSChristopher Meis             for (auto& d : data)
90fc9e7fdaSChristopher Meis             {
91fc9e7fdaSChristopher Meis                 configurations.emplace_back(d);
92fc9e7fdaSChristopher Meis             }
93fc9e7fdaSChristopher Meis         }
94fc9e7fdaSChristopher Meis         else
95fc9e7fdaSChristopher Meis         {
96fc9e7fdaSChristopher Meis             configurations.emplace_back(data);
97fc9e7fdaSChristopher Meis         }
98fc9e7fdaSChristopher Meis     }
99ee9a387fSAlexander Hansen 
100ee9a387fSAlexander Hansen     const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
101ee9a387fSAlexander Hansen                               std::chrono::steady_clock::now() - start)
102ee9a387fSAlexander Hansen                               .count();
103ee9a387fSAlexander Hansen 
1048b7d6344SAlexander Hansen     lg2::debug(
1058b7d6344SAlexander Hansen         "Finished loading {NCONFIGS} json configuration(s) from {NFILES} file(s) in {MILLIS}ms",
1068b7d6344SAlexander Hansen         "NCONFIGS", configurations.size(), "NFILES", jsonPaths.size(), "MILLIS",
107ee9a387fSAlexander Hansen         duration);
108fc9e7fdaSChristopher Meis }
109fc9e7fdaSChristopher Meis 
110fc9e7fdaSChristopher Meis // Iterate over new configuration and erase items from old configuration.
deriveNewConfiguration(const nlohmann::json & oldConfiguration,nlohmann::json & newConfiguration)111fc9e7fdaSChristopher Meis void deriveNewConfiguration(const nlohmann::json& oldConfiguration,
112fc9e7fdaSChristopher Meis                             nlohmann::json& newConfiguration)
113fc9e7fdaSChristopher Meis {
114*064d8affSAlexander Hansen     lg2::debug("deriving new configuration");
115*064d8affSAlexander Hansen 
116fc9e7fdaSChristopher Meis     for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
117fc9e7fdaSChristopher Meis     {
118fc9e7fdaSChristopher Meis         auto findKey = oldConfiguration.find(it.key());
119fc9e7fdaSChristopher Meis         if (findKey != oldConfiguration.end())
120fc9e7fdaSChristopher Meis         {
121fc9e7fdaSChristopher Meis             it = newConfiguration.erase(it);
122fc9e7fdaSChristopher Meis         }
123fc9e7fdaSChristopher Meis         else
124fc9e7fdaSChristopher Meis         {
125fc9e7fdaSChristopher Meis             it++;
126fc9e7fdaSChristopher Meis         }
127fc9e7fdaSChristopher Meis     }
128fc9e7fdaSChristopher Meis }
129fc9e7fdaSChristopher Meis 
130fc9e7fdaSChristopher Meis // validates a given input(configuration) with a given json schema file.
validateJson(const nlohmann::json & schemaFile,const nlohmann::json & input)131fc9e7fdaSChristopher Meis bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
132fc9e7fdaSChristopher Meis {
133fc9e7fdaSChristopher Meis     valijson::Schema schema;
134fc9e7fdaSChristopher Meis     valijson::SchemaParser parser;
135fc9e7fdaSChristopher Meis     valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
136fc9e7fdaSChristopher Meis     parser.populateSchema(schemaAdapter, schema);
137fc9e7fdaSChristopher Meis     valijson::Validator validator;
138fc9e7fdaSChristopher Meis     valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
139fc9e7fdaSChristopher Meis     return validator.validate(schema, targetAdapter, nullptr);
140fc9e7fdaSChristopher Meis }
141fc9e7fdaSChristopher Meis 
142fc9e7fdaSChristopher Meis // Extract the D-Bus interfaces to probe from the JSON config files.
filterProbeInterfaces()143f7252577SChristopher Meis void Configuration::filterProbeInterfaces()
144fc9e7fdaSChristopher Meis {
145fc9e7fdaSChristopher Meis     for (auto it = configurations.begin(); it != configurations.end();)
146fc9e7fdaSChristopher Meis     {
147fc9e7fdaSChristopher Meis         auto findProbe = it->find("Probe");
148fc9e7fdaSChristopher Meis         if (findProbe == it->end())
149fc9e7fdaSChristopher Meis         {
1508feb0454SAlexander Hansen             lg2::error("configuration file missing probe: {PROBE}", "PROBE",
1518feb0454SAlexander Hansen                        *it);
152fc9e7fdaSChristopher Meis             it++;
153fc9e7fdaSChristopher Meis             continue;
154fc9e7fdaSChristopher Meis         }
155fc9e7fdaSChristopher Meis 
156fc9e7fdaSChristopher Meis         nlohmann::json probeCommand;
157fc9e7fdaSChristopher Meis         if ((*findProbe).type() != nlohmann::json::value_t::array)
158fc9e7fdaSChristopher Meis         {
159fc9e7fdaSChristopher Meis             probeCommand = nlohmann::json::array();
160fc9e7fdaSChristopher Meis             probeCommand.push_back(*findProbe);
161fc9e7fdaSChristopher Meis         }
162fc9e7fdaSChristopher Meis         else
163fc9e7fdaSChristopher Meis         {
164fc9e7fdaSChristopher Meis             probeCommand = *findProbe;
165fc9e7fdaSChristopher Meis         }
166fc9e7fdaSChristopher Meis 
167fc9e7fdaSChristopher Meis         for (const nlohmann::json& probeJson : probeCommand)
168fc9e7fdaSChristopher Meis         {
169fc9e7fdaSChristopher Meis             const std::string* probe = probeJson.get_ptr<const std::string*>();
170fc9e7fdaSChristopher Meis             if (probe == nullptr)
171fc9e7fdaSChristopher Meis             {
1728feb0454SAlexander Hansen                 lg2::error("Probe statement wasn't a string, can't parse");
173fc9e7fdaSChristopher Meis                 continue;
174fc9e7fdaSChristopher Meis             }
175fc9e7fdaSChristopher Meis             // Skip it if the probe cmd doesn't contain an interface.
176fc9e7fdaSChristopher Meis             if (probe::findProbeType(*probe))
177fc9e7fdaSChristopher Meis             {
178fc9e7fdaSChristopher Meis                 continue;
179fc9e7fdaSChristopher Meis             }
180fc9e7fdaSChristopher Meis 
181fc9e7fdaSChristopher Meis             // syntax requires probe before first open brace
182fc9e7fdaSChristopher Meis             auto findStart = probe->find('(');
183fc9e7fdaSChristopher Meis             if (findStart != std::string::npos)
184fc9e7fdaSChristopher Meis             {
185fc9e7fdaSChristopher Meis                 std::string interface = probe->substr(0, findStart);
186f7252577SChristopher Meis                 probeInterfaces.emplace(interface);
187fc9e7fdaSChristopher Meis             }
188fc9e7fdaSChristopher Meis         }
189fc9e7fdaSChristopher Meis         it++;
190fc9e7fdaSChristopher Meis     }
191fc9e7fdaSChristopher Meis }
192fc9e7fdaSChristopher Meis 
writeJsonFiles(const nlohmann::json & systemConfiguration)193f7252577SChristopher Meis bool writeJsonFiles(const nlohmann::json& systemConfiguration)
194f7252577SChristopher Meis {
195e665185bSAlexander Hansen     if (!EM_CACHE_CONFIGURATION)
196e665185bSAlexander Hansen     {
197e665185bSAlexander Hansen         return true;
198e665185bSAlexander Hansen     }
199e665185bSAlexander Hansen 
200b3609918SAlexander Hansen     std::error_code ec;
201b3609918SAlexander Hansen     std::filesystem::create_directory(configurationOutDir, ec);
202b3609918SAlexander Hansen     if (ec)
203b3609918SAlexander Hansen     {
204b3609918SAlexander Hansen         return false;
205b3609918SAlexander Hansen     }
206*064d8affSAlexander Hansen 
207*064d8affSAlexander Hansen     lg2::debug("writing system configuration to {PATH}", "PATH",
208*064d8affSAlexander Hansen                currentConfiguration);
209*064d8affSAlexander Hansen 
210f7252577SChristopher Meis     std::ofstream output(currentConfiguration);
211f7252577SChristopher Meis     if (!output.good())
212f7252577SChristopher Meis     {
213f7252577SChristopher Meis         return false;
214f7252577SChristopher Meis     }
215f7252577SChristopher Meis     output << systemConfiguration.dump(4);
216f7252577SChristopher Meis     output.close();
217f7252577SChristopher Meis     return true;
218f7252577SChristopher Meis }
219