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