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