1 #include "association_manager.hpp"
2 
3 #include <phosphor-logging/log.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 using namespace phosphor::logging;
17 namespace fs = std::filesystem;
18 
19 Manager::Manager(sdbusplus::bus::bus& bus, const std::string& jsonPath) :
20     _bus(bus), _jsonFile(jsonPath)
21 {
22     // 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     }
33 }
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  */
41 void throwIfZero(int num)
42 {
43     if (!num)
44     {
45         throw std::invalid_argument("Invalid empty field in JSON");
46     }
47 }
48 
49 bool Manager::loadConditions()
50 {
51     auto dir = _jsonFile.parent_path();
52 
53     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);
60 
61             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                 }
75 
76                 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 
136 bool 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 
187 bool 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 
216 void Manager::load(const nlohmann::json& json)
217 {
218     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     }
249 }
250 
251 void Manager::createAssociations(const std::string& objectPath,
252                                  bool deferSignal)
253 {
254     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,
279                               reverseType, deferSignal);
280         }
281     }
282 }
283 
284 void Manager::createAssociation(const std::string& forwardPath,
285                                 const std::string& forwardType,
286                                 const std::string& reversePath,
287                                 const std::string& reverseType,
288                                 bool deferSignal)
289 {
290     auto object = _associationIfaces.find(forwardPath);
291     if (object == _associationIfaces.end())
292     {
293         auto a = std::make_unique<AssociationObject>(
294             _bus, forwardPath.c_str(), AssociationObject::action::defer_emit);
295 
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));
302         if (!deferSignal)
303         {
304             a->emit_object_added();
305         }
306         _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);
313         object->second->associations(std::move(prop), deferSignal);
314     }
315 }
316 } // namespace associations
317 } // namespace manager
318 } // namespace inventory
319 } // namespace phosphor
320