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