xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 8c243629)
1 #include "associations.hpp"
2 #include "processing.hpp"
3 #include "types.hpp"
4 
5 #include <tinyxml2.h>
6 
7 #include <boost/asio/io_context.hpp>
8 #include <boost/asio/signal_set.hpp>
9 #include <boost/container/flat_map.hpp>
10 #include <sdbusplus/asio/connection.hpp>
11 #include <sdbusplus/asio/object_server.hpp>
12 #include <xyz/openbmc_project/Common/error.hpp>
13 
14 #include <atomic>
15 #include <chrono>
16 #include <exception>
17 #include <iomanip>
18 #include <iostream>
19 #include <string>
20 #include <string_view>
21 #include <utility>
22 
23 AssociationMaps associationMaps;
24 
25 void updateOwners(sdbusplus::asio::connection* conn,
26                   boost::container::flat_map<std::string, std::string>& owners,
27                   const std::string& newObject)
28 {
29     if (newObject.starts_with(":"))
30     {
31         return;
32     }
33     conn->async_method_call(
34         [&, newObject](const boost::system::error_code ec,
35                        const std::string& nameOwner) {
36             if (ec)
37             {
38                 std::cerr << "Error getting owner of " << newObject << " : "
39                           << ec << "\n";
40                 return;
41             }
42             owners[nameOwner] = newObject;
43         },
44         "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
45         newObject);
46 }
47 
48 void sendIntrospectionCompleteSignal(sdbusplus::asio::connection* systemBus,
49                                      const std::string& processName)
50 {
51     // TODO(ed) This signal doesn't get exposed properly in the
52     // introspect right now.  Find out how to register signals in
53     // sdbusplus
54     sdbusplus::message_t m = systemBus->new_signal(
55         "/xyz/openbmc_project/object_mapper",
56         "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete");
57     m.append(processName);
58     m.signal_send();
59 }
60 
61 struct InProgressIntrospect
62 {
63     InProgressIntrospect() = delete;
64     InProgressIntrospect(const InProgressIntrospect&) = delete;
65     InProgressIntrospect(InProgressIntrospect&&) = delete;
66     InProgressIntrospect& operator=(const InProgressIntrospect&) = delete;
67     InProgressIntrospect& operator=(InProgressIntrospect&&) = delete;
68     InProgressIntrospect(
69         sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
70         const std::string& processName, AssociationMaps& am
71 #ifdef MAPPER_ENABLE_DEBUG
72         ,
73         std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
74             globalStartTime
75 #endif
76         ) :
77         systemBus(systemBus),
78         io(io), processName(processName), assocMaps(am)
79 #ifdef MAPPER_ENABLE_DEBUG
80         ,
81         globalStartTime(std::move(globalStartTime)),
82         processStartTime(std::chrono::steady_clock::now())
83 #endif
84     {}
85     ~InProgressIntrospect()
86     {
87         try
88         {
89             sendIntrospectionCompleteSignal(systemBus, processName);
90 #ifdef MAPPER_ENABLE_DEBUG
91             std::chrono::duration<float> diff =
92                 std::chrono::steady_clock::now() - processStartTime;
93             std::cout << std::setw(50) << processName << " scan took "
94                       << diff.count() << " seconds\n";
95 
96             // If we're the last outstanding caller globally, calculate the
97             // time it took
98             if (globalStartTime != nullptr && globalStartTime.use_count() == 1)
99             {
100                 diff = std::chrono::steady_clock::now() - *globalStartTime;
101                 std::cout << "Total scan took " << diff.count()
102                           << " seconds to complete\n";
103             }
104 #endif
105         }
106         catch (const std::exception& e)
107         {
108             std::cerr
109                 << "Terminating, unhandled exception while introspecting: "
110                 << e.what() << "\n";
111             std::terminate();
112         }
113         catch (...)
114         {
115             std::cerr
116                 << "Terminating, unhandled exception while introspecting\n";
117             std::terminate();
118         }
119     }
120     sdbusplus::asio::connection* systemBus;
121     boost::asio::io_context& io;
122     std::string processName;
123     AssociationMaps& assocMaps;
124 #ifdef MAPPER_ENABLE_DEBUG
125     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
126         globalStartTime;
127     std::chrono::time_point<std::chrono::steady_clock> processStartTime;
128 #endif
129 };
130 
131 void doAssociations(sdbusplus::asio::connection* systemBus,
132                     InterfaceMapType& interfaceMap,
133                     sdbusplus::asio::object_server& objectServer,
134                     const std::string& processName, const std::string& path,
135                     int timeoutRetries = 0)
136 {
137     constexpr int maxTimeoutRetries = 3;
138     systemBus->async_method_call(
139         [&objectServer, path, processName, &interfaceMap, systemBus,
140          timeoutRetries](
141             const boost::system::error_code ec,
142             const std::variant<std::vector<Association>>& variantAssociations) {
143             if (ec)
144             {
145                 if (ec.value() == boost::system::errc::timed_out &&
146                     timeoutRetries < maxTimeoutRetries)
147                 {
148                     doAssociations(systemBus, interfaceMap, objectServer,
149                                    processName, path, timeoutRetries + 1);
150                     return;
151                 }
152                 std::cerr << "Error getting associations from " << path << "\n";
153             }
154             std::vector<Association> associations =
155                 std::get<std::vector<Association>>(variantAssociations);
156             associationChanged(objectServer, associations, path, processName,
157                                interfaceMap, associationMaps);
158         },
159         processName, path, "org.freedesktop.DBus.Properties", "Get",
160         assocDefsInterface, assocDefsProperty);
161 }
162 
163 void doIntrospect(sdbusplus::asio::connection* systemBus,
164                   const std::shared_ptr<InProgressIntrospect>& transaction,
165                   InterfaceMapType& interfaceMap,
166                   sdbusplus::asio::object_server& objectServer,
167                   const std::string& path, int timeoutRetries = 0)
168 {
169     constexpr int maxTimeoutRetries = 3;
170     systemBus->async_method_call(
171         [&interfaceMap, &objectServer, transaction, path, systemBus,
172          timeoutRetries](const boost::system::error_code ec,
173                          const std::string& introspectXml) {
174             if (ec)
175             {
176                 if (ec.value() == boost::system::errc::timed_out &&
177                     timeoutRetries < maxTimeoutRetries)
178                 {
179                     doIntrospect(systemBus, transaction, interfaceMap,
180                                  objectServer, path, timeoutRetries + 1);
181                     return;
182                 }
183                 std::cerr << "Introspect call failed with error: " << ec << ", "
184                           << ec.message()
185                           << " on process: " << transaction->processName
186                           << " path: " << path << "\n";
187                 return;
188             }
189 
190             tinyxml2::XMLDocument doc;
191 
192             tinyxml2::XMLError e = doc.Parse(introspectXml.c_str());
193             if (e != tinyxml2::XMLError::XML_SUCCESS)
194             {
195                 std::cerr << "XML parsing failed\n";
196                 return;
197             }
198 
199             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
200             if (pRoot == nullptr)
201             {
202                 std::cerr << "XML document did not contain any data\n";
203                 return;
204             }
205             auto& thisPathMap = interfaceMap[path];
206             tinyxml2::XMLElement* pElement =
207                 pRoot->FirstChildElement("interface");
208             while (pElement != nullptr)
209             {
210                 const char* ifaceName = pElement->Attribute("name");
211                 if (ifaceName == nullptr)
212                 {
213                     continue;
214                 }
215 
216                 thisPathMap[transaction->processName].emplace(ifaceName);
217 
218                 if (std::strcmp(ifaceName, assocDefsInterface) == 0)
219                 {
220                     doAssociations(systemBus, interfaceMap, objectServer,
221                                    transaction->processName, path);
222                 }
223 
224                 pElement = pElement->NextSiblingElement("interface");
225             }
226 
227             // Check if this new path has a pending association that can
228             // now be completed.
229             checkIfPendingAssociation(path, interfaceMap,
230                                       transaction->assocMaps, objectServer);
231 
232             pElement = pRoot->FirstChildElement("node");
233             while (pElement != nullptr)
234             {
235                 const char* childPath = pElement->Attribute("name");
236                 if (childPath != nullptr)
237                 {
238                     std::string parentPath(path);
239                     if (parentPath == "/")
240                     {
241                         parentPath.clear();
242                     }
243 
244                     doIntrospect(systemBus, transaction, interfaceMap,
245                                  objectServer, parentPath + "/" + childPath);
246                 }
247                 pElement = pElement->NextSiblingElement("node");
248             }
249         },
250         transaction->processName, path, "org.freedesktop.DBus.Introspectable",
251         "Introspect");
252 }
253 
254 void startNewIntrospect(
255     sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
256     InterfaceMapType& interfaceMap, const std::string& processName,
257     AssociationMaps& assocMaps,
258 #ifdef MAPPER_ENABLE_DEBUG
259     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
260         globalStartTime,
261 #endif
262     sdbusplus::asio::object_server& objectServer)
263 {
264     if (needToIntrospect(processName))
265     {
266         std::shared_ptr<InProgressIntrospect> transaction =
267             std::make_shared<InProgressIntrospect>(systemBus, io, processName,
268                                                    assocMaps
269 #ifdef MAPPER_ENABLE_DEBUG
270                                                    ,
271                                                    globalStartTime
272 #endif
273             );
274 
275         doIntrospect(systemBus, transaction, interfaceMap, objectServer, "/");
276     }
277 }
278 
279 // TODO(ed) replace with std::set_intersection once c++17 is available
280 template <class InputIt1, class InputIt2>
281 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
282 {
283     while (first1 != last1 && first2 != last2)
284     {
285         if (*first1 < *first2)
286         {
287             ++first1;
288             continue;
289         }
290         if (*first2 < *first1)
291         {
292             ++first2;
293             continue;
294         }
295         return true;
296     }
297     return false;
298 }
299 
300 void doListNames(
301     boost::asio::io_context& io, InterfaceMapType& interfaceMap,
302     sdbusplus::asio::connection* systemBus,
303     boost::container::flat_map<std::string, std::string>& nameOwners,
304     AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer)
305 {
306     systemBus->async_method_call(
307         [&io, &interfaceMap, &nameOwners, &objectServer, systemBus,
308          &assocMaps](const boost::system::error_code ec,
309                      std::vector<std::string> processNames) {
310             if (ec)
311             {
312                 std::cerr << "Error getting names: " << ec << "\n";
313                 std::exit(EXIT_FAILURE);
314                 return;
315             }
316             // Try to make startup consistent
317             std::sort(processNames.begin(), processNames.end());
318 #ifdef MAPPER_ENABLE_DEBUG
319             std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
320                 globalStartTime = std::make_shared<
321                     std::chrono::time_point<std::chrono::steady_clock>>(
322                     std::chrono::steady_clock::now());
323 #endif
324             for (const std::string& processName : processNames)
325             {
326                 if (needToIntrospect(processName))
327                 {
328                     startNewIntrospect(systemBus, io, interfaceMap, processName,
329                                        assocMaps,
330 #ifdef MAPPER_ENABLE_DEBUG
331                                        globalStartTime,
332 #endif
333                                        objectServer);
334                     updateOwners(systemBus, nameOwners, processName);
335                 }
336             }
337         },
338         "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
339         "ListNames");
340 }
341 
342 void addObjectMapResult(std::vector<InterfaceMapType::value_type>& objectMap,
343                         const std::string& objectPath,
344                         const ConnectionNames::value_type& interfaceMap)
345 {
346     // Adds an object path/service name/interface list entry to
347     // the results of GetSubTree and GetAncestors.
348     // If an entry for the object path already exists, just add the
349     // service name and interfaces to that entry, otherwise create
350     // a new entry.
351     auto entry = std::find_if(
352         objectMap.begin(), objectMap.end(),
353         [&objectPath](const auto& i) { return objectPath == i.first; });
354 
355     if (entry != objectMap.end())
356     {
357         entry->second.emplace(interfaceMap);
358     }
359     else
360     {
361         InterfaceMapType::value_type object;
362         object.first = objectPath;
363         object.second.emplace(interfaceMap);
364         objectMap.push_back(object);
365     }
366 }
367 
368 // Remove parents of the passed in path that:
369 // 1) Only have the 3 default interfaces on them
370 //    - Means D-Bus created these, not application code,
371 //      with the Properties, Introspectable, and Peer ifaces
372 // 2) Have no other child for this owner
373 void removeUnneededParents(const std::string& objectPath,
374                            const std::string& owner,
375                            InterfaceMapType& interfaceMap)
376 {
377     auto parent = objectPath;
378 
379     while (true)
380     {
381         auto pos = parent.find_last_of('/');
382         if ((pos == std::string::npos) || (pos == 0))
383         {
384             break;
385         }
386         parent = parent.substr(0, pos);
387 
388         auto parentIt = interfaceMap.find(parent);
389         if (parentIt == interfaceMap.end())
390         {
391             break;
392         }
393 
394         auto ifacesIt = parentIt->second.find(owner);
395         if (ifacesIt == parentIt->second.end())
396         {
397             break;
398         }
399 
400         if (ifacesIt->second.size() != 3)
401         {
402             break;
403         }
404 
405         auto childPath = parent + '/';
406 
407         // Remove this parent if there isn't a remaining child on this owner
408         auto child = std::find_if(
409             interfaceMap.begin(), interfaceMap.end(),
410             [&owner, &childPath](const auto& entry) {
411                 return entry.first.starts_with(childPath) &&
412                        (entry.second.find(owner) != entry.second.end());
413             });
414 
415         if (child == interfaceMap.end())
416         {
417             parentIt->second.erase(ifacesIt);
418             if (parentIt->second.empty())
419             {
420                 interfaceMap.erase(parentIt);
421             }
422         }
423         else
424         {
425             break;
426         }
427     }
428 }
429 
430 std::vector<InterfaceMapType::value_type>
431     getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
432                  std::vector<std::string>& interfaces)
433 {
434     // Interfaces need to be sorted for intersect to function
435     std::sort(interfaces.begin(), interfaces.end());
436 
437     if (reqPath.ends_with("/"))
438     {
439         reqPath.pop_back();
440     }
441     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
442     {
443         throw sdbusplus::xyz::openbmc_project::Common::Error::
444             ResourceNotFound();
445     }
446 
447     std::vector<InterfaceMapType::value_type> ret;
448     for (const auto& objectPath : interfaceMap)
449     {
450         const auto& thisPath = objectPath.first;
451 
452         if (reqPath == thisPath)
453         {
454             continue;
455         }
456 
457         if (reqPath.starts_with(thisPath))
458         {
459             if (interfaces.empty())
460             {
461                 ret.emplace_back(objectPath);
462             }
463             else
464             {
465                 for (const auto& interfaceMap : objectPath.second)
466                 {
467                     if (intersect(interfaces.begin(), interfaces.end(),
468                                   interfaceMap.second.begin(),
469                                   interfaceMap.second.end()))
470                     {
471                         addObjectMapResult(ret, thisPath, interfaceMap);
472                     }
473                 }
474             }
475         }
476     }
477 
478     return ret;
479 }
480 
481 ConnectionNames getObject(const InterfaceMapType& interfaceMap,
482                           const std::string& path,
483                           std::vector<std::string>& interfaces)
484 {
485     ConnectionNames results;
486 
487     // Interfaces need to be sorted for intersect to function
488     std::sort(interfaces.begin(), interfaces.end());
489     auto pathRef = interfaceMap.find(path);
490     if (pathRef == interfaceMap.end())
491     {
492         throw sdbusplus::xyz::openbmc_project::Common::Error::
493             ResourceNotFound();
494     }
495     if (interfaces.empty())
496     {
497         return pathRef->second;
498     }
499     for (const auto& interfaceMap : pathRef->second)
500     {
501         if (intersect(interfaces.begin(), interfaces.end(),
502                       interfaceMap.second.begin(), interfaceMap.second.end()))
503         {
504             results.emplace(interfaceMap.first, interfaceMap.second);
505         }
506     }
507 
508     if (results.empty())
509     {
510         throw sdbusplus::xyz::openbmc_project::Common::Error::
511             ResourceNotFound();
512     }
513 
514     return results;
515 }
516 
517 std::vector<InterfaceMapType::value_type>
518     getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
519                int32_t depth, std::vector<std::string>& interfaces)
520 {
521     if (depth <= 0)
522     {
523         depth = std::numeric_limits<int32_t>::max();
524     }
525     // Interfaces need to be sorted for intersect to function
526     std::sort(interfaces.begin(), interfaces.end());
527 
528     // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
529     // will be guaranteed not to have a trailing "/"
530     if (!reqPath.ends_with("/"))
531     {
532         reqPath += "/";
533     }
534     std::string_view reqPathStripped =
535         std::string_view(reqPath).substr(0, reqPath.size() - 1);
536 
537     if (!reqPathStripped.empty() &&
538         interfaceMap.find(reqPathStripped) == interfaceMap.end())
539     {
540         throw sdbusplus::xyz::openbmc_project::Common::Error::
541             ResourceNotFound();
542     }
543 
544     std::vector<InterfaceMapType::value_type> ret;
545     for (const auto& objectPath : interfaceMap)
546     {
547         const auto& thisPath = objectPath.first;
548 
549         // Skip exact match on stripped search term
550         if (thisPath == reqPathStripped)
551         {
552             continue;
553         }
554 
555         if (thisPath.starts_with(reqPath))
556         {
557             // count the number of slashes past the stripped search term
558             int32_t thisDepth = std::count(
559                 thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
560             if (thisDepth <= depth)
561             {
562                 for (const auto& interfaceMap : objectPath.second)
563                 {
564                     if (intersect(interfaces.begin(), interfaces.end(),
565                                   interfaceMap.second.begin(),
566                                   interfaceMap.second.end()) ||
567                         interfaces.empty())
568                     {
569                         addObjectMapResult(ret, thisPath, interfaceMap);
570                     }
571                 }
572             }
573         }
574     }
575 
576     return ret;
577 }
578 
579 std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
580                                          std::string reqPath, int32_t depth,
581                                          std::vector<std::string>& interfaces)
582 {
583     if (depth <= 0)
584     {
585         depth = std::numeric_limits<int32_t>::max();
586     }
587     // Interfaces need to be sorted for intersect to function
588     std::sort(interfaces.begin(), interfaces.end());
589 
590     // reqPath is now guaranteed to have a trailing "/" while reqPathStripped
591     // will be guaranteed not to have a trailing "/"
592     if (!reqPath.ends_with("/"))
593     {
594         reqPath += "/";
595     }
596     std::string_view reqPathStripped =
597         std::string_view(reqPath).substr(0, reqPath.size() - 1);
598 
599     if (!reqPathStripped.empty() &&
600         interfaceMap.find(reqPathStripped) == interfaceMap.end())
601     {
602         throw sdbusplus::xyz::openbmc_project::Common::Error::
603             ResourceNotFound();
604     }
605 
606     std::vector<std::string> ret;
607     for (const auto& objectPath : interfaceMap)
608     {
609         const auto& thisPath = objectPath.first;
610 
611         // Skip exact match on stripped search term
612         if (thisPath == reqPathStripped)
613         {
614             continue;
615         }
616 
617         if (thisPath.starts_with(reqPath))
618         {
619             // count the number of slashes past the stripped search term
620             int thisDepth = std::count(
621                 thisPath.begin() + reqPathStripped.size(), thisPath.end(), '/');
622             if (thisDepth <= depth)
623             {
624                 bool add = interfaces.empty();
625                 for (const auto& interfaceMap : objectPath.second)
626                 {
627                     if (intersect(interfaces.begin(), interfaces.end(),
628                                   interfaceMap.second.begin(),
629                                   interfaceMap.second.end()))
630                     {
631                         add = true;
632                         break;
633                     }
634                 }
635                 if (add)
636                 {
637                     // TODO(ed) this is a copy
638                     ret.emplace_back(thisPath);
639                 }
640             }
641         }
642     }
643 
644     return ret;
645 }
646 
647 int main()
648 {
649     boost::asio::io_context io;
650     std::shared_ptr<sdbusplus::asio::connection> systemBus =
651         std::make_shared<sdbusplus::asio::connection>(io);
652 
653     sdbusplus::asio::object_server server(systemBus);
654 
655     // Construct a signal set registered for process termination.
656     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
657     signals.async_wait(
658         [&io](const boost::system::error_code&, int) { io.stop(); });
659 
660     InterfaceMapType interfaceMap;
661     boost::container::flat_map<std::string, std::string> nameOwners;
662 
663     std::function<void(sdbusplus::message_t & message)> nameChangeHandler =
664         [&interfaceMap, &io, &nameOwners, &server,
665          systemBus](sdbusplus::message_t& message) {
666             std::string name;     // well-known
667             std::string oldOwner; // unique-name
668             std::string newOwner; // unique-name
669 
670             message.read(name, oldOwner, newOwner);
671 
672             if (!oldOwner.empty())
673             {
674                 processNameChangeDelete(nameOwners, name, oldOwner,
675                                         interfaceMap, associationMaps, server);
676             }
677 
678             if (!newOwner.empty())
679             {
680 #ifdef MAPPER_ENABLE_DEBUG
681                 auto transaction = std::make_shared<
682                     std::chrono::time_point<std::chrono::steady_clock>>(
683                     std::chrono::steady_clock::now());
684 #endif
685                 // New daemon added
686                 if (needToIntrospect(name))
687                 {
688                     nameOwners[newOwner] = name;
689                     startNewIntrospect(systemBus.get(), io, interfaceMap, name,
690                                        associationMaps,
691 #ifdef MAPPER_ENABLE_DEBUG
692                                        transaction,
693 #endif
694                                        server);
695                 }
696             }
697         };
698 
699     sdbusplus::bus::match_t nameOwnerChanged(
700         static_cast<sdbusplus::bus_t&>(*systemBus),
701         sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
702 
703     std::function<void(sdbusplus::message_t & message)> interfacesAddedHandler =
704         [&interfaceMap, &nameOwners, &server](sdbusplus::message_t& message) {
705             sdbusplus::message::object_path objPath;
706             InterfacesAdded interfacesAdded;
707             message.read(objPath, interfacesAdded);
708             std::string wellKnown;
709             if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
710             {
711                 return; // only introspect well-known
712             }
713             if (needToIntrospect(wellKnown))
714             {
715                 processInterfaceAdded(interfaceMap, objPath, interfacesAdded,
716                                       wellKnown, associationMaps, server);
717             }
718         };
719 
720     sdbusplus::bus::match_t interfacesAdded(
721         static_cast<sdbusplus::bus_t&>(*systemBus),
722         sdbusplus::bus::match::rules::interfacesAdded(),
723         interfacesAddedHandler);
724 
725     std::function<void(sdbusplus::message_t & message)>
726         interfacesRemovedHandler = [&interfaceMap, &nameOwners,
727                                     &server](sdbusplus::message_t& message) {
728             sdbusplus::message::object_path objPath;
729             std::vector<std::string> interfacesRemoved;
730             message.read(objPath, interfacesRemoved);
731             auto connectionMap = interfaceMap.find(objPath.str);
732             if (connectionMap == interfaceMap.end())
733             {
734                 return;
735             }
736 
737             std::string sender;
738             if (!getWellKnown(nameOwners, message.get_sender(), sender))
739             {
740                 return;
741             }
742             for (const std::string& interface : interfacesRemoved)
743             {
744                 auto interfaceSet = connectionMap->second.find(sender);
745                 if (interfaceSet == connectionMap->second.end())
746                 {
747                     continue;
748                 }
749 
750                 if (interface == assocDefsInterface)
751                 {
752                     removeAssociation(objPath.str, sender, server,
753                                       associationMaps);
754                 }
755 
756                 interfaceSet->second.erase(interface);
757 
758                 if (interfaceSet->second.empty())
759                 {
760                     // If this was the last interface on this connection,
761                     // erase the connection
762                     connectionMap->second.erase(interfaceSet);
763 
764                     // Instead of checking if every single path is the endpoint
765                     // of an association that needs to be moved to pending,
766                     // only check when the only remaining owner of this path is
767                     // ourself, which would be because we still own the
768                     // association path.
769                     if ((connectionMap->second.size() == 1) &&
770                         (connectionMap->second.begin()->first ==
771                          "xyz.openbmc_project.ObjectMapper"))
772                     {
773                         // Remove the 2 association D-Bus paths and move the
774                         // association to pending.
775                         moveAssociationToPending(objPath.str, associationMaps,
776                                                  server);
777                     }
778                 }
779             }
780             // If this was the last connection on this object path,
781             // erase the object path
782             if (connectionMap->second.empty())
783             {
784                 interfaceMap.erase(connectionMap);
785             }
786 
787             removeUnneededParents(objPath.str, sender, interfaceMap);
788         };
789 
790     sdbusplus::bus::match_t interfacesRemoved(
791         static_cast<sdbusplus::bus_t&>(*systemBus),
792         sdbusplus::bus::match::rules::interfacesRemoved(),
793         interfacesRemovedHandler);
794 
795     std::function<void(sdbusplus::message_t & message)>
796         associationChangedHandler = [&server, &nameOwners, &interfaceMap](
797                                         sdbusplus::message_t& message) {
798             std::string objectName;
799             boost::container::flat_map<std::string,
800                                        std::variant<std::vector<Association>>>
801                 values;
802             message.read(objectName, values);
803             auto prop = values.find(assocDefsProperty);
804             if (prop != values.end())
805             {
806                 std::vector<Association> associations =
807                     std::get<std::vector<Association>>(prop->second);
808 
809                 std::string wellKnown;
810                 if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
811                 {
812                     return;
813                 }
814                 associationChanged(server, associations, message.get_path(),
815                                    wellKnown, interfaceMap, associationMaps);
816             }
817         };
818     sdbusplus::bus::match_t assocChangedMatch(
819         static_cast<sdbusplus::bus_t&>(*systemBus),
820         sdbusplus::bus::match::rules::interface(
821             "org.freedesktop.DBus.Properties") +
822             sdbusplus::bus::match::rules::member("PropertiesChanged") +
823             sdbusplus::bus::match::rules::argN(0, assocDefsInterface),
824         associationChangedHandler);
825 
826     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
827         server.add_interface("/xyz/openbmc_project/object_mapper",
828                              "xyz.openbmc_project.ObjectMapper");
829 
830     iface->register_method(
831         "GetAncestors", [&interfaceMap](std::string& reqPath,
832                                         std::vector<std::string>& interfaces) {
833             return getAncestors(interfaceMap, reqPath, interfaces);
834         });
835 
836     iface->register_method(
837         "GetObject", [&interfaceMap](const std::string& path,
838                                      std::vector<std::string>& interfaces) {
839             return getObject(interfaceMap, path, interfaces);
840         });
841 
842     iface->register_method(
843         "GetSubTree", [&interfaceMap](std::string& reqPath, int32_t depth,
844                                       std::vector<std::string>& interfaces) {
845             return getSubTree(interfaceMap, reqPath, depth, interfaces);
846         });
847 
848     iface->register_method(
849         "GetSubTreePaths",
850         [&interfaceMap](std::string& reqPath, int32_t depth,
851                         std::vector<std::string>& interfaces) {
852             return getSubTreePaths(interfaceMap, reqPath, depth, interfaces);
853         });
854 
855     iface->initialize();
856 
857     io.post([&]() {
858         doListNames(io, interfaceMap, systemBus.get(), nameOwners,
859                     associationMaps, server);
860     });
861 
862     systemBus->request_name("xyz.openbmc_project.ObjectMapper");
863 
864     io.run();
865 }
866