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