xref: /openbmc/entity-manager/src/entity_manager/configuration.cpp (revision 064d8aff8ea9c499be27679cb5c79c6ae25d261b)
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     lg2::debug("deriving new configuration");
115 
116     for (auto it = newConfiguration.begin(); it != newConfiguration.end();)
117     {
118         auto findKey = oldConfiguration.find(it.key());
119         if (findKey != oldConfiguration.end())
120         {
121             it = newConfiguration.erase(it);
122         }
123         else
124         {
125             it++;
126         }
127     }
128 }
129 
130 // validates a given input(configuration) with a given json schema file.
validateJson(const nlohmann::json & schemaFile,const nlohmann::json & input)131 bool validateJson(const nlohmann::json& schemaFile, const nlohmann::json& input)
132 {
133     valijson::Schema schema;
134     valijson::SchemaParser parser;
135     valijson::adapters::NlohmannJsonAdapter schemaAdapter(schemaFile);
136     parser.populateSchema(schemaAdapter, schema);
137     valijson::Validator validator;
138     valijson::adapters::NlohmannJsonAdapter targetAdapter(input);
139     return validator.validate(schema, targetAdapter, nullptr);
140 }
141 
142 // Extract the D-Bus interfaces to probe from the JSON config files.
filterProbeInterfaces()143 void Configuration::filterProbeInterfaces()
144 {
145     for (auto it = configurations.begin(); it != configurations.end();)
146     {
147         auto findProbe = it->find("Probe");
148         if (findProbe == it->end())
149         {
150             lg2::error("configuration file missing probe: {PROBE}", "PROBE",
151                        *it);
152             it++;
153             continue;
154         }
155 
156         nlohmann::json probeCommand;
157         if ((*findProbe).type() != nlohmann::json::value_t::array)
158         {
159             probeCommand = nlohmann::json::array();
160             probeCommand.push_back(*findProbe);
161         }
162         else
163         {
164             probeCommand = *findProbe;
165         }
166 
167         for (const nlohmann::json& probeJson : probeCommand)
168         {
169             const std::string* probe = probeJson.get_ptr<const std::string*>();
170             if (probe == nullptr)
171             {
172                 lg2::error("Probe statement wasn't a string, can't parse");
173                 continue;
174             }
175             // Skip it if the probe cmd doesn't contain an interface.
176             if (probe::findProbeType(*probe))
177             {
178                 continue;
179             }
180 
181             // syntax requires probe before first open brace
182             auto findStart = probe->find('(');
183             if (findStart != std::string::npos)
184             {
185                 std::string interface = probe->substr(0, findStart);
186                 probeInterfaces.emplace(interface);
187             }
188         }
189         it++;
190     }
191 }
192 
writeJsonFiles(const nlohmann::json & systemConfiguration)193 bool writeJsonFiles(const nlohmann::json& systemConfiguration)
194 {
195     if (!EM_CACHE_CONFIGURATION)
196     {
197         return true;
198     }
199 
200     std::error_code ec;
201     std::filesystem::create_directory(configurationOutDir, ec);
202     if (ec)
203     {
204         return false;
205     }
206 
207     lg2::debug("writing system configuration to {PATH}", "PATH",
208                currentConfiguration);
209 
210     std::ofstream output(currentConfiguration);
211     if (!output.good())
212     {
213         return false;
214     }
215     output << systemConfiguration.dump(4);
216     output.close();
217     return true;
218 }
219