1 #include "associations.hpp"
2 
3 #include <iostream>
4 
5 void removeAssociation(const std::string& sourcePath, const std::string& owner,
6                        sdbusplus::asio::object_server& server,
7                        AssociationMaps& assocMaps)
8 {
9     // Use associationOwners to find the association paths and endpoints
10     // that the passed in object path and service own.  Remove all of
11     // these endpoints from the actual association D-Bus objects, and if
12     // the endpoints property is then empty, the whole association object
13     // can be removed.  Note there can be multiple services that own an
14     // association, and also that sourcePath is the path of the object
15     // that contains the org.openbmc.Associations interface and not the
16     // association path itself.
17 
18     // Find the services that have associations for this object path
19     auto owners = assocMaps.owners.find(sourcePath);
20     if (owners == assocMaps.owners.end())
21     {
22         return;
23     }
24 
25     // Find the association paths and endpoints owned by this object
26     // path for this service.
27     auto assocs = owners->second.find(owner);
28     if (assocs == owners->second.end())
29     {
30         return;
31     }
32 
33     for (const auto& [assocPath, endpointsToRemove] : assocs->second)
34     {
35         removeAssociationEndpoints(server, assocPath, endpointsToRemove,
36                                    assocMaps);
37     }
38 
39     // Remove the associationOwners entries for this owning path/service.
40     owners->second.erase(assocs);
41     if (owners->second.empty())
42     {
43         assocMaps.owners.erase(owners);
44     }
45 
46     // If we were still waiting on the other side of this association to
47     // show up, cancel that wait.
48     removeFromPendingAssociations(sourcePath, assocMaps);
49 }
50 
51 void removeAssociationEndpoints(
52     sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
53     const boost::container::flat_set<std::string>& endpointsToRemove,
54     AssociationMaps& assocMaps)
55 {
56     auto assoc = assocMaps.ifaces.find(assocPath);
57     if (assoc == assocMaps.ifaces.end())
58     {
59         return;
60     }
61 
62     auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
63 
64     for (const auto& endpointToRemove : endpointsToRemove)
65     {
66         auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
67                            endpointToRemove);
68 
69         if (e != endpointsInDBus.end())
70         {
71             endpointsInDBus.erase(e);
72         }
73     }
74 
75     if (endpointsInDBus.empty())
76     {
77         objectServer.remove_interface(std::get<ifacePos>(assoc->second));
78         std::get<ifacePos>(assoc->second) = nullptr;
79         std::get<endpointsPos>(assoc->second).clear();
80     }
81     else
82     {
83         std::get<ifacePos>(assoc->second)
84             ->set_property("endpoints", endpointsInDBus);
85     }
86 }
87 
88 void checkAssociationEndpointRemoves(
89     const std::string& sourcePath, const std::string& owner,
90     const AssociationPaths& newAssociations,
91     sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
92 {
93     // Find the services that have associations on this path.
94     auto originalOwners = assocMaps.owners.find(sourcePath);
95     if (originalOwners == assocMaps.owners.end())
96     {
97         return;
98     }
99 
100     // Find the associations for this service
101     auto originalAssociations = originalOwners->second.find(owner);
102     if (originalAssociations == originalOwners->second.end())
103     {
104         return;
105     }
106 
107     // Compare the new endpoints versus the original endpoints, and
108     // remove any of the original ones that aren't in the new list.
109     for (const auto& [originalAssocPath, originalEndpoints] :
110          originalAssociations->second)
111     {
112         // Check if this source even still has each association that
113         // was there previously, and if not, remove all of its endpoints
114         // from the D-Bus endpoints property which will cause the whole
115         // association path to be removed if no endpoints remain.
116         auto newEndpoints = newAssociations.find(originalAssocPath);
117         if (newEndpoints == newAssociations.end())
118         {
119             removeAssociationEndpoints(objectServer, originalAssocPath,
120                                        originalEndpoints, assocMaps);
121         }
122         else
123         {
124             // The association is still there.  Check if the endpoints
125             // changed.
126             boost::container::flat_set<std::string> toRemove;
127 
128             for (auto& originalEndpoint : originalEndpoints)
129             {
130                 if (std::find(newEndpoints->second.begin(),
131                               newEndpoints->second.end(),
132                               originalEndpoint) == newEndpoints->second.end())
133                 {
134                     toRemove.emplace(originalEndpoint);
135                 }
136             }
137             if (!toRemove.empty())
138             {
139                 removeAssociationEndpoints(objectServer, originalAssocPath,
140                                            toRemove, assocMaps);
141             }
142         }
143     }
144 }
145 
146 void associationChanged(sdbusplus::asio::object_server& objectServer,
147                         const std::vector<Association>& associations,
148                         const std::string& path, const std::string& owner,
149                         const interface_map_type& interfaceMap,
150                         AssociationMaps& assocMaps)
151 {
152     AssociationPaths objects;
153 
154     for (const Association& association : associations)
155     {
156         std::string forward;
157         std::string reverse;
158         std::string endpoint;
159         std::tie(forward, reverse, endpoint) = association;
160 
161         if (endpoint.empty())
162         {
163             std::cerr << "Found invalid association on path " << path << "\n";
164             continue;
165         }
166 
167         // Can't create this association if the endpoint isn't on D-Bus.
168         if (interfaceMap.find(endpoint) == interfaceMap.end())
169         {
170             addPendingAssociation(endpoint, reverse, path, forward, owner,
171                                   assocMaps);
172             continue;
173         }
174 
175         if (forward.size())
176         {
177             objects[path + "/" + forward].emplace(endpoint);
178         }
179         if (reverse.size())
180         {
181             objects[endpoint + "/" + reverse].emplace(path);
182         }
183     }
184     for (const auto& object : objects)
185     {
186         // the mapper exposes the new association interface but intakes
187         // the old
188 
189         auto& iface = assocMaps.ifaces[object.first];
190         auto& i = std::get<ifacePos>(iface);
191         auto& endpoints = std::get<endpointsPos>(iface);
192 
193         // Only add new endpoints
194         for (auto& e : object.second)
195         {
196             if (std::find(endpoints.begin(), endpoints.end(), e) ==
197                 endpoints.end())
198             {
199                 endpoints.push_back(e);
200             }
201         }
202 
203         // If the interface already exists, only need to update
204         // the property value, otherwise create it
205         if (i)
206         {
207             i->set_property("endpoints", endpoints);
208         }
209         else
210         {
211             i = objectServer.add_interface(object.first,
212                                            XYZ_ASSOCIATION_INTERFACE);
213             i->register_property("endpoints", endpoints);
214             i->initialize();
215         }
216     }
217 
218     // Check for endpoints being removed instead of added
219     checkAssociationEndpointRemoves(path, owner, objects, objectServer,
220                                     assocMaps);
221 
222     if (!objects.empty())
223     {
224         // Update associationOwners with the latest info
225         auto a = assocMaps.owners.find(path);
226         if (a != assocMaps.owners.end())
227         {
228             auto o = a->second.find(owner);
229             if (o != a->second.end())
230             {
231                 o->second = std::move(objects);
232             }
233             else
234             {
235                 a->second.emplace(owner, std::move(objects));
236             }
237         }
238         else
239         {
240             boost::container::flat_map<std::string, AssociationPaths> owners;
241             owners.emplace(owner, std::move(objects));
242             assocMaps.owners.emplace(path, owners);
243         }
244     }
245 }
246 
247 void addPendingAssociation(const std::string& objectPath,
248                            const std::string& type,
249                            const std::string& endpointPath,
250                            const std::string& endpointType,
251                            const std::string& owner, AssociationMaps& assocMaps)
252 {
253     Association assoc{type, endpointType, endpointPath};
254 
255     auto p = assocMaps.pending.find(objectPath);
256     if (p == assocMaps.pending.end())
257     {
258         ExistingEndpoints ee;
259         ee.emplace_back(owner, std::move(assoc));
260         assocMaps.pending.emplace(objectPath, std::move(ee));
261     }
262     else
263     {
264         // Already waiting on this path for another association,
265         // so just add this endpoint and owner.
266         auto& endpoints = p->second;
267         auto e =
268             std::find_if(endpoints.begin(), endpoints.end(),
269                          [&assoc, &owner](const auto& endpoint) {
270                              return (std::get<ownerPos>(endpoint) == owner) &&
271                                     (std::get<assocPos>(endpoint) == assoc);
272                          });
273         if (e == endpoints.end())
274         {
275             endpoints.emplace_back(owner, std::move(assoc));
276         }
277     }
278 }
279 
280 void removeFromPendingAssociations(const std::string& endpointPath,
281                                    AssociationMaps& assocMaps)
282 {
283     auto assoc = assocMaps.pending.begin();
284     while (assoc != assocMaps.pending.end())
285     {
286         auto endpoint = assoc->second.begin();
287         while (endpoint != assoc->second.end())
288         {
289             auto& e = std::get<assocPos>(*endpoint);
290             if (std::get<reversePathPos>(e) == endpointPath)
291             {
292                 endpoint = assoc->second.erase(endpoint);
293                 continue;
294             }
295 
296             endpoint++;
297         }
298 
299         if (assoc->second.empty())
300         {
301             assoc = assocMaps.pending.erase(assoc);
302             continue;
303         }
304 
305         assoc++;
306     }
307 }
308