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