xref: /openbmc/entity-manager/src/entity_manager/configuration.cpp (revision b36099189e4bdb7d27444ffff280679d442ee96f)
1 #include "configuration.hpp"
2 
3 #include "perform_probe.hpp"
4 #include "phosphor-logging/lg2.hpp"
5 #include "utils.hpp"
6 
7 #include <nlohmann/json.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 <iostream>
16 #include <string>
17 #include <vector>
18 
Configuration()19 Configuration::Configuration()
20 {
21     loadConfigurations();
22     filterProbeInterfaces();
23 }
24 
loadConfigurations()25 void Configuration::loadConfigurations()
26 {
27     const auto start = std::chrono::steady_clock::now();
28 
29     // find configuration files
30     std::vector<std::filesystem::path> jsonPaths;
31     if (!findFiles(
32             std::vector<std::filesystem::path>{configurationDirectory,
33                                                hostConfigurationDirectory},
34             R"(.*\.json)", jsonPaths))
35     {
36         std::cerr << "Unable to find any configuration files in "
37                   << configurationDirectory << "\n";
38         return;
39     }
40 
41     std::ifstream schemaStream(
42         std::string(schemaDirectory) + "/" + globalSchema);
43     if (!schemaStream.good())
44     {
45         std::cerr
46             << "Cannot open schema file,  cannot validate JSON, exiting\n\n";
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         std::cerr
55             << "Illegal schema file detected, cannot validate JSON, exiting\n";
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             std::cerr << "unable to open " << jsonPath.string() << "\n";
66             continue;
67         }
68         auto data = nlohmann::json::parse(jsonStream, nullptr, false, true);
69         if (data.is_discarded())
70         {
71             std::cerr << "syntax error in " << jsonPath.string() << "\n";
72             continue;
73         }
74 
75         if (ENABLE_RUNTIME_VALIDATE_JSON && !validateJson(schema, data))
76         {
77             std::cerr << "Error validating " << jsonPath.string() << "\n";
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             std::cerr << "configuration file missing probe:\n " << *it << "\n";
141             it++;
142             continue;
143         }
144 
145         nlohmann::json probeCommand;
146         if ((*findProbe).type() != nlohmann::json::value_t::array)
147         {
148             probeCommand = nlohmann::json::array();
149             probeCommand.push_back(*findProbe);
150         }
151         else
152         {
153             probeCommand = *findProbe;
154         }
155 
156         for (const nlohmann::json& probeJson : probeCommand)
157         {
158             const std::string* probe = probeJson.get_ptr<const std::string*>();
159             if (probe == nullptr)
160             {
161                 std::cerr << "Probe statement wasn't a string, can't parse";
162                 continue;
163             }
164             // Skip it if the probe cmd doesn't contain an interface.
165             if (probe::findProbeType(*probe))
166             {
167                 continue;
168             }
169 
170             // syntax requires probe before first open brace
171             auto findStart = probe->find('(');
172             if (findStart != std::string::npos)
173             {
174                 std::string interface = probe->substr(0, findStart);
175                 probeInterfaces.emplace(interface);
176             }
177         }
178         it++;
179     }
180 }
181 
writeJsonFiles(const nlohmann::json & systemConfiguration)182 bool writeJsonFiles(const nlohmann::json& systemConfiguration)
183 {
184     if (!EM_CACHE_CONFIGURATION)
185     {
186         return true;
187     }
188 
189     std::error_code ec;
190     std::filesystem::create_directory(configurationOutDir, ec);
191     if (ec)
192     {
193         return false;
194     }
195     std::ofstream output(currentConfiguration);
196     if (!output.good())
197     {
198         return false;
199     }
200     output << systemConfiguration.dump(4);
201     output.close();
202     return true;
203 }
204