xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 937a232e)
1 #include "src/argument.hpp"
2 
3 #include <tinyxml2.h>
4 
5 #include <atomic>
6 #include <boost/algorithm/string/predicate.hpp>
7 #include <boost/container/flat_map.hpp>
8 #include <boost/container/flat_set.hpp>
9 #include <chrono>
10 #include <iomanip>
11 #include <iostream>
12 #include <sdbusplus/asio/connection.hpp>
13 #include <sdbusplus/asio/object_server.hpp>
14 
15 constexpr const char* OBJECT_MAPPER_DBUS_NAME =
16     "xyz.openbmc_project.ObjectMapper";
17 constexpr const char* ASSOCIATIONS_INTERFACE = "org.openbmc.Associations";
18 constexpr const char* XYZ_ASSOCIATION_INTERFACE =
19     "xyz.openbmc_project.Association";
20 
21 // interface_map_type is the underlying datastructure the mapper uses.
22 // The 3 levels of map are
23 // object paths
24 //   connection names
25 //      interface names
26 using interface_map_type = boost::container::flat_map<
27     std::string, boost::container::flat_map<
28                      std::string, boost::container::flat_set<std::string>>>;
29 
30 using Association = std::tuple<std::string, std::string, std::string>;
31 
32 //  Associations and some metadata are stored in associationInterfaces.
33 //  The fields are:
34 //   * ifacePos - holds the D-Bus interface object
35 //   * endpointsPos - holds the endpoints array that shadows the property
36 static constexpr auto ifacePos = 0;
37 static constexpr auto endpointsPos = 1;
38 using Endpoints = std::vector<std::string>;
39 boost::container::flat_map<
40     std::string,
41     std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>
42     associationInterfaces;
43 
44 // The associationOwners map contains information about creators of
45 // associations, so that when a org.openbmc.Association interface is
46 // removed or its 'associations' property is changed, the mapper owned
47 // association objects can be correctly handled.  It is a map of the
48 // object path of the org.openbmc.Association owner to a map of the
49 // service the path is owned by, to a map of the association objects to
50 // their endpoint paths:
51 // map[ownerPath : map[service : map[assocPath : [endpoint paths]]]
52 // For example:
53 // [/logging/entry/1 :
54 //   [xyz.openbmc_project.Logging :
55 //     [/logging/entry/1/callout : [/system/cpu0],
56 //      /system/cpu0/fault : [/logging/entry/1]]]]
57 
58 using AssociationPaths =
59     boost::container::flat_map<std::string,
60                                boost::container::flat_set<std::string>>;
61 
62 using AssociationOwnersType = boost::container::flat_map<
63     std::string, boost::container::flat_map<std::string, AssociationPaths>>;
64 
65 AssociationOwnersType associationOwners;
66 
67 static boost::container::flat_set<std::string> service_whitelist;
68 static boost::container::flat_set<std::string> service_blacklist;
69 
70 /** Exception thrown when a path is not found in the object list. */
71 struct NotFoundException final : public sdbusplus::exception_t
72 {
73     const char* name() const noexcept override
74     {
75         return "org.freedesktop.DBus.Error.FileNotFound";
76     };
77     const char* description() const noexcept override
78     {
79         return "path or object not found";
80     };
81     const char* what() const noexcept override
82     {
83         return "org.freedesktop.DBus.Error.FileNotFound: "
84                "The requested object was not found";
85     };
86 };
87 
88 bool get_well_known(
89     boost::container::flat_map<std::string, std::string>& owners,
90     const std::string& request, std::string& well_known)
91 {
92     // If it's already a well known name, just return
93     if (!boost::starts_with(request, ":"))
94     {
95         well_known = request;
96         return true;
97     }
98 
99     auto it = owners.find(request);
100     if (it == owners.end())
101     {
102         return false;
103     }
104     well_known = it->second;
105     return true;
106 }
107 
108 void update_owners(sdbusplus::asio::connection* conn,
109                    boost::container::flat_map<std::string, std::string>& owners,
110                    const std::string& new_object)
111 {
112     if (boost::starts_with(new_object, ":"))
113     {
114         return;
115     }
116     conn->async_method_call(
117         [&, new_object](const boost::system::error_code ec,
118                         const std::string& nameOwner) {
119             if (ec)
120             {
121                 std::cerr << "Error getting owner of " << new_object << " : "
122                           << ec << "\n";
123                 return;
124             }
125             owners[nameOwner] = new_object;
126         },
127         "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
128         new_object);
129 }
130 
131 void send_introspection_complete_signal(sdbusplus::asio::connection* system_bus,
132                                         const std::string& process_name)
133 {
134     // TODO(ed) This signal doesn't get exposed properly in the
135     // introspect right now.  Find out how to register signals in
136     // sdbusplus
137     sdbusplus::message::message m = system_bus->new_signal(
138         "/xyz/openbmc_project/object_mapper",
139         "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete");
140     m.append(process_name);
141     m.signal_send();
142 }
143 
144 struct InProgressIntrospect
145 {
146     InProgressIntrospect(
147         sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
148         const std::string& process_name
149 #ifdef DEBUG
150         ,
151         std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
152             global_start_time
153 #endif
154         ) :
155         system_bus(system_bus),
156         io(io), process_name(process_name)
157 #ifdef DEBUG
158         ,
159         global_start_time(global_start_time),
160         process_start_time(std::chrono::steady_clock::now())
161 #endif
162     {
163     }
164     ~InProgressIntrospect()
165     {
166         send_introspection_complete_signal(system_bus, process_name);
167 
168 #ifdef DEBUG
169         std::chrono::duration<float> diff =
170             std::chrono::steady_clock::now() - process_start_time;
171         std::cout << std::setw(50) << process_name << " scan took "
172                   << diff.count() << " seconds\n";
173 
174         // If we're the last outstanding caller globally, calculate the
175         // time it took
176         if (global_start_time != nullptr && global_start_time.use_count() == 1)
177         {
178             diff = std::chrono::steady_clock::now() - *global_start_time;
179             std::cout << "Total scan took " << diff.count()
180                       << " seconds to complete\n";
181         }
182 #endif
183     }
184     sdbusplus::asio::connection* system_bus;
185     boost::asio::io_service& io;
186     std::string process_name;
187 #ifdef DEBUG
188     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
189         global_start_time;
190     std::chrono::time_point<std::chrono::steady_clock> process_start_time;
191 #endif
192 };
193 
194 // Called when either a new org.openbmc.Associations interface was
195 // created, or the associations property on that interface changed.
196 void addAssociation(sdbusplus::asio::object_server& objectServer,
197                     const std::vector<Association>& associations,
198                     const std::string& path, const std::string& owner)
199 {
200     AssociationPaths objects;
201 
202     for (const Association& association : associations)
203     {
204         std::string forward;
205         std::string reverse;
206         std::string endpoint;
207         std::tie(forward, reverse, endpoint) = association;
208 
209         if (forward.size())
210         {
211             objects[path + "/" + forward].emplace(endpoint);
212         }
213         if (reverse.size())
214         {
215             if (endpoint.empty())
216             {
217                 std::cerr << "Found invalid association on path " << path
218                           << "\n";
219                 continue;
220             }
221             objects[endpoint + "/" + reverse].emplace(path);
222         }
223     }
224     for (const auto& object : objects)
225     {
226         // the mapper exposes the new association interface but intakes
227         // the old
228 
229         auto& iface = associationInterfaces[object.first];
230         auto& i = std::get<ifacePos>(iface);
231         auto& endpoints = std::get<endpointsPos>(iface);
232 
233         // Only add new endpoints
234         for (auto& e : object.second)
235         {
236             if (std::find(endpoints.begin(), endpoints.end(), e) ==
237                 endpoints.end())
238             {
239                 endpoints.push_back(e);
240             }
241         }
242 
243         // If the interface already exists, only need to update
244         // the property value, otherwise create it
245         if (i)
246         {
247             i->set_property("endpoints", endpoints);
248         }
249         else
250         {
251             i = objectServer.add_interface(object.first,
252                                            XYZ_ASSOCIATION_INTERFACE);
253             i->register_property("endpoints", endpoints);
254             i->initialize();
255         }
256     }
257 
258     // Update associationOwners with the latest info
259     auto a = associationOwners.find(path);
260     if (a != associationOwners.end())
261     {
262         auto o = a->second.find(owner);
263         if (o != a->second.end())
264         {
265             o->second = std::move(objects);
266         }
267         else
268         {
269             a->second.emplace(owner, std::move(objects));
270         }
271     }
272     else
273     {
274         boost::container::flat_map<std::string, AssociationPaths> owners;
275         owners.emplace(owner, std::move(objects));
276         associationOwners.emplace(path, owners);
277     }
278 }
279 
280 void removeAssociation(const std::string& sourcePath, const std::string& owner,
281                        sdbusplus::asio::object_server& server)
282 {
283     // Use associationOwners to find the association paths and endpoints
284     // that the passed in object path and service own.  Remove all of
285     // these endpoints from the actual association D-Bus objects, and if
286     // the endpoints property is then empty, the whole association object
287     // can be removed.  Note there can be multiple services that own an
288     // association, and also that sourcePath is the path of the object
289     // that contains the org.openbmc.Associations interface and not the
290     // association path itself.
291 
292     // Find the services that have associations for this object path
293     auto owners = associationOwners.find(sourcePath);
294     if (owners == associationOwners.end())
295     {
296         return;
297     }
298 
299     // Find the association paths and endpoints owned by this object
300     // path for this service.
301     auto assocs = owners->second.find(owner);
302     if (assocs == owners->second.end())
303     {
304         return;
305     }
306 
307     for (const auto& [assocPath, endpointsToRemove] : assocs->second)
308     {
309         // Get the association D-Bus object for this assocPath
310         auto target = associationInterfaces.find(assocPath);
311         if (target == associationInterfaces.end())
312         {
313             continue;
314         }
315 
316         // Remove the entries in the endpoints D-Bus property for this
317         // path/owner/association-path.
318         auto& existingEndpoints = std::get<endpointsPos>(target->second);
319         for (const auto& endpointToRemove : endpointsToRemove)
320         {
321             auto e = std::find(existingEndpoints.begin(),
322                                existingEndpoints.end(), endpointToRemove);
323 
324             if (e != existingEndpoints.end())
325             {
326                 existingEndpoints.erase(e);
327             }
328         }
329 
330         // Remove the association from D-Bus if there are no more endpoints,
331         // otherwise just update the endpoints property.
332         if (existingEndpoints.empty())
333         {
334             server.remove_interface(std::get<ifacePos>(target->second));
335             std::get<ifacePos>(target->second) = nullptr;
336             std::get<endpointsPos>(target->second).clear();
337         }
338         else
339         {
340             std::get<ifacePos>(target->second)
341                 ->set_property("endpoints", existingEndpoints);
342         }
343     }
344 
345     // Remove the associationOwners entries for this owning path/service.
346     owners->second.erase(assocs);
347     if (owners->second.empty())
348     {
349         associationOwners.erase(owners);
350     }
351 }
352 
353 void do_associations(sdbusplus::asio::connection* system_bus,
354                      sdbusplus::asio::object_server& objectServer,
355                      const std::string& processName, const std::string& path)
356 {
357     system_bus->async_method_call(
358         [&objectServer, path, processName](
359             const boost::system::error_code ec,
360             const sdbusplus::message::variant<std::vector<Association>>&
361                 variantAssociations) {
362             if (ec)
363             {
364                 std::cerr << "Error getting associations from " << path << "\n";
365             }
366             std::vector<Association> associations =
367                 sdbusplus::message::variant_ns::get<std::vector<Association>>(
368                     variantAssociations);
369             addAssociation(objectServer, associations, path, processName);
370         },
371         processName, path, "org.freedesktop.DBus.Properties", "Get",
372         ASSOCIATIONS_INTERFACE, "associations");
373 }
374 
375 void do_introspect(sdbusplus::asio::connection* system_bus,
376                    std::shared_ptr<InProgressIntrospect> transaction,
377                    interface_map_type& interface_map,
378                    sdbusplus::asio::object_server& objectServer,
379                    std::string path)
380 {
381     system_bus->async_method_call(
382         [&interface_map, &objectServer, transaction, path,
383          system_bus](const boost::system::error_code ec,
384                      const std::string& introspect_xml) {
385             if (ec)
386             {
387                 std::cerr << "Introspect call failed with error: " << ec << ", "
388                           << ec.message()
389                           << " on process: " << transaction->process_name
390                           << " path: " << path << "\n";
391                 return;
392             }
393 
394             tinyxml2::XMLDocument doc;
395 
396             tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str());
397             if (e != tinyxml2::XMLError::XML_SUCCESS)
398             {
399                 std::cerr << "XML parsing failed\n";
400                 return;
401             }
402 
403             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
404             if (pRoot == nullptr)
405             {
406                 std::cerr << "XML document did not contain any data\n";
407                 return;
408             }
409             auto& thisPathMap = interface_map[path];
410             tinyxml2::XMLElement* pElement =
411                 pRoot->FirstChildElement("interface");
412             while (pElement != nullptr)
413             {
414                 const char* iface_name = pElement->Attribute("name");
415                 if (iface_name == nullptr)
416                 {
417                     continue;
418                 }
419 
420                 std::string iface{iface_name};
421 
422                 thisPathMap[transaction->process_name].emplace(iface_name);
423 
424                 if (std::strcmp(iface_name, ASSOCIATIONS_INTERFACE) == 0)
425                 {
426                     do_associations(system_bus, objectServer,
427                                     transaction->process_name, path);
428                 }
429 
430                 pElement = pElement->NextSiblingElement("interface");
431             }
432 
433             pElement = pRoot->FirstChildElement("node");
434             while (pElement != nullptr)
435             {
436                 const char* child_path = pElement->Attribute("name");
437                 if (child_path != nullptr)
438                 {
439                     std::string parent_path(path);
440                     if (parent_path == "/")
441                     {
442                         parent_path.clear();
443                     }
444 
445                     do_introspect(system_bus, transaction, interface_map,
446                                   objectServer, parent_path + "/" + child_path);
447                 }
448                 pElement = pElement->NextSiblingElement("node");
449             }
450         },
451         transaction->process_name, path, "org.freedesktop.DBus.Introspectable",
452         "Introspect");
453 }
454 
455 bool need_to_introspect(const std::string& process_name)
456 {
457     auto inWhitelist =
458         std::find_if(service_whitelist.begin(), service_whitelist.end(),
459                      [&process_name](const auto& prefix) {
460                          return boost::starts_with(process_name, prefix);
461                      }) != service_whitelist.end();
462 
463     // This holds full service names, not prefixes
464     auto inBlacklist =
465         service_blacklist.find(process_name) != service_blacklist.end();
466 
467     return inWhitelist && !inBlacklist;
468 }
469 
470 void start_new_introspect(
471     sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
472     interface_map_type& interface_map, const std::string& process_name,
473 #ifdef DEBUG
474     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
475         global_start_time,
476 #endif
477     sdbusplus::asio::object_server& objectServer)
478 {
479     if (need_to_introspect(process_name))
480     {
481         std::shared_ptr<InProgressIntrospect> transaction =
482             std::make_shared<InProgressIntrospect>(system_bus, io, process_name
483 #ifdef DEBUG
484                                                    ,
485                                                    global_start_time
486 #endif
487             );
488 
489         do_introspect(system_bus, transaction, interface_map, objectServer,
490                       "/");
491     }
492 }
493 
494 // TODO(ed) replace with std::set_intersection once c++17 is available
495 template <class InputIt1, class InputIt2>
496 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
497 {
498     while (first1 != last1 && first2 != last2)
499     {
500         if (*first1 < *first2)
501         {
502             ++first1;
503             continue;
504         }
505         if (*first2 < *first1)
506         {
507             ++first2;
508             continue;
509         }
510         return true;
511     }
512     return false;
513 }
514 
515 void doListNames(
516     boost::asio::io_service& io, interface_map_type& interface_map,
517     sdbusplus::asio::connection* system_bus,
518     boost::container::flat_map<std::string, std::string>& name_owners,
519     sdbusplus::asio::object_server& objectServer)
520 {
521     system_bus->async_method_call(
522         [&io, &interface_map, &name_owners, &objectServer,
523          system_bus](const boost::system::error_code ec,
524                      std::vector<std::string> process_names) {
525             if (ec)
526             {
527                 std::cerr << "Error getting names: " << ec << "\n";
528                 std::exit(EXIT_FAILURE);
529                 return;
530             }
531             // Try to make startup consistent
532             std::sort(process_names.begin(), process_names.end());
533 #ifdef DEBUG
534             std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
535                 global_start_time = std::make_shared<
536                     std::chrono::time_point<std::chrono::steady_clock>>(
537                     std::chrono::steady_clock::now());
538 #endif
539             for (const std::string& process_name : process_names)
540             {
541                 if (need_to_introspect(process_name))
542                 {
543                     start_new_introspect(system_bus, io, interface_map,
544                                          process_name,
545 #ifdef DEBUG
546                                          global_start_time,
547 #endif
548                                          objectServer);
549                     update_owners(system_bus, name_owners, process_name);
550                 }
551             }
552         },
553         "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
554         "ListNames");
555 }
556 
557 void splitArgs(const std::string& stringArgs,
558                boost::container::flat_set<std::string>& listArgs)
559 {
560     std::istringstream args;
561     std::string arg;
562 
563     args.str(stringArgs);
564 
565     while (!args.eof())
566     {
567         args >> arg;
568         if (!arg.empty())
569         {
570             listArgs.insert(arg);
571         }
572     }
573 }
574 
575 void addObjectMapResult(
576     std::vector<interface_map_type::value_type>& objectMap,
577     const std::string& objectPath,
578     const std::pair<std::string, boost::container::flat_set<std::string>>&
579         interfaceMap)
580 {
581     // Adds an object path/service name/interface list entry to
582     // the results of GetSubTree and GetAncestors.
583     // If an entry for the object path already exists, just add the
584     // service name and interfaces to that entry, otherwise create
585     // a new entry.
586     auto entry = std::find_if(
587         objectMap.begin(), objectMap.end(),
588         [&objectPath](const auto& i) { return objectPath == i.first; });
589 
590     if (entry != objectMap.end())
591     {
592         entry->second.emplace(interfaceMap);
593     }
594     else
595     {
596         interface_map_type::value_type object;
597         object.first = objectPath;
598         object.second.emplace(interfaceMap);
599         objectMap.push_back(object);
600     }
601 }
602 
603 // Remove parents of the passed in path that:
604 // 1) Only have the 3 default interfaces on them
605 //    - Means D-Bus created these, not application code,
606 //      with the Properties, Introspectable, and Peer ifaces
607 // 2) Have no other child for this owner
608 void removeUnneededParents(const std::string& objectPath,
609                            const std::string& owner,
610                            interface_map_type& interface_map)
611 {
612     auto parent = objectPath;
613 
614     while (true)
615     {
616         auto pos = parent.find_last_of('/');
617         if ((pos == std::string::npos) || (pos == 0))
618         {
619             break;
620         }
621         parent = parent.substr(0, pos);
622 
623         auto parent_it = interface_map.find(parent);
624         if (parent_it == interface_map.end())
625         {
626             break;
627         }
628 
629         auto ifaces_it = parent_it->second.find(owner);
630         if (ifaces_it == parent_it->second.end())
631         {
632             break;
633         }
634 
635         if (ifaces_it->second.size() != 3)
636         {
637             break;
638         }
639 
640         auto child_path = parent + '/';
641 
642         // Remove this parent if there isn't a remaining child on this owner
643         auto child = std::find_if(
644             interface_map.begin(), interface_map.end(),
645             [&owner, &child_path](const auto& entry) {
646                 return boost::starts_with(entry.first, child_path) &&
647                        (entry.second.find(owner) != entry.second.end());
648             });
649 
650         if (child == interface_map.end())
651         {
652             parent_it->second.erase(ifaces_it);
653             if (parent_it->second.empty())
654             {
655                 interface_map.erase(parent_it);
656             }
657         }
658         else
659         {
660             break;
661         }
662     }
663 }
664 
665 int main(int argc, char** argv)
666 {
667     auto options = ArgumentParser(argc, argv);
668     boost::asio::io_service io;
669     std::shared_ptr<sdbusplus::asio::connection> system_bus =
670         std::make_shared<sdbusplus::asio::connection>(io);
671 
672     splitArgs(options["service-namespaces"], service_whitelist);
673     splitArgs(options["service-blacklists"], service_blacklist);
674 
675     // TODO(Ed) Remove this once all service files are updated to not use this.
676     // For now, simply squash the input, and ignore it.
677     boost::container::flat_set<std::string> iface_whitelist;
678     splitArgs(options["interface-namespaces"], iface_whitelist);
679 
680     system_bus->request_name(OBJECT_MAPPER_DBUS_NAME);
681     sdbusplus::asio::object_server server(system_bus);
682 
683     // Construct a signal set registered for process termination.
684     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
685     signals.async_wait([&io](const boost::system::error_code& error,
686                              int signal_number) { io.stop(); });
687 
688     interface_map_type interface_map;
689     boost::container::flat_map<std::string, std::string> name_owners;
690 
691     std::function<void(sdbusplus::message::message & message)>
692         nameChangeHandler = [&interface_map, &io, &name_owners, &server,
693                              system_bus](sdbusplus::message::message& message) {
694             std::string name;
695             std::string old_owner;
696             std::string new_owner;
697 
698             message.read(name, old_owner, new_owner);
699 
700             if (!old_owner.empty())
701             {
702                 if (boost::starts_with(old_owner, ":"))
703                 {
704                     auto it = name_owners.find(old_owner);
705                     if (it != name_owners.end())
706                     {
707                         name_owners.erase(it);
708                     }
709                 }
710                 // Connection removed
711                 interface_map_type::iterator path_it = interface_map.begin();
712                 while (path_it != interface_map.end())
713                 {
714                     // If an associations interface is being removed,
715                     // also need to remove the corresponding associations
716                     // objects and properties.
717                     auto ifaces = path_it->second.find(name);
718                     if (ifaces != path_it->second.end())
719                     {
720                         auto assoc = std::find(ifaces->second.begin(),
721                                                ifaces->second.end(),
722                                                ASSOCIATIONS_INTERFACE);
723 
724                         if (assoc != ifaces->second.end())
725                         {
726                             removeAssociation(path_it->first, name, server);
727                         }
728                     }
729 
730                     path_it->second.erase(name);
731                     if (path_it->second.empty())
732                     {
733                         // If the last connection to the object is gone,
734                         // delete the top level object
735                         path_it = interface_map.erase(path_it);
736                         continue;
737                     }
738                     path_it++;
739                 }
740             }
741 
742             if (!new_owner.empty())
743             {
744 #ifdef DEBUG
745                 auto transaction = std::make_shared<
746                     std::chrono::time_point<std::chrono::steady_clock>>(
747                     std::chrono::steady_clock::now());
748 #endif
749                 // New daemon added
750                 if (need_to_introspect(name))
751                 {
752                     name_owners[new_owner] = name;
753                     start_new_introspect(system_bus.get(), io, interface_map,
754                                          name,
755 #ifdef DEBUG
756                                          transaction,
757 #endif
758                                          server);
759                 }
760             }
761         };
762 
763     sdbusplus::bus::match::match nameOwnerChanged(
764         static_cast<sdbusplus::bus::bus&>(*system_bus),
765         sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
766 
767     std::function<void(sdbusplus::message::message & message)>
768         interfacesAddedHandler = [&interface_map, &name_owners, &server](
769                                      sdbusplus::message::message& message) {
770             sdbusplus::message::object_path obj_path;
771             std::vector<std::pair<
772                 std::string, std::vector<std::pair<
773                                  std::string, sdbusplus::message::variant<
774                                                   std::vector<Association>>>>>>
775                 interfaces_added;
776             message.read(obj_path, interfaces_added);
777             std::string well_known;
778             if (!get_well_known(name_owners, message.get_sender(), well_known))
779             {
780                 return; // only introspect well-known
781             }
782             if (need_to_introspect(well_known))
783             {
784                 auto& iface_list = interface_map[obj_path.str];
785 
786                 for (const auto& interface_pair : interfaces_added)
787                 {
788                     iface_list[well_known].emplace(interface_pair.first);
789 
790                     if (interface_pair.first == ASSOCIATIONS_INTERFACE)
791                     {
792                         const sdbusplus::message::variant<
793                             std::vector<Association>>* variantAssociations =
794                             nullptr;
795                         for (const auto& interface : interface_pair.second)
796                         {
797                             if (interface.first == "associations")
798                             {
799                                 variantAssociations = &(interface.second);
800                             }
801                         }
802                         if (variantAssociations == nullptr)
803                         {
804                             std::cerr << "Illegal association found on "
805                                       << well_known << "\n";
806                             continue;
807                         }
808                         std::vector<Association> associations =
809                             sdbusplus::message::variant_ns::get<
810                                 std::vector<Association>>(*variantAssociations);
811                         addAssociation(server, associations, obj_path.str,
812                                        well_known);
813                     }
814                 }
815 
816                 // To handle the case where an object path is being created
817                 // with 2 or more new path segments, check if the parent paths
818                 // of this path are already in the interface map, and add them
819                 // if they aren't with just the default freedesktop interfaces.
820                 // This would be done via introspection if they would have
821                 // already existed at startup.  While we could also introspect
822                 // them now to do the work, we know there aren't any other
823                 // interfaces or we would have gotten signals for them as well,
824                 // so take a shortcut to speed things up.
825                 //
826                 // This is all needed so that mapper operations can be done
827                 // on the new parent paths.
828                 using iface_map_iterator = interface_map_type::iterator;
829                 using iface_map_value_type = boost::container::flat_map<
830                     std::string, boost::container::flat_set<std::string>>;
831                 using name_map_iterator = iface_map_value_type::iterator;
832 
833                 static const boost::container::flat_set<std::string>
834                     default_ifaces{"org.freedesktop.DBus.Introspectable",
835                                    "org.freedesktop.DBus.Peer",
836                                    "org.freedesktop.DBus.Properties"};
837 
838                 std::string parent = obj_path.str;
839                 auto pos = parent.find_last_of('/');
840 
841                 while (pos != std::string::npos)
842                 {
843                     parent = parent.substr(0, pos);
844 
845                     std::pair<iface_map_iterator, bool> parentEntry =
846                         interface_map.insert(
847                             std::make_pair(parent, iface_map_value_type{}));
848 
849                     std::pair<name_map_iterator, bool> ifaceEntry =
850                         parentEntry.first->second.insert(
851                             std::make_pair(well_known, default_ifaces));
852 
853                     if (!ifaceEntry.second)
854                     {
855                         // Entry was already there for this name so done.
856                         break;
857                     }
858 
859                     pos = parent.find_last_of('/');
860                 }
861             }
862         };
863 
864     sdbusplus::bus::match::match interfacesAdded(
865         static_cast<sdbusplus::bus::bus&>(*system_bus),
866         sdbusplus::bus::match::rules::interfacesAdded(),
867         interfacesAddedHandler);
868 
869     std::function<void(sdbusplus::message::message & message)>
870         interfacesRemovedHandler = [&interface_map, &name_owners, &server](
871                                        sdbusplus::message::message& message) {
872             sdbusplus::message::object_path obj_path;
873             std::vector<std::string> interfaces_removed;
874             message.read(obj_path, interfaces_removed);
875             auto connection_map = interface_map.find(obj_path.str);
876             if (connection_map == interface_map.end())
877             {
878                 return;
879             }
880 
881             std::string sender;
882             if (!get_well_known(name_owners, message.get_sender(), sender))
883             {
884                 return;
885             }
886             for (const std::string& interface : interfaces_removed)
887             {
888                 auto interface_set = connection_map->second.find(sender);
889                 if (interface_set == connection_map->second.end())
890                 {
891                     continue;
892                 }
893 
894                 if (interface == ASSOCIATIONS_INTERFACE)
895                 {
896                     removeAssociation(obj_path.str, sender, server);
897                 }
898 
899                 interface_set->second.erase(interface);
900                 // If this was the last interface on this connection,
901                 // erase the connection
902                 if (interface_set->second.empty())
903                 {
904                     connection_map->second.erase(interface_set);
905                 }
906             }
907             // If this was the last connection on this object path,
908             // erase the object path
909             if (connection_map->second.empty())
910             {
911                 interface_map.erase(connection_map);
912             }
913 
914             removeUnneededParents(obj_path.str, sender, interface_map);
915         };
916 
917     sdbusplus::bus::match::match interfacesRemoved(
918         static_cast<sdbusplus::bus::bus&>(*system_bus),
919         sdbusplus::bus::match::rules::interfacesRemoved(),
920         interfacesRemovedHandler);
921 
922     std::function<void(sdbusplus::message::message & message)>
923         associationChangedHandler =
924             [&server, &name_owners](sdbusplus::message::message& message) {
925                 std::string objectName;
926                 boost::container::flat_map<
927                     std::string,
928                     sdbusplus::message::variant<std::vector<Association>>>
929                     values;
930                 message.read(objectName, values);
931                 auto findAssociations = values.find("associations");
932                 if (findAssociations != values.end())
933                 {
934                     std::vector<Association> associations =
935                         sdbusplus::message::variant_ns::get<
936                             std::vector<Association>>(findAssociations->second);
937 
938                     std::string well_known;
939                     if (!get_well_known(name_owners, message.get_sender(),
940                                         well_known))
941                     {
942                         return;
943                     }
944                     addAssociation(server, associations, message.get_path(),
945                                    well_known);
946                 }
947             };
948     sdbusplus::bus::match::match associationChanged(
949         static_cast<sdbusplus::bus::bus&>(*system_bus),
950         sdbusplus::bus::match::rules::interface(
951             "org.freedesktop.DBus.Properties") +
952             sdbusplus::bus::match::rules::member("PropertiesChanged") +
953             sdbusplus::bus::match::rules::argN(0, ASSOCIATIONS_INTERFACE),
954         associationChangedHandler);
955 
956     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
957         server.add_interface("/xyz/openbmc_project/object_mapper",
958                              "xyz.openbmc_project.ObjectMapper");
959 
960     iface->register_method(
961         "GetAncestors", [&interface_map](std::string& req_path,
962                                          std::vector<std::string>& interfaces) {
963             // Interfaces need to be sorted for intersect to function
964             std::sort(interfaces.begin(), interfaces.end());
965 
966             if (boost::ends_with(req_path, "/"))
967             {
968                 req_path.pop_back();
969             }
970             if (req_path.size() &&
971                 interface_map.find(req_path) == interface_map.end())
972             {
973                 throw NotFoundException();
974             }
975 
976             std::vector<interface_map_type::value_type> ret;
977             for (auto& object_path : interface_map)
978             {
979                 auto& this_path = object_path.first;
980                 if (boost::starts_with(req_path, this_path) &&
981                     (req_path != this_path))
982                 {
983                     if (interfaces.empty())
984                     {
985                         ret.emplace_back(object_path);
986                     }
987                     else
988                     {
989                         for (auto& interface_map : object_path.second)
990                         {
991 
992                             if (intersect(interfaces.begin(), interfaces.end(),
993                                           interface_map.second.begin(),
994                                           interface_map.second.end()))
995                             {
996                                 addObjectMapResult(ret, this_path,
997                                                    interface_map);
998                             }
999                         }
1000                     }
1001                 }
1002             }
1003 
1004             return ret;
1005         });
1006 
1007     iface->register_method(
1008         "GetObject", [&interface_map](const std::string& path,
1009                                       std::vector<std::string>& interfaces) {
1010             boost::container::flat_map<std::string,
1011                                        boost::container::flat_set<std::string>>
1012                 results;
1013 
1014             // Interfaces need to be sorted for intersect to function
1015             std::sort(interfaces.begin(), interfaces.end());
1016             auto path_ref = interface_map.find(path);
1017             if (path_ref == interface_map.end())
1018             {
1019                 throw NotFoundException();
1020             }
1021             if (interfaces.empty())
1022             {
1023                 return path_ref->second;
1024             }
1025             for (auto& interface_map : path_ref->second)
1026             {
1027                 if (intersect(interfaces.begin(), interfaces.end(),
1028                               interface_map.second.begin(),
1029                               interface_map.second.end()))
1030                 {
1031                     results.emplace(interface_map.first, interface_map.second);
1032                 }
1033             }
1034 
1035             if (results.empty())
1036             {
1037                 throw NotFoundException();
1038             }
1039 
1040             return results;
1041         });
1042 
1043     iface->register_method(
1044         "GetSubTree", [&interface_map](std::string& req_path, int32_t depth,
1045                                        std::vector<std::string>& interfaces) {
1046             if (depth <= 0)
1047             {
1048                 depth = std::numeric_limits<int32_t>::max();
1049             }
1050             // Interfaces need to be sorted for intersect to function
1051             std::sort(interfaces.begin(), interfaces.end());
1052             std::vector<interface_map_type::value_type> ret;
1053 
1054             if (boost::ends_with(req_path, "/"))
1055             {
1056                 req_path.pop_back();
1057             }
1058             if (req_path.size() &&
1059                 interface_map.find(req_path) == interface_map.end())
1060             {
1061                 throw NotFoundException();
1062             }
1063 
1064             for (auto& object_path : interface_map)
1065             {
1066                 auto& this_path = object_path.first;
1067 
1068                 if (this_path == req_path)
1069                 {
1070                     continue;
1071                 }
1072 
1073                 if (boost::starts_with(this_path, req_path))
1074                 {
1075                     // count the number of slashes past the search term
1076                     int32_t this_depth =
1077                         std::count(this_path.begin() + req_path.size(),
1078                                    this_path.end(), '/');
1079                     if (this_depth <= depth)
1080                     {
1081                         for (auto& interface_map : object_path.second)
1082                         {
1083                             if (intersect(interfaces.begin(), interfaces.end(),
1084                                           interface_map.second.begin(),
1085                                           interface_map.second.end()) ||
1086                                 interfaces.empty())
1087                             {
1088                                 addObjectMapResult(ret, this_path,
1089                                                    interface_map);
1090                             }
1091                         }
1092                     }
1093                 }
1094             }
1095 
1096             return ret;
1097         });
1098 
1099     iface->register_method(
1100         "GetSubTreePaths",
1101         [&interface_map](std::string& req_path, int32_t depth,
1102                          std::vector<std::string>& interfaces) {
1103             if (depth <= 0)
1104             {
1105                 depth = std::numeric_limits<int32_t>::max();
1106             }
1107             // Interfaces need to be sorted for intersect to function
1108             std::sort(interfaces.begin(), interfaces.end());
1109             std::vector<std::string> ret;
1110 
1111             if (boost::ends_with(req_path, "/"))
1112             {
1113                 req_path.pop_back();
1114             }
1115             if (req_path.size() &&
1116                 interface_map.find(req_path) == interface_map.end())
1117             {
1118                 throw NotFoundException();
1119             }
1120 
1121             for (auto& object_path : interface_map)
1122             {
1123                 auto& this_path = object_path.first;
1124 
1125                 if (this_path == req_path)
1126                 {
1127                     continue;
1128                 }
1129 
1130                 if (boost::starts_with(this_path, req_path))
1131                 {
1132                     // count the number of slashes past the search term
1133                     int this_depth =
1134                         std::count(this_path.begin() + req_path.size(),
1135                                    this_path.end(), '/');
1136                     if (this_depth <= depth)
1137                     {
1138                         bool add = interfaces.empty();
1139                         for (auto& interface_map : object_path.second)
1140                         {
1141                             if (intersect(interfaces.begin(), interfaces.end(),
1142                                           interface_map.second.begin(),
1143                                           interface_map.second.end()))
1144                             {
1145                                 add = true;
1146                                 break;
1147                             }
1148                         }
1149                         if (add)
1150                         {
1151                             // TODO(ed) this is a copy
1152                             ret.emplace_back(this_path);
1153                         }
1154                     }
1155                 }
1156             }
1157 
1158             return ret;
1159         });
1160 
1161     iface->initialize();
1162 
1163     io.post([&]() {
1164         doListNames(io, interface_map, system_bus.get(), name_owners, server);
1165     });
1166 
1167     io.run();
1168 }
1169