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