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