1 #include "associations.hpp"
2 
3 #include <boost/algorithm/string/predicate.hpp>
4 #include <sdbusplus/exception.hpp>
5 
6 #include <iostream>
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 (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 (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 endpoint;
194         std::tie(forward, reverse, endpoint) = association;
195 
196         if (endpoint.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(endpoint) == interfaceMap.end())
204         {
205             addPendingAssociation(endpoint, reverse, path, forward, owner,
206                                   assocMaps);
207             continue;
208         }
209 
210         if (forward.size())
211         {
212             objects[path + "/" + forward].emplace(endpoint);
213         }
214         if (reverse.size())
215         {
216             objects[endpoint + "/" + 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::exception& 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             fprintf(stderr,
419                     "Error adding association: assocPath %s, endpointPath %s, "
420                     "what: %s\n",
421                     assocPath.c_str(), endpointPath.c_str(), e.what());
422         }
423 
424         // Not pending anymore
425         endpoint = pending->second.erase(endpoint);
426     }
427 
428     if (pending->second.empty())
429     {
430         assocMaps.pending.erase(objectPath);
431     }
432 }
433 
434 void findAssociations(const std::string& endpointPath,
435                       AssociationMaps& assocMaps,
436                       FindAssocResults& associationData)
437 {
438     for (const auto& [sourcePath, owners] : assocMaps.owners)
439     {
440         for (const auto& [owner, assocs] : owners)
441         {
442             for (const auto& [assocPath, endpoints] : assocs)
443             {
444                 if (std::find(endpoints.begin(), endpoints.end(),
445                               endpointPath) != endpoints.end())
446                 {
447                     // assocPath is <path>/<type> which tells us what is on the
448                     // other side of the association.
449                     auto pos = assocPath.rfind('/');
450                     auto otherPath = assocPath.substr(0, pos);
451                     auto otherType = assocPath.substr(pos + 1);
452 
453                     // Now we need to find the endpointPath/<type> ->
454                     // [otherPath] entry so that we can get the type for
455                     // endpointPath's side of the assoc.  Do this by finding
456                     // otherPath as an endpoint, and also checking for
457                     // 'endpointPath/*' as the key.
458                     auto a = std::find_if(
459                         assocs.begin(), assocs.end(),
460                         [&endpointPath, &otherPath](const auto& ap) {
461                             const auto& endpoints = ap.second;
462                             auto endpoint = std::find(
463                                 endpoints.begin(), endpoints.end(), otherPath);
464                             if (endpoint != endpoints.end())
465                             {
466                                 return boost::starts_with(ap.first,
467                                                           endpointPath + '/');
468                             }
469                             return false;
470                         });
471 
472                     if (a != assocs.end())
473                     {
474                         // Pull out the type from endpointPath/<type>
475                         pos = a->first.rfind('/');
476                         auto thisType = a->first.substr(pos + 1);
477 
478                         // Now we know the full association:
479                         // endpointPath/thisType -> otherPath/otherType
480                         Association association{thisType, otherType, otherPath};
481                         associationData.emplace_back(owner, association);
482                     }
483                 }
484             }
485         }
486     }
487 }
488 
489 /** @brief Remove an endpoint for a particular association from D-Bus.
490  *
491  * If the last endpoint is gone, remove the whole association interface,
492  * otherwise just update the D-Bus endpoints property.
493  *
494  * @param[in] assocPath     - the association path
495  * @param[in] endpointPath  - the endpoint path to find and remove
496  * @param[in,out] assocMaps - the association maps
497  * @param[in,out] server    - sdbus system object
498  */
499 void removeAssociationIfacesEntry(const std::string& assocPath,
500                                   const std::string& endpointPath,
501                                   AssociationMaps& assocMaps,
502                                   sdbusplus::asio::object_server& server)
503 {
504     auto assoc = assocMaps.ifaces.find(assocPath);
505     if (assoc != assocMaps.ifaces.end())
506     {
507         auto& endpoints = std::get<endpointsPos>(assoc->second);
508         auto e = std::find(endpoints.begin(), endpoints.end(), endpointPath);
509         if (e != endpoints.end())
510         {
511             endpoints.erase(e);
512 
513             if (endpoints.empty())
514             {
515                 server.remove_interface(std::get<ifacePos>(assoc->second));
516                 std::get<ifacePos>(assoc->second) = nullptr;
517             }
518             else
519             {
520                 std::get<ifacePos>(assoc->second)
521                     ->set_property("endpoints", endpoints);
522             }
523         }
524     }
525 }
526 
527 /** @brief Remove an endpoint from the association owners map.
528  *
529  * For a specific association path and owner, remove the endpoint.
530  * Remove all remaining artifacts of that endpoint in the owners map
531  * based on what frees up after the erase.
532  *
533  * @param[in] assocPath     - the association object path
534  * @param[in] endpointPath  - the endpoint object path
535  * @param[in] owner         - the owner of the association
536  * @param[in,out] assocMaps - the association maps
537  * @param[in,out] server    - sdbus system object
538  */
539 void removeAssociationOwnersEntry(const std::string& assocPath,
540                                   const std::string& endpointPath,
541                                   const std::string& owner,
542                                   AssociationMaps& assocMaps,
543                                   sdbusplus::asio::object_server&)
544 {
545     auto sources = assocMaps.owners.begin();
546     while (sources != assocMaps.owners.end())
547     {
548         auto owners = sources->second.find(owner);
549         if (owners != sources->second.end())
550         {
551             auto entry = owners->second.find(assocPath);
552             if (entry != owners->second.end())
553             {
554                 auto e = std::find(entry->second.begin(), entry->second.end(),
555                                    endpointPath);
556                 if (e != entry->second.end())
557                 {
558                     entry->second.erase(e);
559                     if (entry->second.empty())
560                     {
561                         owners->second.erase(entry);
562                     }
563                 }
564             }
565 
566             if (owners->second.empty())
567             {
568                 sources->second.erase(owners);
569             }
570         }
571 
572         if (sources->second.empty())
573         {
574             sources = assocMaps.owners.erase(sources);
575             continue;
576         }
577         sources++;
578     }
579 }
580 
581 void moveAssociationToPending(const std::string& endpointPath,
582                               AssociationMaps& assocMaps,
583                               sdbusplus::asio::object_server& server)
584 {
585     FindAssocResults associationData;
586 
587     // Check which associations this path is an endpoint of, and
588     // then add them to the pending associations map and remove
589     // the associations objects.
590     findAssociations(endpointPath, assocMaps, associationData);
591 
592     for (const auto& [owner, association] : associationData)
593     {
594         const auto& forwardPath = endpointPath;
595         const auto& forwardType = std::get<forwardTypePos>(association);
596         const auto& reversePath = std::get<reversePathPos>(association);
597         const auto& reverseType = std::get<reverseTypePos>(association);
598 
599         addPendingAssociation(forwardPath, forwardType, reversePath,
600                               reverseType, owner, assocMaps);
601 
602         // Remove both sides of the association from assocMaps.ifaces
603         removeAssociationIfacesEntry(forwardPath + '/' + forwardType,
604                                      reversePath, assocMaps, server);
605         removeAssociationIfacesEntry(reversePath + '/' + reverseType,
606                                      forwardPath, assocMaps, server);
607 
608         // Remove both sides of the association from assocMaps.owners
609         removeAssociationOwnersEntry(forwardPath + '/' + forwardType,
610                                      reversePath, owner, assocMaps, server);
611         removeAssociationOwnersEntry(reversePath + '/' + reverseType,
612                                      forwardPath, owner, assocMaps, server);
613     }
614 }
615