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