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