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