blob: 2ff4cec954a30d76f9dfceaab90fa71e27b34998 [file] [log] [blame]
Matt Spinler852db672019-03-06 13:46:14 -06001#include "association_manager.hpp"
2
George Liucf5d0242022-04-14 14:39:47 +08003#include <phosphor-logging/lg2.hpp>
Matt Spinler99e66a02019-03-06 14:11:22 -06004
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 Spinler59521e82021-02-11 13:41:06 -060016namespace fs = std::filesystem;
Matt Spinler852db672019-03-06 13:46:14 -060017
Patrick Williams563306f2022-07-22 19:26:52 -050018Manager::Manager(sdbusplus::bus_t& bus, const std::string& jsonPath) :
Matt Spinler852db672019-03-06 13:46:14 -060019 _bus(bus), _jsonFile(jsonPath)
20{
Matt Spinler59521e82021-02-11 13:41:06 -060021 // 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 }
Matt Spinler99e66a02019-03-06 14:11:22 -060032}
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 */
40void throwIfZero(int num)
41{
42 if (!num)
43 {
44 throw std::invalid_argument("Invalid empty field in JSON");
45 }
46}
47
Matt Spinler59521e82021-02-11 13:41:06 -060048bool Manager::loadConditions()
Matt Spinler99e66a02019-03-06 14:11:22 -060049{
Matt Spinler59521e82021-02-11 13:41:06 -060050 auto dir = _jsonFile.parent_path();
Matt Spinler99e66a02019-03-06 14:11:22 -060051
Matt Spinler59521e82021-02-11 13:41:06 -060052 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);
Matt Spinler99e66a02019-03-06 14:11:22 -060059
Matt Spinler59521e82021-02-11 13:41:06 -060060 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 {
George Liucf5d0242022-04-14 14:39:47 +080068 lg2::error(
69 "Invalid JSON in associations condition entry in {PATH}. Skipping file.",
70 "PATH", path);
Matt Spinler59521e82021-02-11 13:41:06 -060071 continue;
72 }
Matt Spinler99e66a02019-03-06 14:11:22 -060073
Matt Spinler59521e82021-02-11 13:41:06 -060074 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 {
George Liucf5d0242022-04-14 14:39:47 +0800118 lg2::error(
119 "Invalid condition property value in {FILE}:",
120 "FILE", c.file);
121 throw std::runtime_error(
122 "Invalid condition property value");
Matt Spinler59521e82021-02-11 13:41:06 -0600123 }
124 }
125
126 _conditions.push_back(std::move(c));
127 }
128 }
129 }
130
131 return !_conditions.empty();
132}
133
134bool 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
Patrick Williamsd8fba8b2024-08-16 15:20:15 -0400145 auto interface = std::find_if(
146 object.begin(), object.end(), [&condition](const auto& i) {
147 return i.first == condition.interface;
148 });
Matt Spinler59521e82021-02-11 13:41:06 -0600149 if (interface == object.end())
150 {
151 continue;
152 }
153
Patrick Williamsd8fba8b2024-08-16 15:20:15 -0400154 auto property =
155 std::find_if(interface->second.begin(), interface->second.end(),
156 [&condition](const auto& p) {
157 return condition.property == p.first;
158 });
Matt Spinler59521e82021-02-11 13:41:06 -0600159 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
185bool 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
214void Manager::load(const nlohmann::json& json)
215{
Matt Spinler99e66a02019-03-06 14:11:22 -0600216 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 }
Matt Spinler852db672019-03-06 13:46:14 -0600247}
248
Brad Bishop104ccba2020-12-06 15:22:10 -0500249void Manager::createAssociations(const std::string& objectPath,
250 bool deferSignal)
Matt Spinler852db672019-03-06 13:46:14 -0600251{
Matt Spinlerc47ca582019-03-06 14:37:42 -0600252 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,
Brad Bishop104ccba2020-12-06 15:22:10 -0500277 reverseType, deferSignal);
Matt Spinlerc47ca582019-03-06 14:37:42 -0600278 }
279 }
280}
281
Patrick Williamsd8fba8b2024-08-16 15:20:15 -0400282void Manager::createAssociation(
283 const std::string& forwardPath, const std::string& forwardType,
284 const std::string& reversePath, const std::string& reverseType,
285 bool deferSignal)
Matt Spinlerc47ca582019-03-06 14:37:42 -0600286{
287 auto object = _associationIfaces.find(forwardPath);
288 if (object == _associationIfaces.end())
289 {
Patrick Williams1bb06232022-04-05 21:13:17 -0500290 auto a = std::make_unique<AssociationObject>(
291 _bus, forwardPath.c_str(), AssociationObject::action::defer_emit);
Matt Spinlerc47ca582019-03-06 14:37:42 -0600292
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));
Brad Bishop104ccba2020-12-06 15:22:10 -0500299 if (!deferSignal)
300 {
301 a->emit_object_added();
302 }
Matt Spinlerc47ca582019-03-06 14:37:42 -0600303 _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);
Brad Bishop104ccba2020-12-06 15:22:10 -0500310 object->second->associations(std::move(prop), deferSignal);
Matt Spinlerc47ca582019-03-06 14:37:42 -0600311 }
Matt Spinler852db672019-03-06 13:46:14 -0600312}
313} // namespace associations
314} // namespace manager
315} // namespace inventory
316} // namespace phosphor