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