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