xref: /openbmc/entity-manager/src/entity_manager/configuration.cpp (revision 8290ca426d12519eb182032837a5eb3baf05567e)
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)18 Configuration::Configuration(
19     const std::vector<std::filesystem::path>& configurationDirectories) :
20     configurationDirectories(configurationDirectories)
21 {
22     loadConfigurations();
23     filterProbeInterfaces();
24 }
25 
loadConfigurations()26 void Configuration::loadConfigurations()
27 {
28     const auto start = std::chrono::steady_clock::now();
29 
30     // find configuration files
31     std::vector<std::filesystem::path> jsonPaths;
32     if (!findFiles(configurationDirectories, R"(.*\.json)", jsonPaths))
33     {
34         for (const auto& configurationDirectory : configurationDirectories)
35         {
36             lg2::error("Unable to find any configuration files in {DIR}", "DIR",
37                        configurationDirectory);
38         }
39         return;
40     }
41 
42     std::ifstream schemaStream(
43         std::string(schemaDirectory) + "/" + globalSchema);
44     if (!schemaStream.good())
45     {
46         lg2::error("Cannot open schema file,  cannot validate JSON, exiting");
47         std::exit(EXIT_FAILURE);
48         return;
49     }
50     nlohmann::json schema =
51         nlohmann::json::parse(schemaStream, nullptr, false, true);
52     if (schema.is_discarded())
53     {
54         lg2::error(
55             "Illegal schema file detected, cannot validate JSON, exiting");
56         std::exit(EXIT_FAILURE);
57         return;
58     }
59 
60     for (auto& jsonPath : jsonPaths)
61     {
62         std::ifstream jsonStream(jsonPath.c_str());
63         if (!jsonStream.good())
64         {
65             lg2::error("unable to open {PATH}", "PATH", jsonPath.string());
66             continue;
67         }
68         auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
69         if (data.is_discarded())
70         {
71             lg2::error("syntax error in {PATH}", "PATH", jsonPath.string());
72             continue;
73         }
74 
75         if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data))
76         {
77             lg2::error("Error validating {PATH}", "PATH", jsonPath.string());
78             continue;
79         }
80 
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     }
93 
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);
100 }
101 
102 // Iterate over new configuration and erase items from old configuration.
deriveNewConfiguration(const nlohmann::json & oldConfiguration,nlohmann::json & newConfiguration)103 void 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.
validateJson(const nlohmann::json & schemaFile,const nlohmann::json & input)121 bool 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.
filterProbeInterfaces()133 void Configuration::filterProbeInterfaces()
134 {
135     for (auto it = configurations.begin(); it != configurations.end();)
136     {
137         auto findProbe = it->find("Probe");
138         if (findProbe == it->end())
139         {
140             lg2::error("configuration file missing probe: {PROBE}", "PROBE",
141                        *it);
142             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             {
162                 lg2::error("Probe statement wasn't a string, can't parse");
163                 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);
176                 probeInterfaces.emplace(interface);
177             }
178         }
179         it++;
180     }
181 }
182 
writeJsonFiles(const nlohmann::json & systemConfiguration)183 bool writeJsonFiles(const nlohmann::json& systemConfiguration)
184 {
185     if (!EM_CACHE_CONFIGURATION)
186     {
187         return true;
188     }
189 
190     std::error_code ec;
191     std::filesystem::create_directory(configurationOutDir, ec);
192     if (ec)
193     {
194         return false;
195     }
196     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 }
205