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