1 #include "associations.hpp"
2 
3 #include <boost/algorithm/string/predicate.hpp>
4 #include <iostream>
5 
6 void removeAssociation(const std::string& sourcePath, const std::string& owner,
7                        sdbusplus::asio::object_server& server,
8                        AssociationMaps& assocMaps)
9 {
10     // Use associationOwners to find the association paths and endpoints
11     // that the passed in object path and service own.  Remove all of
12     // these endpoints from the actual association D-Bus objects, and if
13     // the endpoints property is then empty, the whole association object
14     // can be removed.  Note there can be multiple services that own an
15     // association, and also that sourcePath is the path of the object
16     // that contains the org.openbmc.Associations interface and not the
17     // association path itself.
18 
19     // Find the services that have associations for this object path
20     auto owners = assocMaps.owners.find(sourcePath);
21     if (owners == assocMaps.owners.end())
22     {
23         return;
24     }
25 
26     // Find the association paths and endpoints owned by this object
27     // path for this service.
28     auto assocs = owners->second.find(owner);
29     if (assocs == owners->second.end())
30     {
31         return;
32     }
33 
34     for (const auto& [assocPath, endpointsToRemove] : assocs->second)
35     {
36         removeAssociationEndpoints(server, assocPath, endpointsToRemove,
37                                    assocMaps);
38     }
39 
40     // Remove the associationOwners entries for this owning path/service.
41     owners->second.erase(assocs);
42     if (owners->second.empty())
43     {
44         assocMaps.owners.erase(owners);
45     }
46 
47     // If we were still waiting on the other side of this association to
48     // show up, cancel that wait.
49     removeFromPendingAssociations(sourcePath, assocMaps);
50 }
51 
52 void removeAssociationEndpoints(
53     sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
54     const boost::container::flat_set<std::string>& endpointsToRemove,
55     AssociationMaps& assocMaps)
56 {
57     auto assoc = assocMaps.ifaces.find(assocPath);
58     if (assoc == assocMaps.ifaces.end())
59     {
60         return;
61     }
62 
63     auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
64 
65     for (const auto& endpointToRemove : endpointsToRemove)
66     {
67         auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
68                            endpointToRemove);
69 
70         if (e != endpointsInDBus.end())
71         {
72             endpointsInDBus.erase(e);
73         }
74     }
75 
76     if (endpointsInDBus.empty())
77     {
78         objectServer.remove_interface(std::get<ifacePos>(assoc->second));
79         std::get<ifacePos>(assoc->second) = nullptr;
80         std::get<endpointsPos>(assoc->second).clear();
81     }
82     else
83     {
84         std::get<ifacePos>(assoc->second)
85             ->set_property("endpoints", endpointsInDBus);
86     }
87 }
88 
89 void checkAssociationEndpointRemoves(
90     const std::string& sourcePath, const std::string& owner,
91     const AssociationPaths& newAssociations,
92     sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
93 {
94     // Find the services that have associations on this path.
95     auto originalOwners = assocMaps.owners.find(sourcePath);
96     if (originalOwners == assocMaps.owners.end())
97     {
98         return;
99     }
100 
101     // Find the associations for this service
102     auto originalAssociations = originalOwners->second.find(owner);
103     if (originalAssociations == originalOwners->second.end())
104     {
105         return;
106     }
107 
108     // Compare the new endpoints versus the original endpoints, and
109     // remove any of the original ones that aren't in the new list.
110     for (const auto& [originalAssocPath, originalEndpoints] :
111          originalAssociations->second)
112     {
113         // Check if this source even still has each association that
114         // was there previously, and if not, remove all of its endpoints
115         // from the D-Bus endpoints property which will cause the whole
116         // association path to be removed if no endpoints remain.
117         auto newEndpoints = newAssociations.find(originalAssocPath);
118         if (newEndpoints == newAssociations.end())
119         {
120             removeAssociationEndpoints(objectServer, originalAssocPath,
121                                        originalEndpoints, assocMaps);
122         }
123         else
124         {
125             // The association is still there.  Check if the endpoints
126             // changed.
127             boost::container::flat_set<std::string> toRemove;
128 
129             for (auto& originalEndpoint : originalEndpoints)
130             {
131                 if (std::find(newEndpoints->second.begin(),
132                               newEndpoints->second.end(),
133                               originalEndpoint) == newEndpoints->second.end())
134                 {
135                     toRemove.emplace(originalEndpoint);
136                 }
137             }
138             if (!toRemove.empty())
139             {
140                 removeAssociationEndpoints(objectServer, originalAssocPath,
141                                            toRemove, assocMaps);
142             }
143         }
144     }
145 }
146 
147 void addEndpointsToAssocIfaces(
148     sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
149     const boost::container::flat_set<std::string>& endpointPaths,
150     AssociationMaps& assocMaps)
151 {
152     auto& iface = assocMaps.ifaces[assocPath];
153     auto& i = std::get<ifacePos>(iface);
154     auto& endpoints = std::get<endpointsPos>(iface);
155 
156     // Only add new endpoints
157     for (auto& e : endpointPaths)
158     {
159         if (std::find(endpoints.begin(), endpoints.end(), e) == endpoints.end())
160         {
161             endpoints.push_back(e);
162         }
163     }
164 
165     // If the interface already exists, only need to update
166     // the property value, otherwise create it
167     if (i)
168     {
169         i->set_property("endpoints", endpoints);
170     }
171     else
172     {
173         i = objectServer.add_interface(assocPath, XYZ_ASSOCIATION_INTERFACE);
174         i->register_property("endpoints", endpoints);
175         i->initialize();
176     }
177 }
178 
179 void associationChanged(sdbusplus::asio::object_server& objectServer,
180                         const std::vector<Association>& associations,
181                         const std::string& path, const std::string& owner,
182                         const interface_map_type& interfaceMap,
183                         AssociationMaps& assocMaps)
184 {
185     AssociationPaths objects;
186 
187     for (const Association& association : associations)
188     {
189         std::string forward;
190         std::string reverse;
191         std::string endpoint;
192         std::tie(forward, reverse, endpoint) = association;
193 
194         if (endpoint.empty())
195         {
196             std::cerr << "Found invalid association on path " << path << "\n";
197             continue;
198         }
199 
200         // Can't create this association if the endpoint isn't on D-Bus.
201         if (interfaceMap.find(endpoint) == interfaceMap.end())
202         {
203             addPendingAssociation(endpoint, reverse, path, forward, owner,
204                                   assocMaps);
205             continue;
206         }
207 
208         if (forward.size())
209         {
210             objects[path + "/" + forward].emplace(endpoint);
211         }
212         if (reverse.size())
213         {
214             objects[endpoint + "/" + reverse].emplace(path);
215         }
216     }
217     for (const auto& object : objects)
218     {
219         addEndpointsToAssocIfaces(objectServer, object.first, object.second,
220                                   assocMaps);
221     }
222 
223     // Check for endpoints being removed instead of added
224     checkAssociationEndpointRemoves(path, owner, objects, objectServer,
225                                     assocMaps);
226 
227     if (!objects.empty())
228     {
229         // Update associationOwners with the latest info
230         auto a = assocMaps.owners.find(path);
231         if (a != assocMaps.owners.end())
232         {
233             auto o = a->second.find(owner);
234             if (o != a->second.end())
235             {
236                 o->second = std::move(objects);
237             }
238             else
239             {
240                 a->second.emplace(owner, std::move(objects));
241             }
242         }
243         else
244         {
245             boost::container::flat_map<std::string, AssociationPaths> owners;
246             owners.emplace(owner, std::move(objects));
247             assocMaps.owners.emplace(path, owners);
248         }
249     }
250 }
251 
252 void addPendingAssociation(const std::string& objectPath,
253                            const std::string& type,
254                            const std::string& endpointPath,
255                            const std::string& endpointType,
256                            const std::string& owner, AssociationMaps& assocMaps)
257 {
258     Association assoc{type, endpointType, endpointPath};
259 
260     auto p = assocMaps.pending.find(objectPath);
261     if (p == assocMaps.pending.end())
262     {
263         ExistingEndpoints ee;
264         ee.emplace_back(owner, std::move(assoc));
265         assocMaps.pending.emplace(objectPath, std::move(ee));
266     }
267     else
268     {
269         // Already waiting on this path for another association,
270         // so just add this endpoint and owner.
271         auto& endpoints = p->second;
272         auto e =
273             std::find_if(endpoints.begin(), endpoints.end(),
274                          [&assoc, &owner](const auto& endpoint) {
275                              return (std::get<ownerPos>(endpoint) == owner) &&
276                                     (std::get<assocPos>(endpoint) == assoc);
277                          });
278         if (e == endpoints.end())
279         {
280             endpoints.emplace_back(owner, std::move(assoc));
281         }
282     }
283 }
284 
285 void removeFromPendingAssociations(const std::string& endpointPath,
286                                    AssociationMaps& assocMaps)
287 {
288     auto assoc = assocMaps.pending.begin();
289     while (assoc != assocMaps.pending.end())
290     {
291         auto endpoint = assoc->second.begin();
292         while (endpoint != assoc->second.end())
293         {
294             auto& e = std::get<assocPos>(*endpoint);
295             if (std::get<reversePathPos>(e) == endpointPath)
296             {
297                 endpoint = assoc->second.erase(endpoint);
298                 continue;
299             }
300 
301             endpoint++;
302         }
303 
304         if (assoc->second.empty())
305         {
306             assoc = assocMaps.pending.erase(assoc);
307             continue;
308         }
309 
310         assoc++;
311     }
312 }
313 
314 void addSingleAssociation(sdbusplus::asio::object_server& server,
315                           const std::string& assocPath,
316                           const std::string& endpoint, const std::string& owner,
317                           const std::string& ownerPath,
318                           AssociationMaps& assocMaps)
319 {
320     boost::container::flat_set<std::string> endpoints{endpoint};
321 
322     addEndpointsToAssocIfaces(server, assocPath, endpoints, assocMaps);
323 
324     AssociationPaths objects;
325     boost::container::flat_set e{endpoint};
326     objects.emplace(assocPath, e);
327 
328     auto a = assocMaps.owners.find(ownerPath);
329     if (a != assocMaps.owners.end())
330     {
331         auto o = a->second.find(owner);
332         if (o != a->second.end())
333         {
334             auto p = o->second.find(assocPath);
335             if (p != o->second.end())
336             {
337                 p->second.emplace(endpoint);
338             }
339             else
340             {
341                 o->second.emplace(assocPath, e);
342             }
343         }
344         else
345         {
346             a->second.emplace(owner, std::move(objects));
347         }
348     }
349     else
350     {
351         boost::container::flat_map<std::string, AssociationPaths> owners;
352         owners.emplace(owner, std::move(objects));
353         assocMaps.owners.emplace(endpoint, owners);
354     }
355 }
356 
357 void checkIfPendingAssociation(const std::string& objectPath,
358                                const interface_map_type& interfaceMap,
359                                AssociationMaps& assocMaps,
360                                sdbusplus::asio::object_server& server)
361 {
362     auto pending = assocMaps.pending.find(objectPath);
363     if (pending == assocMaps.pending.end())
364     {
365         return;
366     }
367 
368     if (interfaceMap.find(objectPath) == interfaceMap.end())
369     {
370         return;
371     }
372 
373     auto endpoint = pending->second.begin();
374 
375     while (endpoint != pending->second.end())
376     {
377         const auto& e = std::get<assocPos>(*endpoint);
378 
379         // Ensure the other side of the association still exists
380         if (interfaceMap.find(std::get<reversePathPos>(e)) ==
381             interfaceMap.end())
382         {
383             endpoint++;
384             continue;
385         }
386 
387         // Add both sides of the association:
388         //  objectPath/forwardType and reversePath/reverseType
389         //
390         // The ownerPath is the reversePath - i.e. the endpoint that
391         // is on D-Bus and owns the org.openbmc.Associations iface.
392         //
393         const auto& ownerPath = std::get<reversePathPos>(e);
394         const auto& owner = std::get<ownerPos>(*endpoint);
395 
396         auto assocPath = objectPath + '/' + std::get<forwardTypePos>(e);
397         auto endpointPath = ownerPath;
398 
399         addSingleAssociation(server, assocPath, endpointPath, owner, ownerPath,
400                              assocMaps);
401 
402         // Now the reverse direction (still the same owner and ownerPath)
403         assocPath = endpointPath + '/' + std::get<reverseTypePos>(e);
404         endpointPath = objectPath;
405         addSingleAssociation(server, assocPath, endpointPath, owner, ownerPath,
406                              assocMaps);
407 
408         // Not pending anymore
409         endpoint = pending->second.erase(endpoint);
410     }
411 
412     if (pending->second.empty())
413     {
414         assocMaps.pending.erase(objectPath);
415     }
416 }
417 
418 void findAssociations(const std::string& endpointPath,
419                       AssociationMaps& assocMaps,
420                       FindAssocResults& associationData)
421 {
422     for (const auto& [sourcePath, owners] : assocMaps.owners)
423     {
424         for (const auto& [owner, assocs] : owners)
425         {
426             for (const auto& [assocPath, endpoints] : assocs)
427             {
428                 if (std::find(endpoints.begin(), endpoints.end(),
429                               endpointPath) != endpoints.end())
430                 {
431                     // assocPath is <path>/<type> which tells us what is on the
432                     // other side of the association.
433                     auto pos = assocPath.rfind('/');
434                     auto otherPath = assocPath.substr(0, pos);
435                     auto otherType = assocPath.substr(pos + 1);
436 
437                     // Now we need to find the endpointPath/<type> ->
438                     // [otherPath] entry so that we can get the type for
439                     // endpointPath's side of the assoc.  Do this by finding
440                     // otherPath as an endpoint, and also checking for
441                     // 'endpointPath/*' as the key.
442                     auto a = std::find_if(
443                         assocs.begin(), assocs.end(),
444                         [&endpointPath, &otherPath](const auto& ap) {
445                             const auto& endpoints = ap.second;
446                             auto endpoint = std::find(
447                                 endpoints.begin(), endpoints.end(), otherPath);
448                             if (endpoint != endpoints.end())
449                             {
450                                 return boost::starts_with(ap.first,
451                                                           endpointPath + '/');
452                             }
453                             return false;
454                         });
455 
456                     if (a != assocs.end())
457                     {
458                         // Pull out the type from endpointPath/<type>
459                         pos = a->first.rfind('/');
460                         auto thisType = a->first.substr(pos + 1);
461 
462                         // Now we know the full association:
463                         // endpointPath/thisType -> otherPath/otherType
464                         Association association{thisType, otherType, otherPath};
465                         associationData.emplace_back(owner, association);
466                     }
467                 }
468             }
469         }
470     }
471 }
472 
473 /** @brief Remove an endpoint for a particular association from D-Bus.
474  *
475  * If the last endpoint is gone, remove the whole association interface,
476  * otherwise just update the D-Bus endpoints property.
477  *
478  * @param[in] assocPath     - the association path
479  * @param[in] endpointPath  - the endpoint path to find and remove
480  * @param[in,out] assocMaps - the association maps
481  * @param[in,out] server    - sdbus system object
482  */
483 void removeAssociationIfacesEntry(const std::string& assocPath,
484                                   const std::string& endpointPath,
485                                   AssociationMaps& assocMaps,
486                                   sdbusplus::asio::object_server& server)
487 {
488     auto assoc = assocMaps.ifaces.find(assocPath);
489     if (assoc != assocMaps.ifaces.end())
490     {
491         auto& endpoints = std::get<endpointsPos>(assoc->second);
492         auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath);
493         if (e != endpoints.end())
494         {
495             endpoints.erase(e);
496 
497             if (endpoints.empty())
498             {
499                 server.remove_interface(std::get<ifacePos>(assoc->second));
500                 std::get<ifacePos>(assoc->second) = nullptr;
501             }
502             else
503             {
504                 std::get<ifacePos>(assoc->second)
505                     ->set_property("endpoints", endpoints);
506             }
507         }
508     }
509 }
510 
511 /** @brief Remove an endpoint from the association owners map.
512  *
513  * For a specific association path and owner, remove the endpoint.
514  * Remove all remaining artifacts of that endpoint in the owners map
515  * based on what frees up after the erase.
516  *
517  * @param[in] assocPath     - the association object path
518  * @param[in] endpointPath  - the endpoint object path
519  * @param[in] owner         - the owner of the association
520  * @param[in,out] assocMaps - the association maps
521  * @param[in,out] server    - sdbus system object
522  */
523 void removeAssociationOwnersEntry(const std::string& assocPath,
524                                   const std::string& endpointPath,
525                                   const std::string& owner,
526                                   AssociationMaps& assocMaps,
527                                   sdbusplus::asio::object_server& server)
528 {
529     auto sources = assocMaps.owners.begin();
530     while (sources != assocMaps.owners.end())
531     {
532         auto owners = sources->second.find(owner);
533         if (owners != sources->second.end())
534         {
535             auto entry = owners->second.find(assocPath);
536             if (entry != owners->second.end())
537             {
538                 auto e = std::find(entry->second.begin(), entry->second.end(),
539                                    endpointPath);
540                 if (e != entry->second.end())
541                 {
542                     entry->second.erase(e);
543                     if (entry->second.empty())
544                     {
545                         owners->second.erase(entry);
546                     }
547                 }
548             }
549 
550             if (owners->second.empty())
551             {
552                 sources->second.erase(owners);
553             }
554         }
555 
556         if (sources->second.empty())
557         {
558             sources = assocMaps.owners.erase(sources);
559             continue;
560         }
561         sources++;
562     }
563 }
564 
565 void moveAssociationToPending(const std::string& endpointPath,
566                               AssociationMaps& assocMaps,
567                               sdbusplus::asio::object_server& server)
568 {
569     FindAssocResults associationData;
570 
571     // Check which associations this path is an endpoint of, and
572     // then add them to the pending associations map and remove
573     // the associations objects.
574     findAssociations(endpointPath, assocMaps, associationData);
575 
576     for (const auto& [owner, association] : associationData)
577     {
578         const auto& forwardPath = endpointPath;
579         const auto& forwardType = std::get<forwardTypePos>(association);
580         const auto& reversePath = std::get<reversePathPos>(association);
581         const auto& reverseType = std::get<reverseTypePos>(association);
582 
583         addPendingAssociation(forwardPath, forwardType, reversePath,
584                               reverseType, owner, assocMaps);
585 
586         // Remove both sides of the association from assocMaps.ifaces
587         removeAssociationIfacesEntry(forwardPath + '/' + forwardType,
588                                      reversePath, assocMaps, server);
589         removeAssociationIfacesEntry(reversePath + '/' + reverseType,
590                                      forwardPath, assocMaps, server);
591 
592         // Remove both sides of the association from assocMaps.owners
593         removeAssociationOwnersEntry(forwardPath + '/' + forwardType,
594                                      reversePath, owner, assocMaps, server);
595         removeAssociationOwnersEntry(reversePath + '/' + reverseType,
596                                      forwardPath, owner, assocMaps, server);
597     }
598 }
599