blob: 974b02c39f3cf75d8b19412505703c60bb967445 [file] [log] [blame]
Matt Spinler852db672019-03-06 13:46:14 -06001#include "association_manager.hpp"
2
Matt Spinler99e66a02019-03-06 14:11:22 -06003#include <phosphor-logging/log.hpp>
4
Brad Bishopa83db302020-12-06 14:51:23 -05005#include <filesystem>
6#include <fstream>
7
Matt Spinler852db672019-03-06 13:46:14 -06008namespace phosphor
9{
10namespace inventory
11{
12namespace manager
13{
14namespace associations
15{
Matt Spinler99e66a02019-03-06 14:11:22 -060016using namespace phosphor::logging;
17using sdbusplus::exception::SdBusError;
Matt Spinler59521e82021-02-11 13:41:06 -060018namespace fs = std::filesystem;
Matt Spinler852db672019-03-06 13:46:14 -060019
20Manager::Manager(sdbusplus::bus::bus& bus, const std::string& jsonPath) :
21 _bus(bus), _jsonFile(jsonPath)
22{
Matt Spinler59521e82021-02-11 13:41:06 -060023 // If there aren't any conditional associations files, look for
24 // that default nonconditional one.
25 if (!loadConditions())
26 {
27 if (fs::exists(_jsonFile))
28 {
29 std::ifstream file{_jsonFile};
30 auto json = nlohmann::json::parse(file, nullptr, true);
31 load(json);
32 }
33 }
Matt Spinler99e66a02019-03-06 14:11:22 -060034}
35
36/**
37 * @brief Throws an exception if 'num' is zero. Used for JSON
38 * sanity checking.
39 *
40 * @param[in] num - the number to check
41 */
42void throwIfZero(int num)
43{
44 if (!num)
45 {
46 throw std::invalid_argument("Invalid empty field in JSON");
47 }
48}
49
Matt Spinler59521e82021-02-11 13:41:06 -060050bool Manager::loadConditions()
Matt Spinler99e66a02019-03-06 14:11:22 -060051{
Matt Spinler59521e82021-02-11 13:41:06 -060052 auto dir = _jsonFile.parent_path();
Matt Spinler99e66a02019-03-06 14:11:22 -060053
Matt Spinler59521e82021-02-11 13:41:06 -060054 for (const auto& dirent : fs::recursive_directory_iterator(dir))
55 {
56 const auto& path = dirent.path();
57 if (path.extension() == ".json")
58 {
59 std::ifstream file{path};
60 auto json = nlohmann::json::parse(file, nullptr, true);
Matt Spinler99e66a02019-03-06 14:11:22 -060061
Matt Spinler59521e82021-02-11 13:41:06 -060062 if (json.is_object() && json.contains("condition"))
63 {
64 const auto& conditionJSON = json.at("condition");
65 if (!conditionJSON.contains("path") ||
66 !conditionJSON.contains("interface") ||
67 !conditionJSON.contains("property") ||
68 !conditionJSON.contains("values"))
69 {
70 std::string msg =
71 "Invalid JSON in associations condition entry in " +
72 path.string() + ". Skipping file.";
73 log<level::ERR>(msg.c_str());
74 continue;
75 }
Matt Spinler99e66a02019-03-06 14:11:22 -060076
Matt Spinler59521e82021-02-11 13:41:06 -060077 Condition c;
78 c.file = path;
79 c.path = conditionJSON["path"].get<std::string>();
80 if (c.path.front() != '/')
81 {
82 c.path = '/' + c.path;
83 }
84 fprintf(stderr, "found conditions file %s\n", c.file.c_str());
85 c.interface = conditionJSON["interface"].get<std::string>();
86 c.property = conditionJSON["property"].get<std::string>();
87
88 // The values are in an array, and need to be
89 // converted to an InterfaceVariantType.
90 for (const auto& value : conditionJSON["values"])
91 {
92 if (value.is_array())
93 {
94 std::vector<uint8_t> variantValue;
95 for (const auto& v : value)
96 {
97 variantValue.push_back(v.get<uint8_t>());
98 }
99 c.values.push_back(variantValue);
100 continue;
101 }
102
103 // Try the remaining types
104 auto s = value.get_ptr<const std::string*>();
105 auto i = value.get_ptr<const int64_t*>();
106 auto b = value.get_ptr<const bool*>();
107 if (s)
108 {
109 c.values.push_back(*s);
110 }
111 else if (i)
112 {
113 c.values.push_back(*i);
114 }
115 else if (b)
116 {
117 c.values.push_back(*b);
118 }
119 else
120 {
121 std::stringstream ss;
122 ss << "Invalid condition property value in " << c.file
123 << ": " << value;
124 log<level::ERR>(ss.str().c_str());
125 throw std::runtime_error(ss.str());
126 }
127 }
128
129 _conditions.push_back(std::move(c));
130 }
131 }
132 }
133
134 return !_conditions.empty();
135}
136
137bool Manager::conditionMatch(const sdbusplus::message::object_path& objectPath,
138 const Object& object)
139{
140 fs::path foundPath;
141 for (const auto& condition : _conditions)
142 {
143 if (condition.path != objectPath)
144 {
145 continue;
146 }
147
148 auto interface = std::find_if(object.begin(), object.end(),
149 [&condition](const auto& i) {
150 return i.first == condition.interface;
151 });
152 if (interface == object.end())
153 {
154 continue;
155 }
156
157 auto property =
158 std::find_if(interface->second.begin(), interface->second.end(),
159 [&condition](const auto& p) {
160 return condition.property == p.first;
161 });
162 if (property == interface->second.end())
163 {
164 continue;
165 }
166
167 auto match = std::find(condition.values.begin(), condition.values.end(),
168 property->second);
169 if (match != condition.values.end())
170 {
171 foundPath = condition.file;
172 break;
173 }
174 }
175
176 if (!foundPath.empty())
177 {
178 std::ifstream file{foundPath};
179 auto json = nlohmann::json::parse(file, nullptr, true);
180 load(json["associations"]);
181 _conditions.clear();
182 return true;
183 }
184
185 return false;
186}
187
188bool Manager::conditionMatch()
189{
190 fs::path foundPath;
191
192 for (const auto& condition : _conditions)
193 {
194 // Compare the actualValue field against the values in the
195 // values vector to see if there is a condition match.
196 auto found = std::find(condition.values.begin(), condition.values.end(),
197 condition.actualValue);
198 if (found != condition.values.end())
199 {
200 foundPath = condition.file;
201 break;
202 }
203 }
204
205 if (!foundPath.empty())
206 {
207 std::ifstream file{foundPath};
208 auto json = nlohmann::json::parse(file, nullptr, true);
209 load(json["associations"]);
210 _conditions.clear();
211 return true;
212 }
213
214 return false;
215}
216
217void Manager::load(const nlohmann::json& json)
218{
Matt Spinler99e66a02019-03-06 14:11:22 -0600219 const std::string root{INVENTORY_ROOT};
220
221 for (const auto& jsonAssoc : json)
222 {
223 // Only add the slash if necessary
224 std::string path = jsonAssoc.at("path");
225 throwIfZero(path.size());
226 if (path.front() != '/')
227 {
228 path = root + "/" + path;
229 }
230 else
231 {
232 path = root + path;
233 }
234
235 auto& assocEndpoints = _associations[path];
236
237 for (const auto& endpoint : jsonAssoc.at("endpoints"))
238 {
239 std::string ftype = endpoint.at("types").at("fType");
240 std::string rtype = endpoint.at("types").at("rType");
241 throwIfZero(ftype.size());
242 throwIfZero(rtype.size());
243 Types types{std::move(ftype), std::move(rtype)};
244
245 Paths paths = endpoint.at("paths");
246 throwIfZero(paths.size());
247 assocEndpoints.emplace_back(std::move(types), std::move(paths));
248 }
249 }
Matt Spinler852db672019-03-06 13:46:14 -0600250}
251
Brad Bishop104ccba2020-12-06 15:22:10 -0500252void Manager::createAssociations(const std::string& objectPath,
253 bool deferSignal)
Matt Spinler852db672019-03-06 13:46:14 -0600254{
Matt Spinlerc47ca582019-03-06 14:37:42 -0600255 auto endpoints = _associations.find(objectPath);
256 if (endpoints == _associations.end())
257 {
258 return;
259 }
260
261 if (std::find(_handled.begin(), _handled.end(), objectPath) !=
262 _handled.end())
263 {
264 return;
265 }
266
267 _handled.push_back(objectPath);
268
269 for (const auto& endpoint : endpoints->second)
270 {
271 const auto& types = std::get<typesPos>(endpoint);
272 const auto& paths = std::get<pathsPos>(endpoint);
273
274 for (const auto& endpointPath : paths)
275 {
276 const auto& forwardType = std::get<forwardTypePos>(types);
277 const auto& reverseType = std::get<reverseTypePos>(types);
278
279 createAssociation(objectPath, forwardType, endpointPath,
Brad Bishop104ccba2020-12-06 15:22:10 -0500280 reverseType, deferSignal);
Matt Spinlerc47ca582019-03-06 14:37:42 -0600281 }
282 }
283}
284
285void Manager::createAssociation(const std::string& forwardPath,
286 const std::string& forwardType,
287 const std::string& reversePath,
Brad Bishop104ccba2020-12-06 15:22:10 -0500288 const std::string& reverseType,
289 bool deferSignal)
Matt Spinlerc47ca582019-03-06 14:37:42 -0600290{
291 auto object = _associationIfaces.find(forwardPath);
292 if (object == _associationIfaces.end())
293 {
294 auto a = std::make_unique<AssociationObject>(_bus, forwardPath.c_str(),
295 true);
296
297 using AssociationProperty =
298 std::vector<std::tuple<std::string, std::string, std::string>>;
299 AssociationProperty prop;
300
301 prop.emplace_back(forwardType, reverseType, reversePath);
302 a->associations(std::move(prop));
Brad Bishop104ccba2020-12-06 15:22:10 -0500303 if (!deferSignal)
304 {
305 a->emit_object_added();
306 }
Matt Spinlerc47ca582019-03-06 14:37:42 -0600307 _associationIfaces.emplace(forwardPath, std::move(a));
308 }
309 else
310 {
311 // Interface exists, just update the property
312 auto prop = object->second->associations();
313 prop.emplace_back(forwardType, reverseType, reversePath);
Brad Bishop104ccba2020-12-06 15:22:10 -0500314 object->second->associations(std::move(prop), deferSignal);
Matt Spinlerc47ca582019-03-06 14:37:42 -0600315 }
Matt Spinler852db672019-03-06 13:46:14 -0600316}
317} // namespace associations
318} // namespace manager
319} // namespace inventory
320} // namespace phosphor