xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 3b025e69)
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 <boost/container/flat_set.hpp>
10 #include <chrono>
11 #include <iomanip>
12 #include <iostream>
13 #include <sdbusplus/asio/connection.hpp>
14 #include <sdbusplus/asio/object_server.hpp>
15 
16 constexpr const char* OBJECT_MAPPER_DBUS_NAME =
17     "xyz.openbmc_project.ObjectMapper";
18 constexpr const char* ASSOCIATIONS_INTERFACE = "org.openbmc.Associations";
19 constexpr const char* XYZ_ASSOCIATION_INTERFACE =
20     "xyz.openbmc_project.Association";
21 
22 // interface_map_type is the underlying datastructure the mapper uses.
23 // The 3 levels of map are
24 // object paths
25 //   connection names
26 //      interface names
27 using interface_map_type = boost::container::flat_map<
28     std::string, boost::container::flat_map<
29                      std::string, boost::container::flat_set<std::string>>>;
30 
31 using Association = std::tuple<std::string, std::string, std::string>;
32 
33 //  Associations and some metadata are stored in associationInterfaces.
34 //  The fields are:
35 //   * ifacePos - holds the D-Bus interface object
36 //   * endpointsPos - holds the endpoints array that shadows the property
37 static constexpr auto ifacePos = 0;
38 static constexpr auto endpointsPos = 1;
39 using Endpoints = std::vector<std::string>;
40 boost::container::flat_map<
41     std::string,
42     std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>
43     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 boost::container::flat_set<std::string> service_whitelist;
69 static boost::container::flat_set<std::string> 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 {
371     // Use associationOwners to find the association paths and endpoints
372     // that the passed in object path and service own.  Remove all of
373     // these endpoints from the actual association D-Bus objects, and if
374     // the endpoints property is then empty, the whole association object
375     // can be removed.  Note there can be multiple services that own an
376     // association, and also that sourcePath is the path of the object
377     // that contains the org.openbmc.Associations interface and not the
378     // association path itself.
379 
380     // Find the services that have associations for this object path
381     auto owners = associationOwners.find(sourcePath);
382     if (owners == associationOwners.end())
383     {
384         return;
385     }
386 
387     // Find the association paths and endpoints owned by this object
388     // path for this service.
389     auto assocs = owners->second.find(owner);
390     if (assocs == owners->second.end())
391     {
392         return;
393     }
394 
395     for (const auto& [assocPath, endpointsToRemove] : assocs->second)
396     {
397         // Get the association D-Bus object for this assocPath
398         auto target = associationInterfaces.find(assocPath);
399         if (target == associationInterfaces.end())
400         {
401             continue;
402         }
403 
404         // Remove the entries in the endpoints D-Bus property for this
405         // path/owner/association-path.
406         auto& existingEndpoints = std::get<endpointsPos>(target->second);
407         for (const auto& endpointToRemove : endpointsToRemove)
408         {
409             auto e = std::find(existingEndpoints.begin(),
410                                existingEndpoints.end(), endpointToRemove);
411 
412             if (e != existingEndpoints.end())
413             {
414                 existingEndpoints.erase(e);
415             }
416         }
417 
418         // Remove the association from D-Bus if there are no more endpoints,
419         // otherwise just update the endpoints property.
420         if (existingEndpoints.empty())
421         {
422             server.remove_interface(std::get<ifacePos>(target->second));
423             std::get<ifacePos>(target->second) = nullptr;
424             std::get<endpointsPos>(target->second).clear();
425         }
426         else
427         {
428             std::get<ifacePos>(target->second)
429                 ->set_property("endpoints", existingEndpoints);
430         }
431     }
432 
433     // Remove the associationOwners entries for this owning path/service.
434     owners->second.erase(assocs);
435     if (owners->second.empty())
436     {
437         associationOwners.erase(owners);
438     }
439 }
440 
441 void do_associations(sdbusplus::asio::connection* system_bus,
442                      sdbusplus::asio::object_server& objectServer,
443                      const std::string& processName, const std::string& path)
444 {
445     system_bus->async_method_call(
446         [&objectServer, path, processName](
447             const boost::system::error_code ec,
448             const sdbusplus::message::variant<std::vector<Association>>&
449                 variantAssociations) {
450             if (ec)
451             {
452                 std::cerr << "Error getting associations from " << path << "\n";
453             }
454             std::vector<Association> associations =
455                 sdbusplus::message::variant_ns::get<std::vector<Association>>(
456                     variantAssociations);
457             associationChanged(objectServer, associations, path, processName);
458         },
459         processName, path, "org.freedesktop.DBus.Properties", "Get",
460         ASSOCIATIONS_INTERFACE, "associations");
461 }
462 
463 void do_introspect(sdbusplus::asio::connection* system_bus,
464                    std::shared_ptr<InProgressIntrospect> transaction,
465                    interface_map_type& interface_map,
466                    sdbusplus::asio::object_server& objectServer,
467                    std::string path)
468 {
469     system_bus->async_method_call(
470         [&interface_map, &objectServer, transaction, path,
471          system_bus](const boost::system::error_code ec,
472                      const std::string& introspect_xml) {
473             if (ec)
474             {
475                 std::cerr << "Introspect call failed with error: " << ec << ", "
476                           << ec.message()
477                           << " on process: " << transaction->process_name
478                           << " path: " << path << "\n";
479                 return;
480             }
481 
482             tinyxml2::XMLDocument doc;
483 
484             tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str());
485             if (e != tinyxml2::XMLError::XML_SUCCESS)
486             {
487                 std::cerr << "XML parsing failed\n";
488                 return;
489             }
490 
491             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
492             if (pRoot == nullptr)
493             {
494                 std::cerr << "XML document did not contain any data\n";
495                 return;
496             }
497             auto& thisPathMap = interface_map[path];
498             tinyxml2::XMLElement* pElement =
499                 pRoot->FirstChildElement("interface");
500             while (pElement != nullptr)
501             {
502                 const char* iface_name = pElement->Attribute("name");
503                 if (iface_name == nullptr)
504                 {
505                     continue;
506                 }
507 
508                 std::string iface{iface_name};
509 
510                 thisPathMap[transaction->process_name].emplace(iface_name);
511 
512                 if (std::strcmp(iface_name, ASSOCIATIONS_INTERFACE) == 0)
513                 {
514                     do_associations(system_bus, objectServer,
515                                     transaction->process_name, path);
516                 }
517 
518                 pElement = pElement->NextSiblingElement("interface");
519             }
520 
521             pElement = pRoot->FirstChildElement("node");
522             while (pElement != nullptr)
523             {
524                 const char* child_path = pElement->Attribute("name");
525                 if (child_path != nullptr)
526                 {
527                     std::string parent_path(path);
528                     if (parent_path == "/")
529                     {
530                         parent_path.clear();
531                     }
532 
533                     do_introspect(system_bus, transaction, interface_map,
534                                   objectServer, parent_path + "/" + child_path);
535                 }
536                 pElement = pElement->NextSiblingElement("node");
537             }
538         },
539         transaction->process_name, path, "org.freedesktop.DBus.Introspectable",
540         "Introspect");
541 }
542 
543 bool need_to_introspect(const std::string& process_name)
544 {
545     auto inWhitelist =
546         std::find_if(service_whitelist.begin(), service_whitelist.end(),
547                      [&process_name](const auto& prefix) {
548                          return boost::starts_with(process_name, prefix);
549                      }) != service_whitelist.end();
550 
551     // This holds full service names, not prefixes
552     auto inBlacklist =
553         service_blacklist.find(process_name) != service_blacklist.end();
554 
555     return inWhitelist && !inBlacklist;
556 }
557 
558 void start_new_introspect(
559     sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
560     interface_map_type& interface_map, const std::string& process_name,
561 #ifdef DEBUG
562     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
563         global_start_time,
564 #endif
565     sdbusplus::asio::object_server& objectServer)
566 {
567     if (need_to_introspect(process_name))
568     {
569         std::shared_ptr<InProgressIntrospect> transaction =
570             std::make_shared<InProgressIntrospect>(system_bus, io, process_name
571 #ifdef DEBUG
572                                                    ,
573                                                    global_start_time
574 #endif
575             );
576 
577         do_introspect(system_bus, transaction, interface_map, objectServer,
578                       "/");
579     }
580 }
581 
582 // TODO(ed) replace with std::set_intersection once c++17 is available
583 template <class InputIt1, class InputIt2>
584 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
585 {
586     while (first1 != last1 && first2 != last2)
587     {
588         if (*first1 < *first2)
589         {
590             ++first1;
591             continue;
592         }
593         if (*first2 < *first1)
594         {
595             ++first2;
596             continue;
597         }
598         return true;
599     }
600     return false;
601 }
602 
603 void doListNames(
604     boost::asio::io_service& io, interface_map_type& interface_map,
605     sdbusplus::asio::connection* system_bus,
606     boost::container::flat_map<std::string, std::string>& name_owners,
607     sdbusplus::asio::object_server& objectServer)
608 {
609     system_bus->async_method_call(
610         [&io, &interface_map, &name_owners, &objectServer,
611          system_bus](const boost::system::error_code ec,
612                      std::vector<std::string> process_names) {
613             if (ec)
614             {
615                 std::cerr << "Error getting names: " << ec << "\n";
616                 std::exit(EXIT_FAILURE);
617                 return;
618             }
619             // Try to make startup consistent
620             std::sort(process_names.begin(), process_names.end());
621 #ifdef DEBUG
622             std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
623                 global_start_time = std::make_shared<
624                     std::chrono::time_point<std::chrono::steady_clock>>(
625                     std::chrono::steady_clock::now());
626 #endif
627             for (const std::string& process_name : process_names)
628             {
629                 if (need_to_introspect(process_name))
630                 {
631                     start_new_introspect(system_bus, io, interface_map,
632                                          process_name,
633 #ifdef DEBUG
634                                          global_start_time,
635 #endif
636                                          objectServer);
637                     update_owners(system_bus, name_owners, process_name);
638                 }
639             }
640         },
641         "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
642         "ListNames");
643 }
644 
645 void splitArgs(const std::string& stringArgs,
646                boost::container::flat_set<std::string>& listArgs)
647 {
648     std::istringstream args;
649     std::string arg;
650 
651     args.str(stringArgs);
652 
653     while (!args.eof())
654     {
655         args >> arg;
656         if (!arg.empty())
657         {
658             listArgs.insert(arg);
659         }
660     }
661 }
662 
663 void addObjectMapResult(
664     std::vector<interface_map_type::value_type>& objectMap,
665     const std::string& objectPath,
666     const std::pair<std::string, boost::container::flat_set<std::string>>&
667         interfaceMap)
668 {
669     // Adds an object path/service name/interface list entry to
670     // the results of GetSubTree and GetAncestors.
671     // If an entry for the object path already exists, just add the
672     // service name and interfaces to that entry, otherwise create
673     // a new entry.
674     auto entry = std::find_if(
675         objectMap.begin(), objectMap.end(),
676         [&objectPath](const auto& i) { return objectPath == i.first; });
677 
678     if (entry != objectMap.end())
679     {
680         entry->second.emplace(interfaceMap);
681     }
682     else
683     {
684         interface_map_type::value_type object;
685         object.first = objectPath;
686         object.second.emplace(interfaceMap);
687         objectMap.push_back(object);
688     }
689 }
690 
691 // Remove parents of the passed in path that:
692 // 1) Only have the 3 default interfaces on them
693 //    - Means D-Bus created these, not application code,
694 //      with the Properties, Introspectable, and Peer ifaces
695 // 2) Have no other child for this owner
696 void removeUnneededParents(const std::string& objectPath,
697                            const std::string& owner,
698                            interface_map_type& interface_map)
699 {
700     auto parent = objectPath;
701 
702     while (true)
703     {
704         auto pos = parent.find_last_of('/');
705         if ((pos == std::string::npos) || (pos == 0))
706         {
707             break;
708         }
709         parent = parent.substr(0, pos);
710 
711         auto parent_it = interface_map.find(parent);
712         if (parent_it == interface_map.end())
713         {
714             break;
715         }
716 
717         auto ifaces_it = parent_it->second.find(owner);
718         if (ifaces_it == parent_it->second.end())
719         {
720             break;
721         }
722 
723         if (ifaces_it->second.size() != 3)
724         {
725             break;
726         }
727 
728         auto child_path = parent + '/';
729 
730         // Remove this parent if there isn't a remaining child on this owner
731         auto child = std::find_if(
732             interface_map.begin(), interface_map.end(),
733             [&owner, &child_path](const auto& entry) {
734                 return boost::starts_with(entry.first, child_path) &&
735                        (entry.second.find(owner) != entry.second.end());
736             });
737 
738         if (child == interface_map.end())
739         {
740             parent_it->second.erase(ifaces_it);
741             if (parent_it->second.empty())
742             {
743                 interface_map.erase(parent_it);
744             }
745         }
746         else
747         {
748             break;
749         }
750     }
751 }
752 
753 int main(int argc, char** argv)
754 {
755     auto options = ArgumentParser(argc, argv);
756     boost::asio::io_service io;
757     std::shared_ptr<sdbusplus::asio::connection> system_bus =
758         std::make_shared<sdbusplus::asio::connection>(io);
759 
760     splitArgs(options["service-namespaces"], service_whitelist);
761     splitArgs(options["service-blacklists"], service_blacklist);
762 
763     // TODO(Ed) Remove this once all service files are updated to not use this.
764     // For now, simply squash the input, and ignore it.
765     boost::container::flat_set<std::string> iface_whitelist;
766     splitArgs(options["interface-namespaces"], iface_whitelist);
767 
768     system_bus->request_name(OBJECT_MAPPER_DBUS_NAME);
769     sdbusplus::asio::object_server server(system_bus);
770 
771     // Construct a signal set registered for process termination.
772     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
773     signals.async_wait([&io](const boost::system::error_code& error,
774                              int signal_number) { io.stop(); });
775 
776     interface_map_type interface_map;
777     boost::container::flat_map<std::string, std::string> name_owners;
778 
779     std::function<void(sdbusplus::message::message & message)>
780         nameChangeHandler = [&interface_map, &io, &name_owners, &server,
781                              system_bus](sdbusplus::message::message& message) {
782             std::string name;
783             std::string old_owner;
784             std::string new_owner;
785 
786             message.read(name, old_owner, new_owner);
787 
788             if (!old_owner.empty())
789             {
790                 if (boost::starts_with(old_owner, ":"))
791                 {
792                     auto it = name_owners.find(old_owner);
793                     if (it != name_owners.end())
794                     {
795                         name_owners.erase(it);
796                     }
797                 }
798                 // Connection removed
799                 interface_map_type::iterator path_it = interface_map.begin();
800                 while (path_it != interface_map.end())
801                 {
802                     // If an associations interface is being removed,
803                     // also need to remove the corresponding associations
804                     // objects and properties.
805                     auto ifaces = path_it->second.find(name);
806                     if (ifaces != path_it->second.end())
807                     {
808                         auto assoc = std::find(ifaces->second.begin(),
809                                                ifaces->second.end(),
810                                                ASSOCIATIONS_INTERFACE);
811 
812                         if (assoc != ifaces->second.end())
813                         {
814                             removeAssociation(path_it->first, name, server);
815                         }
816                     }
817 
818                     path_it->second.erase(name);
819                     if (path_it->second.empty())
820                     {
821                         // If the last connection to the object is gone,
822                         // delete the top level object
823                         path_it = interface_map.erase(path_it);
824                         continue;
825                     }
826                     path_it++;
827                 }
828             }
829 
830             if (!new_owner.empty())
831             {
832 #ifdef DEBUG
833                 auto transaction = std::make_shared<
834                     std::chrono::time_point<std::chrono::steady_clock>>(
835                     std::chrono::steady_clock::now());
836 #endif
837                 // New daemon added
838                 if (need_to_introspect(name))
839                 {
840                     name_owners[new_owner] = name;
841                     start_new_introspect(system_bus.get(), io, interface_map,
842                                          name,
843 #ifdef DEBUG
844                                          transaction,
845 #endif
846                                          server);
847                 }
848             }
849         };
850 
851     sdbusplus::bus::match::match nameOwnerChanged(
852         static_cast<sdbusplus::bus::bus&>(*system_bus),
853         sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
854 
855     std::function<void(sdbusplus::message::message & message)>
856         interfacesAddedHandler = [&interface_map, &name_owners, &server](
857                                      sdbusplus::message::message& message) {
858             sdbusplus::message::object_path obj_path;
859             std::vector<std::pair<
860                 std::string, std::vector<std::pair<
861                                  std::string, sdbusplus::message::variant<
862                                                   std::vector<Association>>>>>>
863                 interfaces_added;
864             message.read(obj_path, interfaces_added);
865             std::string well_known;
866             if (!getWellKnown(name_owners, message.get_sender(), well_known))
867             {
868                 return; // only introspect well-known
869             }
870             if (need_to_introspect(well_known))
871             {
872                 auto& iface_list = interface_map[obj_path.str];
873 
874                 for (const auto& interface_pair : interfaces_added)
875                 {
876                     iface_list[well_known].emplace(interface_pair.first);
877 
878                     if (interface_pair.first == ASSOCIATIONS_INTERFACE)
879                     {
880                         const sdbusplus::message::variant<
881                             std::vector<Association>>* variantAssociations =
882                             nullptr;
883                         for (const auto& interface : interface_pair.second)
884                         {
885                             if (interface.first == "associations")
886                             {
887                                 variantAssociations = &(interface.second);
888                             }
889                         }
890                         if (variantAssociations == nullptr)
891                         {
892                             std::cerr << "Illegal association found on "
893                                       << well_known << "\n";
894                             continue;
895                         }
896                         std::vector<Association> associations =
897                             sdbusplus::message::variant_ns::get<
898                                 std::vector<Association>>(*variantAssociations);
899                         associationChanged(server, associations, obj_path.str,
900                                            well_known);
901                     }
902                 }
903 
904                 // To handle the case where an object path is being created
905                 // with 2 or more new path segments, check if the parent paths
906                 // of this path are already in the interface map, and add them
907                 // if they aren't with just the default freedesktop interfaces.
908                 // This would be done via introspection if they would have
909                 // already existed at startup.  While we could also introspect
910                 // them now to do the work, we know there aren't any other
911                 // interfaces or we would have gotten signals for them as well,
912                 // so take a shortcut to speed things up.
913                 //
914                 // This is all needed so that mapper operations can be done
915                 // on the new parent paths.
916                 using iface_map_iterator = interface_map_type::iterator;
917                 using iface_map_value_type = boost::container::flat_map<
918                     std::string, boost::container::flat_set<std::string>>;
919                 using name_map_iterator = iface_map_value_type::iterator;
920 
921                 static const boost::container::flat_set<std::string>
922                     default_ifaces{"org.freedesktop.DBus.Introspectable",
923                                    "org.freedesktop.DBus.Peer",
924                                    "org.freedesktop.DBus.Properties"};
925 
926                 std::string parent = obj_path.str;
927                 auto pos = parent.find_last_of('/');
928 
929                 while (pos != std::string::npos)
930                 {
931                     parent = parent.substr(0, pos);
932 
933                     std::pair<iface_map_iterator, bool> parentEntry =
934                         interface_map.insert(
935                             std::make_pair(parent, iface_map_value_type{}));
936 
937                     std::pair<name_map_iterator, bool> ifaceEntry =
938                         parentEntry.first->second.insert(
939                             std::make_pair(well_known, default_ifaces));
940 
941                     if (!ifaceEntry.second)
942                     {
943                         // Entry was already there for this name so done.
944                         break;
945                     }
946 
947                     pos = parent.find_last_of('/');
948                 }
949             }
950         };
951 
952     sdbusplus::bus::match::match interfacesAdded(
953         static_cast<sdbusplus::bus::bus&>(*system_bus),
954         sdbusplus::bus::match::rules::interfacesAdded(),
955         interfacesAddedHandler);
956 
957     std::function<void(sdbusplus::message::message & message)>
958         interfacesRemovedHandler = [&interface_map, &name_owners, &server](
959                                        sdbusplus::message::message& message) {
960             sdbusplus::message::object_path obj_path;
961             std::vector<std::string> interfaces_removed;
962             message.read(obj_path, interfaces_removed);
963             auto connection_map = interface_map.find(obj_path.str);
964             if (connection_map == interface_map.end())
965             {
966                 return;
967             }
968 
969             std::string sender;
970             if (!getWellKnown(name_owners, message.get_sender(), sender))
971             {
972                 return;
973             }
974             for (const std::string& interface : interfaces_removed)
975             {
976                 auto interface_set = connection_map->second.find(sender);
977                 if (interface_set == connection_map->second.end())
978                 {
979                     continue;
980                 }
981 
982                 if (interface == ASSOCIATIONS_INTERFACE)
983                 {
984                     removeAssociation(obj_path.str, sender, server);
985                 }
986 
987                 interface_set->second.erase(interface);
988                 // If this was the last interface on this connection,
989                 // erase the connection
990                 if (interface_set->second.empty())
991                 {
992                     connection_map->second.erase(interface_set);
993                 }
994             }
995             // If this was the last connection on this object path,
996             // erase the object path
997             if (connection_map->second.empty())
998             {
999                 interface_map.erase(connection_map);
1000             }
1001 
1002             removeUnneededParents(obj_path.str, sender, interface_map);
1003         };
1004 
1005     sdbusplus::bus::match::match interfacesRemoved(
1006         static_cast<sdbusplus::bus::bus&>(*system_bus),
1007         sdbusplus::bus::match::rules::interfacesRemoved(),
1008         interfacesRemovedHandler);
1009 
1010     std::function<void(sdbusplus::message::message & message)>
1011         associationChangedHandler =
1012             [&server, &name_owners](sdbusplus::message::message& message) {
1013                 std::string objectName;
1014                 boost::container::flat_map<
1015                     std::string,
1016                     sdbusplus::message::variant<std::vector<Association>>>
1017                     values;
1018                 message.read(objectName, values);
1019                 auto findAssociations = values.find("associations");
1020                 if (findAssociations != values.end())
1021                 {
1022                     std::vector<Association> associations =
1023                         sdbusplus::message::variant_ns::get<
1024                             std::vector<Association>>(findAssociations->second);
1025 
1026                     std::string well_known;
1027                     if (!getWellKnown(name_owners, message.get_sender(),
1028                                       well_known))
1029                     {
1030                         return;
1031                     }
1032                     associationChanged(server, associations, message.get_path(),
1033                                        well_known);
1034                 }
1035             };
1036     sdbusplus::bus::match::match associationChanged(
1037         static_cast<sdbusplus::bus::bus&>(*system_bus),
1038         sdbusplus::bus::match::rules::interface(
1039             "org.freedesktop.DBus.Properties") +
1040             sdbusplus::bus::match::rules::member("PropertiesChanged") +
1041             sdbusplus::bus::match::rules::argN(0, ASSOCIATIONS_INTERFACE),
1042         associationChangedHandler);
1043 
1044     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
1045         server.add_interface("/xyz/openbmc_project/object_mapper",
1046                              "xyz.openbmc_project.ObjectMapper");
1047 
1048     iface->register_method(
1049         "GetAncestors", [&interface_map](std::string& req_path,
1050                                          std::vector<std::string>& interfaces) {
1051             // Interfaces need to be sorted for intersect to function
1052             std::sort(interfaces.begin(), interfaces.end());
1053 
1054             if (boost::ends_with(req_path, "/"))
1055             {
1056                 req_path.pop_back();
1057             }
1058             if (req_path.size() &&
1059                 interface_map.find(req_path) == interface_map.end())
1060             {
1061                 throw NotFoundException();
1062             }
1063 
1064             std::vector<interface_map_type::value_type> ret;
1065             for (auto& object_path : interface_map)
1066             {
1067                 auto& this_path = object_path.first;
1068                 if (boost::starts_with(req_path, this_path) &&
1069                     (req_path != this_path))
1070                 {
1071                     if (interfaces.empty())
1072                     {
1073                         ret.emplace_back(object_path);
1074                     }
1075                     else
1076                     {
1077                         for (auto& interface_map : object_path.second)
1078                         {
1079 
1080                             if (intersect(interfaces.begin(), interfaces.end(),
1081                                           interface_map.second.begin(),
1082                                           interface_map.second.end()))
1083                             {
1084                                 addObjectMapResult(ret, this_path,
1085                                                    interface_map);
1086                             }
1087                         }
1088                     }
1089                 }
1090             }
1091 
1092             return ret;
1093         });
1094 
1095     iface->register_method(
1096         "GetObject", [&interface_map](const std::string& path,
1097                                       std::vector<std::string>& interfaces) {
1098             boost::container::flat_map<std::string,
1099                                        boost::container::flat_set<std::string>>
1100                 results;
1101 
1102             // Interfaces need to be sorted for intersect to function
1103             std::sort(interfaces.begin(), interfaces.end());
1104             auto path_ref = interface_map.find(path);
1105             if (path_ref == interface_map.end())
1106             {
1107                 throw NotFoundException();
1108             }
1109             if (interfaces.empty())
1110             {
1111                 return path_ref->second;
1112             }
1113             for (auto& interface_map : path_ref->second)
1114             {
1115                 if (intersect(interfaces.begin(), interfaces.end(),
1116                               interface_map.second.begin(),
1117                               interface_map.second.end()))
1118                 {
1119                     results.emplace(interface_map.first, interface_map.second);
1120                 }
1121             }
1122 
1123             if (results.empty())
1124             {
1125                 throw NotFoundException();
1126             }
1127 
1128             return results;
1129         });
1130 
1131     iface->register_method(
1132         "GetSubTree", [&interface_map](std::string& req_path, int32_t depth,
1133                                        std::vector<std::string>& interfaces) {
1134             if (depth <= 0)
1135             {
1136                 depth = std::numeric_limits<int32_t>::max();
1137             }
1138             // Interfaces need to be sorted for intersect to function
1139             std::sort(interfaces.begin(), interfaces.end());
1140             std::vector<interface_map_type::value_type> ret;
1141 
1142             if (boost::ends_with(req_path, "/"))
1143             {
1144                 req_path.pop_back();
1145             }
1146             if (req_path.size() &&
1147                 interface_map.find(req_path) == interface_map.end())
1148             {
1149                 throw NotFoundException();
1150             }
1151 
1152             for (auto& object_path : interface_map)
1153             {
1154                 auto& this_path = object_path.first;
1155 
1156                 if (this_path == req_path)
1157                 {
1158                     continue;
1159                 }
1160 
1161                 if (boost::starts_with(this_path, req_path))
1162                 {
1163                     // count the number of slashes past the search term
1164                     int32_t this_depth =
1165                         std::count(this_path.begin() + req_path.size(),
1166                                    this_path.end(), '/');
1167                     if (this_depth <= depth)
1168                     {
1169                         for (auto& interface_map : object_path.second)
1170                         {
1171                             if (intersect(interfaces.begin(), interfaces.end(),
1172                                           interface_map.second.begin(),
1173                                           interface_map.second.end()) ||
1174                                 interfaces.empty())
1175                             {
1176                                 addObjectMapResult(ret, this_path,
1177                                                    interface_map);
1178                             }
1179                         }
1180                     }
1181                 }
1182             }
1183 
1184             return ret;
1185         });
1186 
1187     iface->register_method(
1188         "GetSubTreePaths",
1189         [&interface_map](std::string& req_path, int32_t depth,
1190                          std::vector<std::string>& interfaces) {
1191             if (depth <= 0)
1192             {
1193                 depth = std::numeric_limits<int32_t>::max();
1194             }
1195             // Interfaces need to be sorted for intersect to function
1196             std::sort(interfaces.begin(), interfaces.end());
1197             std::vector<std::string> ret;
1198 
1199             if (boost::ends_with(req_path, "/"))
1200             {
1201                 req_path.pop_back();
1202             }
1203             if (req_path.size() &&
1204                 interface_map.find(req_path) == interface_map.end())
1205             {
1206                 throw NotFoundException();
1207             }
1208 
1209             for (auto& object_path : interface_map)
1210             {
1211                 auto& this_path = object_path.first;
1212 
1213                 if (this_path == req_path)
1214                 {
1215                     continue;
1216                 }
1217 
1218                 if (boost::starts_with(this_path, req_path))
1219                 {
1220                     // count the number of slashes past the search term
1221                     int this_depth =
1222                         std::count(this_path.begin() + req_path.size(),
1223                                    this_path.end(), '/');
1224                     if (this_depth <= depth)
1225                     {
1226                         bool add = interfaces.empty();
1227                         for (auto& interface_map : object_path.second)
1228                         {
1229                             if (intersect(interfaces.begin(), interfaces.end(),
1230                                           interface_map.second.begin(),
1231                                           interface_map.second.end()))
1232                             {
1233                                 add = true;
1234                                 break;
1235                             }
1236                         }
1237                         if (add)
1238                         {
1239                             // TODO(ed) this is a copy
1240                             ret.emplace_back(this_path);
1241                         }
1242                     }
1243                 }
1244             }
1245 
1246             return ret;
1247         });
1248 
1249     iface->initialize();
1250 
1251     io.post([&]() {
1252         doListNames(io, interface_map, system_bus.get(), name_owners, server);
1253     });
1254 
1255     io.run();
1256 }
1257