xref: /openbmc/phosphor-objmgr/src/main.cpp (revision cc8070ba)
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_t 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(std::vector<InterfaceMapType::value_type>& objectMap,
363                         const std::string& objectPath,
364                         const ConnectionNames::value_type& interfaceMap)
365 {
366     // Adds an object path/service name/interface list entry to
367     // the results of GetSubTree and GetAncestors.
368     // If an entry for the object path already exists, just add the
369     // service name and interfaces to that entry, otherwise create
370     // a new entry.
371     auto entry = std::find_if(
372         objectMap.begin(), objectMap.end(),
373         [&objectPath](const auto& i) { return objectPath == i.first; });
374 
375     if (entry != objectMap.end())
376     {
377         entry->second.emplace(interfaceMap);
378     }
379     else
380     {
381         InterfaceMapType::value_type object;
382         object.first = objectPath;
383         object.second.emplace(interfaceMap);
384         objectMap.push_back(object);
385     }
386 }
387 
388 // Remove parents of the passed in path that:
389 // 1) Only have the 3 default interfaces on them
390 //    - Means D-Bus created these, not application code,
391 //      with the Properties, Introspectable, and Peer ifaces
392 // 2) Have no other child for this owner
393 void removeUnneededParents(const std::string& objectPath,
394                            const std::string& owner,
395                            InterfaceMapType& interfaceMap)
396 {
397     auto parent = objectPath;
398 
399     while (true)
400     {
401         auto pos = parent.find_last_of('/');
402         if ((pos == std::string::npos) || (pos == 0))
403         {
404             break;
405         }
406         parent = parent.substr(0, pos);
407 
408         auto parentIt = interfaceMap.find(parent);
409         if (parentIt == interfaceMap.end())
410         {
411             break;
412         }
413 
414         auto ifacesIt = parentIt->second.find(owner);
415         if (ifacesIt == parentIt->second.end())
416         {
417             break;
418         }
419 
420         if (ifacesIt->second.size() != 3)
421         {
422             break;
423         }
424 
425         auto childPath = parent + '/';
426 
427         // Remove this parent if there isn't a remaining child on this owner
428         auto child = std::find_if(
429             interfaceMap.begin(), interfaceMap.end(),
430             [&owner, &childPath](const auto& entry) {
431                 return boost::starts_with(entry.first, childPath) &&
432                        (entry.second.find(owner) != entry.second.end());
433             });
434 
435         if (child == interfaceMap.end())
436         {
437             parentIt->second.erase(ifacesIt);
438             if (parentIt->second.empty())
439             {
440                 interfaceMap.erase(parentIt);
441             }
442         }
443         else
444         {
445             break;
446         }
447     }
448 }
449 
450 std::vector<InterfaceMapType::value_type>
451     getAncestors(const InterfaceMapType& interfaceMap, std::string reqPath,
452                  std::vector<std::string>& interfaces)
453 {
454     // Interfaces need to be sorted for intersect to function
455     std::sort(interfaces.begin(), interfaces.end());
456 
457     if (boost::ends_with(reqPath, "/"))
458     {
459         reqPath.pop_back();
460     }
461     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
462     {
463         throw sdbusplus::xyz::openbmc_project::Common::Error::
464             ResourceNotFound();
465     }
466 
467     std::vector<InterfaceMapType::value_type> ret;
468     for (const auto& objectPath : interfaceMap)
469     {
470         const auto& thisPath = objectPath.first;
471 
472         if (reqPath == thisPath)
473         {
474             continue;
475         }
476 
477         if (boost::starts_with(reqPath, thisPath))
478         {
479             if (interfaces.empty())
480             {
481                 ret.emplace_back(objectPath);
482             }
483             else
484             {
485                 for (const auto& interfaceMap : objectPath.second)
486                 {
487                     if (intersect(interfaces.begin(), interfaces.end(),
488                                   interfaceMap.second.begin(),
489                                   interfaceMap.second.end()))
490                     {
491                         addObjectMapResult(ret, thisPath, interfaceMap);
492                     }
493                 }
494             }
495         }
496     }
497 
498     return ret;
499 }
500 
501 ConnectionNames getObject(const InterfaceMapType& interfaceMap,
502                           const std::string& path,
503                           std::vector<std::string>& interfaces)
504 {
505     ConnectionNames results;
506 
507     // Interfaces need to be sorted for intersect to function
508     std::sort(interfaces.begin(), interfaces.end());
509     auto pathRef = interfaceMap.find(path);
510     if (pathRef == interfaceMap.end())
511     {
512         throw sdbusplus::xyz::openbmc_project::Common::Error::
513             ResourceNotFound();
514     }
515     if (interfaces.empty())
516     {
517         return pathRef->second;
518     }
519     for (const auto& interfaceMap : pathRef->second)
520     {
521         if (intersect(interfaces.begin(), interfaces.end(),
522                       interfaceMap.second.begin(), interfaceMap.second.end()))
523         {
524             results.emplace(interfaceMap.first, interfaceMap.second);
525         }
526     }
527 
528     if (results.empty())
529     {
530         throw sdbusplus::xyz::openbmc_project::Common::Error::
531             ResourceNotFound();
532     }
533 
534     return results;
535 }
536 
537 std::vector<InterfaceMapType::value_type>
538     getSubTree(const InterfaceMapType& interfaceMap, std::string reqPath,
539                int32_t depth, std::vector<std::string>& interfaces)
540 {
541     if (depth <= 0)
542     {
543         depth = std::numeric_limits<int32_t>::max();
544     }
545     // Interfaces need to be sorted for intersect to function
546     std::sort(interfaces.begin(), interfaces.end());
547 
548     if (boost::ends_with(reqPath, "/"))
549     {
550         reqPath.pop_back();
551     }
552     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
553     {
554         throw sdbusplus::xyz::openbmc_project::Common::Error::
555             ResourceNotFound();
556     }
557 
558     std::vector<InterfaceMapType::value_type> ret;
559     for (const auto& objectPath : interfaceMap)
560     {
561         const auto& thisPath = objectPath.first;
562 
563         if (thisPath == reqPath)
564         {
565             continue;
566         }
567 
568         if (boost::starts_with(thisPath, reqPath))
569         {
570             // count the number of slashes past the search term
571             int32_t thisDepth = std::count(thisPath.begin() + reqPath.size(),
572                                            thisPath.end(), '/');
573             if (thisDepth <= depth)
574             {
575                 for (const auto& interfaceMap : objectPath.second)
576                 {
577                     if (intersect(interfaces.begin(), interfaces.end(),
578                                   interfaceMap.second.begin(),
579                                   interfaceMap.second.end()) ||
580                         interfaces.empty())
581                     {
582                         addObjectMapResult(ret, thisPath, interfaceMap);
583                     }
584                 }
585             }
586         }
587     }
588 
589     return ret;
590 }
591 
592 std::vector<std::string> getSubTreePaths(const InterfaceMapType& interfaceMap,
593                                          std::string reqPath, int32_t depth,
594                                          std::vector<std::string>& interfaces)
595 {
596     if (depth <= 0)
597     {
598         depth = std::numeric_limits<int32_t>::max();
599     }
600     // Interfaces need to be sorted for intersect to function
601     std::sort(interfaces.begin(), interfaces.end());
602 
603     if (boost::ends_with(reqPath, "/"))
604     {
605         reqPath.pop_back();
606     }
607     if (!reqPath.empty() && interfaceMap.find(reqPath) == interfaceMap.end())
608     {
609         throw sdbusplus::xyz::openbmc_project::Common::Error::
610             ResourceNotFound();
611     }
612 
613     std::vector<std::string> ret;
614     for (const auto& objectPath : interfaceMap)
615     {
616         const auto& thisPath = objectPath.first;
617 
618         if (thisPath == reqPath)
619         {
620             continue;
621         }
622 
623         if (boost::starts_with(thisPath, reqPath))
624         {
625             // count the number of slashes past the search term
626             int thisDepth = std::count(thisPath.begin() + reqPath.size(),
627                                        thisPath.end(), '/');
628             if (thisDepth <= depth)
629             {
630                 bool add = interfaces.empty();
631                 for (const auto& interfaceMap : objectPath.second)
632                 {
633                     if (intersect(interfaces.begin(), interfaces.end(),
634                                   interfaceMap.second.begin(),
635                                   interfaceMap.second.end()))
636                     {
637                         add = true;
638                         break;
639                     }
640                 }
641                 if (add)
642                 {
643                     // TODO(ed) this is a copy
644                     ret.emplace_back(thisPath);
645                 }
646             }
647         }
648     }
649 
650     return ret;
651 }
652 
653 int main(int argc, char** argv)
654 {
655     auto options = ArgumentParser(argc, argv);
656     boost::asio::io_context io;
657     std::shared_ptr<sdbusplus::asio::connection> systemBus =
658         std::make_shared<sdbusplus::asio::connection>(io);
659 
660     splitArgs(options["service-namespaces"], serviceAllowList);
661 
662     sdbusplus::asio::object_server server(systemBus);
663 
664     // Construct a signal set registered for process termination.
665     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
666     signals.async_wait(
667         [&io](const boost::system::error_code&, int) { io.stop(); });
668 
669     InterfaceMapType interfaceMap;
670     boost::container::flat_map<std::string, std::string> nameOwners;
671 
672     std::function<void(sdbusplus::message_t & message)> nameChangeHandler =
673         [&interfaceMap, &io, &nameOwners, &server,
674          systemBus](sdbusplus::message_t& message) {
675             std::string name;     // well-known
676             std::string oldOwner; // unique-name
677             std::string newOwner; // unique-name
678 
679             message.read(name, oldOwner, newOwner);
680 
681             if (!oldOwner.empty())
682             {
683                 processNameChangeDelete(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, serviceAllowList))
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         [&interfaceMap, &nameOwners, &server](sdbusplus::message_t& message) {
714             sdbusplus::message::object_path objPath;
715             InterfacesAdded interfacesAdded;
716             message.read(objPath, interfacesAdded);
717             std::string wellKnown;
718             if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
719             {
720                 return; // only introspect well-known
721             }
722             if (needToIntrospect(wellKnown, serviceAllowList))
723             {
724                 processInterfaceAdded(interfaceMap, objPath, interfacesAdded,
725                                       wellKnown, associationMaps, server);
726             }
727         };
728 
729     sdbusplus::bus::match_t interfacesAdded(
730         static_cast<sdbusplus::bus_t&>(*systemBus),
731         sdbusplus::bus::match::rules::interfacesAdded(),
732         interfacesAddedHandler);
733 
734     std::function<void(sdbusplus::message_t & message)>
735         interfacesRemovedHandler = [&interfaceMap, &nameOwners,
736                                     &server](sdbusplus::message_t& message) {
737             sdbusplus::message::object_path objPath;
738             std::vector<std::string> interfacesRemoved;
739             message.read(objPath, interfacesRemoved);
740             auto connectionMap = interfaceMap.find(objPath.str);
741             if (connectionMap == interfaceMap.end())
742             {
743                 return;
744             }
745 
746             std::string sender;
747             if (!getWellKnown(nameOwners, message.get_sender(), sender))
748             {
749                 return;
750             }
751             for (const std::string& interface : interfacesRemoved)
752             {
753                 auto interfaceSet = connectionMap->second.find(sender);
754                 if (interfaceSet == connectionMap->second.end())
755                 {
756                     continue;
757                 }
758 
759                 if (interface == assocDefsInterface)
760                 {
761                     removeAssociation(objPath.str, sender, server,
762                                       associationMaps);
763                 }
764 
765                 interfaceSet->second.erase(interface);
766 
767                 if (interfaceSet->second.empty())
768                 {
769                     // If this was the last interface on this connection,
770                     // erase the connection
771                     connectionMap->second.erase(interfaceSet);
772 
773                     // Instead of checking if every single path is the endpoint
774                     // of an association that needs to be moved to pending,
775                     // only check when the only remaining owner of this path is
776                     // ourself, which would be because we still own the
777                     // association path.
778                     if ((connectionMap->second.size() == 1) &&
779                         (connectionMap->second.begin()->first ==
780                          "xyz.openbmc_project.ObjectMapper"))
781                     {
782                         // Remove the 2 association D-Bus paths and move the
783                         // association to pending.
784                         moveAssociationToPending(objPath.str, associationMaps,
785                                                  server);
786                     }
787                 }
788             }
789             // If this was the last connection on this object path,
790             // erase the object path
791             if (connectionMap->second.empty())
792             {
793                 interfaceMap.erase(connectionMap);
794             }
795 
796             removeUnneededParents(objPath.str, sender, interfaceMap);
797         };
798 
799     sdbusplus::bus::match_t interfacesRemoved(
800         static_cast<sdbusplus::bus_t&>(*systemBus),
801         sdbusplus::bus::match::rules::interfacesRemoved(),
802         interfacesRemovedHandler);
803 
804     std::function<void(sdbusplus::message_t & message)>
805         associationChangedHandler = [&server, &nameOwners, &interfaceMap](
806                                         sdbusplus::message_t& message) {
807             std::string objectName;
808             boost::container::flat_map<std::string,
809                                        std::variant<std::vector<Association>>>
810                 values;
811             message.read(objectName, values);
812             auto prop = values.find(assocDefsProperty);
813             if (prop != values.end())
814             {
815                 std::vector<Association> associations =
816                     std::get<std::vector<Association>>(prop->second);
817 
818                 std::string wellKnown;
819                 if (!getWellKnown(nameOwners, message.get_sender(), wellKnown))
820                 {
821                     return;
822                 }
823                 associationChanged(server, associations, message.get_path(),
824                                    wellKnown, interfaceMap, associationMaps);
825             }
826         };
827     sdbusplus::bus::match_t assocChangedMatch(
828         static_cast<sdbusplus::bus_t&>(*systemBus),
829         sdbusplus::bus::match::rules::interface(
830             "org.freedesktop.DBus.Properties") +
831             sdbusplus::bus::match::rules::member("PropertiesChanged") +
832             sdbusplus::bus::match::rules::argN(0, assocDefsInterface),
833         associationChangedHandler);
834 
835     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
836         server.add_interface("/xyz/openbmc_project/object_mapper",
837                              "xyz.openbmc_project.ObjectMapper");
838 
839     iface->register_method(
840         "GetAncestors", [&interfaceMap](std::string& reqPath,
841                                         std::vector<std::string>& interfaces) {
842             return getAncestors(interfaceMap, reqPath, interfaces);
843         });
844 
845     iface->register_method(
846         "GetObject", [&interfaceMap](const std::string& path,
847                                      std::vector<std::string>& interfaces) {
848             return getObject(interfaceMap, path, interfaces);
849         });
850 
851     iface->register_method(
852         "GetSubTree", [&interfaceMap](std::string& reqPath, int32_t depth,
853                                       std::vector<std::string>& interfaces) {
854             return getSubTree(interfaceMap, reqPath, depth, interfaces);
855         });
856 
857     iface->register_method(
858         "GetSubTreePaths",
859         [&interfaceMap](std::string& reqPath, int32_t depth,
860                         std::vector<std::string>& interfaces) {
861             return getSubTreePaths(interfaceMap, reqPath, depth, interfaces);
862         });
863 
864     iface->initialize();
865 
866     io.post([&]() {
867         doListNames(io, interfaceMap, systemBus.get(), nameOwners,
868                     associationMaps, server);
869     });
870 
871     systemBus->request_name("xyz.openbmc_project.ObjectMapper");
872 
873     io.run();
874 }
875