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