xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 1f62380a)
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 #include <utility>
21 
22 AssociationMaps associationMaps;
23 
24 static AllowDenyList serviceAllowList;
25 static AllowDenyList serviceDenyList;
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 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 DEBUG
82         ,
83         globalStartTime(std::move(globalStartTime)),
84         processStartTime(std::chrono::steady_clock::now())
85 #endif
86     {}
87     ~InProgressIntrospect()
88     {
89         sendIntrospectionCompleteSignal(systemBus, processName);
90 
91 #ifdef DEBUG
92         std::chrono::duration<float> diff =
93             std::chrono::steady_clock::now() - processStartTime;
94         std::cout << std::setw(50) << processName << " scan took "
95                   << diff.count() << " seconds\n";
96 
97         // If we're the last outstanding caller globally, calculate the
98         // time it took
99         if (globalStartTime != nullptr && globalStartTime.use_count() == 1)
100         {
101             diff = std::chrono::steady_clock::now() - *globalStartTime;
102             std::cout << "Total scan took " << diff.count()
103                       << " seconds to complete\n";
104         }
105 #endif
106     }
107     sdbusplus::asio::connection* systemBus;
108     boost::asio::io_context& io;
109     std::string processName;
110     AssociationMaps& assocMaps;
111 #ifdef DEBUG
112     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
113         globalStartTime;
114     std::chrono::time_point<std::chrono::steady_clock> processStartTime;
115 #endif
116 };
117 
118 void doAssociations(sdbusplus::asio::connection* systemBus,
119                     InterfaceMapType& interfaceMap,
120                     sdbusplus::asio::object_server& objectServer,
121                     const std::string& processName, const std::string& path,
122                     int timeoutRetries = 0)
123 {
124     constexpr int maxTimeoutRetries = 3;
125     systemBus->async_method_call(
126         [&objectServer, path, processName, &interfaceMap, systemBus,
127          timeoutRetries](
128             const boost::system::error_code ec,
129             const std::variant<std::vector<Association>>& variantAssociations) {
130             if (ec)
131             {
132                 if (ec.value() == boost::system::errc::timed_out &&
133                     timeoutRetries < maxTimeoutRetries)
134                 {
135                     doAssociations(systemBus, interfaceMap, objectServer,
136                                    processName, path, timeoutRetries + 1);
137                     return;
138                 }
139                 std::cerr << "Error getting associations from " << path << "\n";
140             }
141             std::vector<Association> associations =
142                 std::get<std::vector<Association>>(variantAssociations);
143             associationChanged(objectServer, associations, path, processName,
144                                interfaceMap, associationMaps);
145         },
146         processName, path, "org.freedesktop.DBus.Properties", "Get",
147         assocDefsInterface, assocDefsProperty);
148 }
149 
150 void doIntrospect(sdbusplus::asio::connection* systemBus,
151                   const std::shared_ptr<InProgressIntrospect>& transaction,
152                   InterfaceMapType& interfaceMap,
153                   sdbusplus::asio::object_server& objectServer,
154                   const std::string& path, int timeoutRetries = 0)
155 {
156     constexpr int maxTimeoutRetries = 3;
157     systemBus->async_method_call(
158         [&interfaceMap, &objectServer, transaction, path, systemBus,
159          timeoutRetries](const boost::system::error_code ec,
160                          const std::string& introspectXml) {
161             if (ec)
162             {
163                 if (ec.value() == boost::system::errc::timed_out &&
164                     timeoutRetries < maxTimeoutRetries)
165                 {
166                     doIntrospect(systemBus, transaction, interfaceMap,
167                                  objectServer, path, timeoutRetries + 1);
168                     return;
169                 }
170                 std::cerr << "Introspect call failed with error: " << ec << ", "
171                           << ec.message()
172                           << " on process: " << transaction->processName
173                           << " path: " << path << "\n";
174                 return;
175             }
176 
177             tinyxml2::XMLDocument doc;
178 
179             tinyxml2::XMLError e = doc.Parse(introspectXml.c_str());
180             if (e != tinyxml2::XMLError::XML_SUCCESS)
181             {
182                 std::cerr << "XML parsing failed\n";
183                 return;
184             }
185 
186             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
187             if (pRoot == nullptr)
188             {
189                 std::cerr << "XML document did not contain any data\n";
190                 return;
191             }
192             auto& thisPathMap = interfaceMap[path];
193             tinyxml2::XMLElement* pElement =
194                 pRoot->FirstChildElement("interface");
195             while (pElement != nullptr)
196             {
197                 const char* ifaceName = pElement->Attribute("name");
198                 if (ifaceName == nullptr)
199                 {
200                     continue;
201                 }
202 
203                 thisPathMap[transaction->processName].emplace(ifaceName);
204 
205                 if (std::strcmp(ifaceName, assocDefsInterface) == 0)
206                 {
207                     doAssociations(systemBus, interfaceMap, objectServer,
208                                    transaction->processName, path);
209                 }
210 
211                 pElement = pElement->NextSiblingElement("interface");
212             }
213 
214             // Check if this new path has a pending association that can
215             // now be completed.
216             checkIfPendingAssociation(path, interfaceMap,
217                                       transaction->assocMaps, objectServer);
218 
219             pElement = pRoot->FirstChildElement("node");
220             while (pElement != nullptr)
221             {
222                 const char* childPath = pElement->Attribute("name");
223                 if (childPath != nullptr)
224                 {
225                     std::string parentPath(path);
226                     if (parentPath == "/")
227                     {
228                         parentPath.clear();
229                     }
230 
231                     doIntrospect(systemBus, transaction, interfaceMap,
232                                  objectServer, parentPath + "/" + childPath);
233                 }
234                 pElement = pElement->NextSiblingElement("node");
235             }
236         },
237         transaction->processName, path, "org.freedesktop.DBus.Introspectable",
238         "Introspect");
239 }
240 
241 void startNewIntrospect(
242     sdbusplus::asio::connection* systemBus, boost::asio::io_context& io,
243     InterfaceMapType& interfaceMap, const std::string& processName,
244     AssociationMaps& assocMaps,
245 #ifdef DEBUG
246     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
247         globalStartTime,
248 #endif
249     sdbusplus::asio::object_server& objectServer)
250 {
251     if (needToIntrospect(processName, serviceAllowList, serviceDenyList))
252     {
253         std::shared_ptr<InProgressIntrospect> transaction =
254             std::make_shared<InProgressIntrospect>(systemBus, io, processName,
255                                                    assocMaps
256 #ifdef DEBUG
257                                                    ,
258                                                    globalStartTime
259 #endif
260             );
261 
262         doIntrospect(systemBus, transaction, interfaceMap, objectServer, "/");
263     }
264 }
265 
266 // TODO(ed) replace with std::set_intersection once c++17 is available
267 template <class InputIt1, class InputIt2>
268 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
269 {
270     while (first1 != last1 && first2 != last2)
271     {
272         if (*first1 < *first2)
273         {
274             ++first1;
275             continue;
276         }
277         if (*first2 < *first1)
278         {
279             ++first2;
280             continue;
281         }
282         return true;
283     }
284     return false;
285 }
286 
287 void doListNames(
288     boost::asio::io_context& io, InterfaceMapType& interfaceMap,
289     sdbusplus::asio::connection* systemBus,
290     boost::container::flat_map<std::string, std::string>& nameOwners,
291     AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer)
292 {
293     systemBus->async_method_call(
294         [&io, &interfaceMap, &nameOwners, &objectServer, systemBus,
295          &assocMaps](const boost::system::error_code ec,
296                      std::vector<std::string> processNames) {
297             if (ec)
298             {
299                 std::cerr << "Error getting names: " << ec << "\n";
300                 std::exit(EXIT_FAILURE);
301                 return;
302             }
303             // Try to make startup consistent
304             std::sort(processNames.begin(), processNames.end());
305 #ifdef DEBUG
306             std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
307                 globalStartTime = std::make_shared<
308                     std::chrono::time_point<std::chrono::steady_clock>>(
309                     std::chrono::steady_clock::now());
310 #endif
311             for (const std::string& processName : processNames)
312             {
313                 if (needToIntrospect(processName, serviceAllowList,
314                                      serviceDenyList))
315                 {
316                     startNewIntrospect(systemBus, io, interfaceMap, processName,
317                                        assocMaps,
318 #ifdef DEBUG
319                                        globalStartTime,
320 #endif
321                                        objectServer);
322                     updateOwners(systemBus, nameOwners, processName);
323                 }
324             }
325         },
326         "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
327         "ListNames");
328 }
329 
330 void splitArgs(const std::string& stringArgs,
331                boost::container::flat_set<std::string>& listArgs)
332 {
333     std::istringstream args;
334     std::string arg;
335 
336     args.str(stringArgs);
337 
338     while (!args.eof())
339     {
340         args >> arg;
341         if (!arg.empty())
342         {
343             listArgs.insert(arg);
344         }
345     }
346 }
347 
348 void addObjectMapResult(
349     std::vector<InterfaceMapType::value_type>& objectMap,
350     const std::string& objectPath,
351     const std::pair<std::string, boost::container::flat_set<std::string>>&
352         interfaceMap)
353 {
354     // Adds an object path/service name/interface list entry to
355     // the results of GetSubTree and GetAncestors.
356     // If an entry for the object path already exists, just add the
357     // service name and interfaces to that entry, otherwise create
358     // a new entry.
359     auto entry = std::find_if(
360         objectMap.begin(), objectMap.end(),
361         [&objectPath](const auto& i) { return objectPath == i.first; });
362 
363     if (entry != objectMap.end())
364     {
365         entry->second.emplace(interfaceMap);
366     }
367     else
368     {
369         InterfaceMapType::value_type object;
370         object.first = objectPath;
371         object.second.emplace(interfaceMap);
372         objectMap.push_back(object);
373     }
374 }
375 
376 // Remove parents of the passed in path that:
377 // 1) Only have the 3 default interfaces on them
378 //    - Means D-Bus created these, not application code,
379 //      with the Properties, Introspectable, and Peer ifaces
380 // 2) Have no other child for this owner
381 void removeUnneededParents(const std::string& objectPath,
382                            const std::string& owner,
383                            InterfaceMapType& interfaceMap)
384 {
385     auto parent = objectPath;
386 
387     while (true)
388     {
389         auto pos = parent.find_last_of('/');
390         if ((pos == std::string::npos) || (pos == 0))
391         {
392             break;
393         }
394         parent = parent.substr(0, pos);
395 
396         auto parentIt = interfaceMap.find(parent);
397         if (parentIt == interfaceMap.end())
398         {
399             break;
400         }
401 
402         auto ifacesIt = parentIt->second.find(owner);
403         if (ifacesIt == parentIt->second.end())
404         {
405             break;
406         }
407 
408         if (ifacesIt->second.size() != 3)
409         {
410             break;
411         }
412 
413         auto childPath = parent + '/';
414 
415         // Remove this parent if there isn't a remaining child on this owner
416         auto child = std::find_if(
417             interfaceMap.begin(), interfaceMap.end(),
418             [&owner, &childPath](const auto& entry) {
419                 return boost::starts_with(entry.first, childPath) &&
420                        (entry.second.find(owner) != entry.second.end());
421             });
422 
423         if (child == interfaceMap.end())
424         {
425             parentIt->second.erase(ifacesIt);
426             if (parentIt->second.empty())
427             {
428                 interfaceMap.erase(parentIt);
429             }
430         }
431         else
432         {
433             break;
434         }
435     }
436 }
437 
438 std::vector<InterfaceMapType::value_type>
439     getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
440                  std::vector<std::string>& interfaces)
441 {
442     // Interfaces need to be sorted for intersect to function
443     std::sort(interfaces.begin(), interfaces.end());
444 
445     if (boost::ends_with(reqPath, "/"))
446     {
447         reqPath.pop_back();
448     }
449     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
450     {
451         throw sdbusplus::xyz::openbmc_project::Common::Error::
452             ResourceNotFound();
453     }
454 
455     std::vector<InterfaceMapType::value_type> ret;
456     for (const auto& objectPath : interfaceMap)
457     {
458         const auto& thisPath = objectPath.first;
459         if (boost::starts_with(reqPath, thisPath) && (reqPath != thisPath))
460         {
461             if (interfaces.empty())
462             {
463                 ret.emplace_back(objectPath);
464             }
465             else
466             {
467                 for (const auto& interfaceMap : objectPath.second)
468                 {
469                     if (intersect(interfaces.begin(), interfaces.end(),
470                                   interfaceMap.second.begin(),
471                                   interfaceMap.second.end()))
472                     {
473                         addObjectMapResult(ret, thisPath, interfaceMap);
474                     }
475                 }
476             }
477         }
478     }
479 
480     return ret;
481 }
482 
483 boost::container::flat_map<std::string, boost::container::flat_set<std::string>>
484     getObject(const InterfaceMapType& interfaceMap, const std::string& path,
485               std::vector<std::string>& interfaces)
486 {
487     boost::container::flat_map<std::string,
488                                boost::container::flat_set<std::string>>
489         results;
490 
491     // Interfaces need to be sorted for intersect to function
492     std::sort(interfaces.begin(), interfaces.end());
493     auto pathRef = interfaceMap.find(path);
494     if (pathRef == interfaceMap.end())
495     {
496         throw sdbusplus::xyz::openbmc_project::Common::Error::
497             ResourceNotFound();
498     }
499     if (interfaces.empty())
500     {
501         return pathRef->second;
502     }
503     for (const auto& interfaceMap : pathRef->second)
504     {
505         if (intersect(interfaces.begin(), interfaces.end(),
506                       interfaceMap.second.begin(), interfaceMap.second.end()))
507         {
508             results.emplace(interfaceMap.first, interfaceMap.second);
509         }
510     }
511 
512     if (results.empty())
513     {
514         throw sdbusplus::xyz::openbmc_project::Common::Error::
515             ResourceNotFound();
516     }
517 
518     return results;
519 }
520 
521 std::vector<InterfaceMapType::value_type>
522     getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
523                int32_t depth, std::vector<std::string>& interfaces)
524 {
525     if (depth <= 0)
526     {
527         depth = std::numeric_limits<int32_t>::max();
528     }
529     // Interfaces need to be sorted for intersect to function
530     std::sort(interfaces.begin(), interfaces.end());
531     std::vector<InterfaceMapType::value_type> ret;
532 
533     if (boost::ends_with(reqPath, "/"))
534     {
535         reqPath.pop_back();
536     }
537     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
538     {
539         throw sdbusplus::xyz::openbmc_project::Common::Error::
540             ResourceNotFound();
541     }
542 
543     for (const auto& objectPath : interfaceMap)
544     {
545         const auto& thisPath = objectPath.first;
546 
547         if (thisPath == reqPath)
548         {
549             continue;
550         }
551 
552         if (boost::starts_with(thisPath, reqPath))
553         {
554             // count the number of slashes past the search term
555             int32_t thisDepth = std::count(thisPath.begin() + reqPath.size(),
556                                            thisPath.end(), '/');
557             if (thisDepth <= depth)
558             {
559                 for (const auto& interfaceMap : objectPath.second)
560                 {
561                     if (intersect(interfaces.begin(), interfaces.end(),
562                                   interfaceMap.second.begin(),
563                                   interfaceMap.second.end()) ||
564                         interfaces.empty())
565                     {
566                         addObjectMapResult(ret, thisPath, interfaceMap);
567                     }
568                 }
569             }
570         }
571     }
572 
573     return ret;
574 }
575 
576 std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
577                                          std::string reqPath, int32_t depth,
578                                          std::vector<std::string>& interfaces)
579 {
580     if (depth <= 0)
581     {
582         depth = std::numeric_limits<int32_t>::max();
583     }
584     // Interfaces need to be sorted for intersect to function
585     std::sort(interfaces.begin(), interfaces.end());
586     std::vector<std::string> ret;
587 
588     if (boost::ends_with(reqPath, "/"))
589     {
590         reqPath.pop_back();
591     }
592     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
593     {
594         throw sdbusplus::xyz::openbmc_project::Common::Error::
595             ResourceNotFound();
596     }
597 
598     for (const auto& objectPath : interfaceMap)
599     {
600         const auto& thisPath = objectPath.first;
601 
602         if (thisPath == reqPath)
603         {
604             continue;
605         }
606 
607         if (boost::starts_with(thisPath, reqPath))
608         {
609             // count the number of slashes past the search term
610             int thisDepth = std::count(thisPath.begin() + reqPath.size(),
611                                        thisPath.end(), '/');
612             if (thisDepth <= depth)
613             {
614                 bool add = interfaces.empty();
615                 for (const auto& interfaceMap : objectPath.second)
616                 {
617                     if (intersect(interfaces.begin(), interfaces.end(),
618                                   interfaceMap.second.begin(),
619                                   interfaceMap.second.end()))
620                     {
621                         add = true;
622                         break;
623                     }
624                 }
625                 if (add)
626                 {
627                     // TODO(ed) this is a copy
628                     ret.emplace_back(thisPath);
629                 }
630             }
631         }
632     }
633 
634     return ret;
635 }
636 
637 int main(int argc, char** argv)
638 {
639     auto options = ArgumentParser(argc, argv);
640     boost::asio::io_context io;
641     std::shared_ptr<sdbusplus::asio::connection> systemBus =
642         std::make_shared<sdbusplus::asio::connection>(io);
643 
644     splitArgs(options["service-namespaces"], serviceAllowList);
645     splitArgs(options["service-blacklists"], serviceDenyList);
646 
647     // TODO(Ed) Remove this once all service files are updated to not use this.
648     // For now, simply squash the input, and ignore it.
649     boost::container::flat_set<std::string> ifaceAllowlist;
650     splitArgs(options["interface-namespaces"], ifaceAllowlist);
651 
652     sdbusplus::asio::object_server server(systemBus);
653 
654     // Construct a signal set registered for process termination.
655     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
656     signals.async_wait(
657         [&io](const boost::system::error_code&, int) { io.stop(); });
658 
659     InterfaceMapType interfaceMap;
660     boost::container::flat_map<std::string, std::string> nameOwners;
661 
662     std::function<void(sdbusplus::message::message & message)>
663         nameChangeHandler = [&interfaceMap, &io, &nameOwners, &server,
664                              systemBus](sdbusplus::message::message& message) {
665             std::string name;     // well-known
666             std::string oldOwner; // unique-name
667             std::string newOwner; // unique-name
668 
669             message.read(name, oldOwner, newOwner);
670 
671             if (!oldOwner.empty())
672             {
673                 processNameChangeDelete(nameOwners, name, oldOwner,
674                                         interfaceMap, associationMaps, server);
675             }
676 
677             if (!newOwner.empty())
678             {
679 #ifdef DEBUG
680                 auto transaction = std::make_shared<
681                     std::chrono::time_point<std::chrono::steady_clock>>(
682                     std::chrono::steady_clock::now());
683 #endif
684                 // New daemon added
685                 if (needToIntrospect(name, serviceAllowList, serviceDenyList))
686                 {
687                     nameOwners[newOwner] = name;
688                     startNewIntrospect(systemBus.get(), io, interfaceMap, name,
689                                        associationMaps,
690 #ifdef DEBUG
691                                        transaction,
692 #endif
693                                        server);
694                 }
695             }
696         };
697 
698     sdbusplus::bus::match::match nameOwnerChanged(
699         static_cast<sdbusplus::bus::bus&>(*systemBus),
700         sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
701 
702     std::function<void(sdbusplus::message::message & message)>
703         interfacesAddedHandler = [&interfaceMap, &nameOwners, &server](
704                                      sdbusplus::message::message& 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, serviceAllowList, serviceDenyList))
714             {
715                 processInterfaceAdded(interfaceMap, objPath, interfacesAdded,
716                                       wellKnown, associationMaps, server);
717             }
718         };
719 
720     sdbusplus::bus::match::match interfacesAdded(
721         static_cast<sdbusplus::bus::bus&>(*systemBus),
722         sdbusplus::bus::match::rules::interfacesAdded(),
723         interfacesAddedHandler);
724 
725     std::function<void(sdbusplus::message::message & message)>
726         interfacesRemovedHandler = [&interfaceMap, &nameOwners, &server](
727                                        sdbusplus::message::message& 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::match interfacesRemoved(
791         static_cast<sdbusplus::bus::bus&>(*systemBus),
792         sdbusplus::bus::match::rules::interfacesRemoved(),
793         interfacesRemovedHandler);
794 
795     std::function<void(sdbusplus::message::message & message)>
796         associationChangedHandler = [&server, &nameOwners, &interfaceMap](
797                                         sdbusplus::message::message& 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::match assocChangedMatch(
819         static_cast<sdbusplus::bus::bus&>(*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