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