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