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