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