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 
47 void removeAssociationEndpoints(
48     sdbusplus::asio::object_server& objectServer, const std::string& assocPath,
49     const boost::container::flat_set<std::string>& endpointsToRemove,
50     AssociationMaps& assocMaps)
51 {
52     auto assoc = assocMaps.ifaces.find(assocPath);
53     if (assoc == assocMaps.ifaces.end())
54     {
55         return;
56     }
57 
58     auto& endpointsInDBus = std::get<endpointsPos>(assoc->second);
59 
60     for (const auto& endpointToRemove : endpointsToRemove)
61     {
62         auto e = std::find(endpointsInDBus.begin(), endpointsInDBus.end(),
63                            endpointToRemove);
64 
65         if (e != endpointsInDBus.end())
66         {
67             endpointsInDBus.erase(e);
68         }
69     }
70 
71     if (endpointsInDBus.empty())
72     {
73         objectServer.remove_interface(std::get<ifacePos>(assoc->second));
74         std::get<ifacePos>(assoc->second) = nullptr;
75         std::get<endpointsPos>(assoc->second).clear();
76     }
77     else
78     {
79         std::get<ifacePos>(assoc->second)
80             ->set_property("endpoints", endpointsInDBus);
81     }
82 }
83 
84 void checkAssociationEndpointRemoves(
85     const std::string& sourcePath, const std::string& owner,
86     const AssociationPaths& newAssociations,
87     sdbusplus::asio::object_server& objectServer, AssociationMaps& assocMaps)
88 {
89     // Find the services that have associations on this path.
90     auto originalOwners = assocMaps.owners.find(sourcePath);
91     if (originalOwners == assocMaps.owners.end())
92     {
93         return;
94     }
95 
96     // Find the associations for this service
97     auto originalAssociations = originalOwners->second.find(owner);
98     if (originalAssociations == originalOwners->second.end())
99     {
100         return;
101     }
102 
103     // Compare the new endpoints versus the original endpoints, and
104     // remove any of the original ones that aren't in the new list.
105     for (const auto& [originalAssocPath, originalEndpoints] :
106          originalAssociations->second)
107     {
108         // Check if this source even still has each association that
109         // was there previously, and if not, remove all of its endpoints
110         // from the D-Bus endpoints property which will cause the whole
111         // association path to be removed if no endpoints remain.
112         auto newEndpoints = newAssociations.find(originalAssocPath);
113         if (newEndpoints == newAssociations.end())
114         {
115             removeAssociationEndpoints(objectServer, originalAssocPath,
116                                        originalEndpoints, assocMaps);
117         }
118         else
119         {
120             // The association is still there.  Check if the endpoints
121             // changed.
122             boost::container::flat_set<std::string> toRemove;
123 
124             for (auto& originalEndpoint : originalEndpoints)
125             {
126                 if (std::find(newEndpoints->second.begin(),
127                               newEndpoints->second.end(),
128                               originalEndpoint) == newEndpoints->second.end())
129                 {
130                     toRemove.emplace(originalEndpoint);
131                 }
132             }
133             if (!toRemove.empty())
134             {
135                 removeAssociationEndpoints(objectServer, originalAssocPath,
136                                            toRemove, assocMaps);
137             }
138         }
139     }
140 }
141 
142 void associationChanged(sdbusplus::asio::object_server& objectServer,
143                         const std::vector<Association>& associations,
144                         const std::string& path, const std::string& owner,
145                         const interface_map_type& interfaceMap,
146                         AssociationMaps& assocMaps)
147 {
148     AssociationPaths objects;
149 
150     for (const Association& association : associations)
151     {
152         std::string forward;
153         std::string reverse;
154         std::string endpoint;
155         std::tie(forward, reverse, endpoint) = association;
156 
157         if (endpoint.empty())
158         {
159             std::cerr << "Found invalid association on path " << path << "\n";
160             continue;
161         }
162 
163         // Can't create this association if the endpoint isn't on D-Bus.
164         if (interfaceMap.find(endpoint) == interfaceMap.end())
165         {
166             addPendingAssociation(endpoint, reverse, path, forward, owner,
167                                   assocMaps);
168             continue;
169         }
170 
171         if (forward.size())
172         {
173             objects[path + "/" + forward].emplace(endpoint);
174         }
175         if (reverse.size())
176         {
177             objects[endpoint + "/" + reverse].emplace(path);
178         }
179     }
180     for (const auto& object : objects)
181     {
182         // the mapper exposes the new association interface but intakes
183         // the old
184 
185         auto& iface = assocMaps.ifaces[object.first];
186         auto& i = std::get<ifacePos>(iface);
187         auto& endpoints = std::get<endpointsPos>(iface);
188 
189         // Only add new endpoints
190         for (auto& e : object.second)
191         {
192             if (std::find(endpoints.begin(), endpoints.end(), e) ==
193                 endpoints.end())
194             {
195                 endpoints.push_back(e);
196             }
197         }
198 
199         // If the interface already exists, only need to update
200         // the property value, otherwise create it
201         if (i)
202         {
203             i->set_property("endpoints", endpoints);
204         }
205         else
206         {
207             i = objectServer.add_interface(object.first,
208                                            XYZ_ASSOCIATION_INTERFACE);
209             i->register_property("endpoints", endpoints);
210             i->initialize();
211         }
212     }
213 
214     // Check for endpoints being removed instead of added
215     checkAssociationEndpointRemoves(path, owner, objects, objectServer,
216                                     assocMaps);
217 
218     if (!objects.empty())
219     {
220         // Update associationOwners with the latest info
221         auto a = assocMaps.owners.find(path);
222         if (a != assocMaps.owners.end())
223         {
224             auto o = a->second.find(owner);
225             if (o != a->second.end())
226             {
227                 o->second = std::move(objects);
228             }
229             else
230             {
231                 a->second.emplace(owner, std::move(objects));
232             }
233         }
234         else
235         {
236             boost::container::flat_map<std::string, AssociationPaths> owners;
237             owners.emplace(owner, std::move(objects));
238             assocMaps.owners.emplace(path, owners);
239         }
240     }
241 }
242 
243 void addPendingAssociation(const std::string& objectPath,
244                            const std::string& type,
245                            const std::string& endpointPath,
246                            const std::string& endpointType,
247                            const std::string& owner, AssociationMaps& assocMaps)
248 {
249     Association assoc{type, endpointType, endpointPath};
250 
251     auto p = assocMaps.pending.find(objectPath);
252     if (p == assocMaps.pending.end())
253     {
254         ExistingEndpoints ee;
255         ee.emplace_back(owner, std::move(assoc));
256         assocMaps.pending.emplace(objectPath, std::move(ee));
257     }
258     else
259     {
260         // Already waiting on this path for another association,
261         // so just add this endpoint and owner.
262         auto& endpoints = p->second;
263         auto e =
264             std::find_if(endpoints.begin(), endpoints.end(),
265                          [&assoc, &owner](const auto& endpoint) {
266                              return (std::get<ownerPos>(endpoint) == owner) &&
267                                     (std::get<assocPos>(endpoint) == assoc);
268                          });
269         if (e == endpoints.end())
270         {
271             endpoints.emplace_back(owner, std::move(assoc));
272         }
273     }
274 }
275