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