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