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