xref: /openbmc/phosphor-objmgr/src/main.cpp (revision d0cf9428)
1 #include "config.h"
2 
3 #include "associations.hpp"
4 #include "processing.hpp"
5 #include "src/argument.hpp"
6 #include "types.hpp"
7 
8 #include <tinyxml2.h>
9 
10 #include <atomic>
11 #include <boost/algorithm/string/predicate.hpp>
12 #include <boost/container/flat_map.hpp>
13 #include <chrono>
14 #include <iomanip>
15 #include <iostream>
16 #include <sdbusplus/asio/connection.hpp>
17 #include <sdbusplus/asio/object_server.hpp>
18 
19 AssociationMaps associationMaps;
20 
21 static WhiteBlackList service_whitelist;
22 static WhiteBlackList service_blacklist;
23 
24 /** Exception thrown when a path is not found in the object list. */
25 struct NotFoundException final : public sdbusplus::exception_t
26 {
27     const char* name() const noexcept override
28     {
29         return "org.freedesktop.DBus.Error.FileNotFound";
30     };
31     const char* description() const noexcept override
32     {
33         return "path or object not found";
34     };
35     const char* what() const noexcept override
36     {
37         return "org.freedesktop.DBus.Error.FileNotFound: "
38                "The requested object was not found";
39     };
40 };
41 
42 void update_owners(sdbusplus::asio::connection* conn,
43                    boost::container::flat_map<std::string, std::string>& owners,
44                    const std::string& new_object)
45 {
46     if (boost::starts_with(new_object, ":"))
47     {
48         return;
49     }
50     conn->async_method_call(
51         [&, new_object](const boost::system::error_code ec,
52                         const std::string& nameOwner) {
53             if (ec)
54             {
55                 std::cerr << "Error getting owner of " << new_object << " : "
56                           << ec << "\n";
57                 return;
58             }
59             owners[nameOwner] = new_object;
60         },
61         "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner",
62         new_object);
63 }
64 
65 void send_introspection_complete_signal(sdbusplus::asio::connection* system_bus,
66                                         const std::string& process_name)
67 {
68     // TODO(ed) This signal doesn't get exposed properly in the
69     // introspect right now.  Find out how to register signals in
70     // sdbusplus
71     sdbusplus::message::message m = system_bus->new_signal(
72         MAPPER_PATH, "xyz.openbmc_project.ObjectMapper.Private",
73         "IntrospectionComplete");
74     m.append(process_name);
75     m.signal_send();
76 }
77 
78 struct InProgressIntrospect
79 {
80     InProgressIntrospect(
81         sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
82         const std::string& process_name, AssociationMaps& am
83 #ifdef DEBUG
84         ,
85         std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
86             global_start_time
87 #endif
88         ) :
89         system_bus(system_bus),
90         io(io), process_name(process_name), assocMaps(am)
91 #ifdef DEBUG
92         ,
93         global_start_time(global_start_time),
94         process_start_time(std::chrono::steady_clock::now())
95 #endif
96     {
97     }
98     ~InProgressIntrospect()
99     {
100         send_introspection_complete_signal(system_bus, process_name);
101 
102 #ifdef DEBUG
103         std::chrono::duration<float> diff =
104             std::chrono::steady_clock::now() - process_start_time;
105         std::cout << std::setw(50) << process_name << " scan took "
106                   << diff.count() << " seconds\n";
107 
108         // If we're the last outstanding caller globally, calculate the
109         // time it took
110         if (global_start_time != nullptr && global_start_time.use_count() == 1)
111         {
112             diff = std::chrono::steady_clock::now() - *global_start_time;
113             std::cout << "Total scan took " << diff.count()
114                       << " seconds to complete\n";
115         }
116 #endif
117     }
118     sdbusplus::asio::connection* system_bus;
119     boost::asio::io_service& io;
120     std::string process_name;
121     AssociationMaps& assocMaps;
122 #ifdef DEBUG
123     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
124         global_start_time;
125     std::chrono::time_point<std::chrono::steady_clock> process_start_time;
126 #endif
127 };
128 
129 void do_associations(sdbusplus::asio::connection* system_bus,
130                      interface_map_type& interfaceMap,
131                      sdbusplus::asio::object_server& objectServer,
132                      const std::string& processName, const std::string& path)
133 {
134     system_bus->async_method_call(
135         [&objectServer, path, processName, &interfaceMap](
136             const boost::system::error_code ec,
137             const sdbusplus::message::variant<std::vector<Association>>&
138                 variantAssociations) {
139             if (ec)
140             {
141                 std::cerr << "Error getting associations from " << path << "\n";
142             }
143             std::vector<Association> associations =
144                 sdbusplus::message::variant_ns::get<std::vector<Association>>(
145                     variantAssociations);
146             associationChanged(objectServer, associations, path, processName,
147                                interfaceMap, associationMaps);
148         },
149         processName, path, "org.freedesktop.DBus.Properties", "Get",
150         assocDefsInterface, assocDefsProperty);
151 }
152 
153 void do_introspect(sdbusplus::asio::connection* system_bus,
154                    std::shared_ptr<InProgressIntrospect> transaction,
155                    interface_map_type& interface_map,
156                    sdbusplus::asio::object_server& objectServer,
157                    std::string path)
158 {
159     system_bus->async_method_call(
160         [&interface_map, &objectServer, transaction, path,
161          system_bus](const boost::system::error_code ec,
162                      const std::string& introspect_xml) {
163             if (ec)
164             {
165                 std::cerr << "Introspect call failed with error: " << ec << ", "
166                           << ec.message()
167                           << " on process: " << transaction->process_name
168                           << " path: " << path << "\n";
169                 return;
170             }
171 
172             tinyxml2::XMLDocument doc;
173 
174             tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str());
175             if (e != tinyxml2::XMLError::XML_SUCCESS)
176             {
177                 std::cerr << "XML parsing failed\n";
178                 return;
179             }
180 
181             tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node");
182             if (pRoot == nullptr)
183             {
184                 std::cerr << "XML document did not contain any data\n";
185                 return;
186             }
187             auto& thisPathMap = interface_map[path];
188             tinyxml2::XMLElement* pElement =
189                 pRoot->FirstChildElement("interface");
190             while (pElement != nullptr)
191             {
192                 const char* iface_name = pElement->Attribute("name");
193                 if (iface_name == nullptr)
194                 {
195                     continue;
196                 }
197 
198                 thisPathMap[transaction->process_name].emplace(iface_name);
199 
200                 if (std::strcmp(iface_name, assocDefsInterface) == 0)
201                 {
202                     do_associations(system_bus, interface_map, objectServer,
203                                     transaction->process_name, path);
204                 }
205 
206                 pElement = pElement->NextSiblingElement("interface");
207             }
208 
209             // Check if this new path has a pending association that can
210             // now be completed.
211             checkIfPendingAssociation(path, interface_map,
212                                       transaction->assocMaps, objectServer);
213 
214             pElement = pRoot->FirstChildElement("node");
215             while (pElement != nullptr)
216             {
217                 const char* child_path = pElement->Attribute("name");
218                 if (child_path != nullptr)
219                 {
220                     std::string parent_path(path);
221                     if (parent_path == "/")
222                     {
223                         parent_path.clear();
224                     }
225 
226                     do_introspect(system_bus, transaction, interface_map,
227                                   objectServer, parent_path + "/" + child_path);
228                 }
229                 pElement = pElement->NextSiblingElement("node");
230             }
231         },
232         transaction->process_name, path, "org.freedesktop.DBus.Introspectable",
233         "Introspect");
234 }
235 
236 void start_new_introspect(
237     sdbusplus::asio::connection* system_bus, boost::asio::io_service& io,
238     interface_map_type& interface_map, const std::string& process_name,
239     AssociationMaps& assocMaps,
240 #ifdef DEBUG
241     std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
242         global_start_time,
243 #endif
244     sdbusplus::asio::object_server& objectServer)
245 {
246     if (needToIntrospect(process_name, service_whitelist, service_blacklist))
247     {
248         std::shared_ptr<InProgressIntrospect> transaction =
249             std::make_shared<InProgressIntrospect>(system_bus, io, process_name,
250                                                    assocMaps
251 #ifdef DEBUG
252                                                    ,
253                                                    global_start_time
254 #endif
255             );
256 
257         do_introspect(system_bus, transaction, interface_map, objectServer,
258                       "/");
259     }
260 }
261 
262 // TODO(ed) replace with std::set_intersection once c++17 is available
263 template <class InputIt1, class InputIt2>
264 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2)
265 {
266     while (first1 != last1 && first2 != last2)
267     {
268         if (*first1 < *first2)
269         {
270             ++first1;
271             continue;
272         }
273         if (*first2 < *first1)
274         {
275             ++first2;
276             continue;
277         }
278         return true;
279     }
280     return false;
281 }
282 
283 void doListNames(
284     boost::asio::io_service& io, interface_map_type& interface_map,
285     sdbusplus::asio::connection* system_bus,
286     boost::container::flat_map<std::string, std::string>& name_owners,
287     AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer)
288 {
289     system_bus->async_method_call(
290         [&io, &interface_map, &name_owners, &objectServer, system_bus,
291          &assocMaps](const boost::system::error_code ec,
292                      std::vector<std::string> process_names) {
293             if (ec)
294             {
295                 std::cerr << "Error getting names: " << ec << "\n";
296                 std::exit(EXIT_FAILURE);
297                 return;
298             }
299             // Try to make startup consistent
300             std::sort(process_names.begin(), process_names.end());
301 #ifdef DEBUG
302             std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>>
303                 global_start_time = std::make_shared<
304                     std::chrono::time_point<std::chrono::steady_clock>>(
305                     std::chrono::steady_clock::now());
306 #endif
307             for (const std::string& process_name : process_names)
308             {
309                 if (needToIntrospect(process_name, service_whitelist,
310                                      service_blacklist))
311                 {
312                     start_new_introspect(system_bus, io, interface_map,
313                                          process_name, assocMaps,
314 #ifdef DEBUG
315                                          global_start_time,
316 #endif
317                                          objectServer);
318                     update_owners(system_bus, name_owners, process_name);
319                 }
320             }
321         },
322         "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
323         "ListNames");
324 }
325 
326 void splitArgs(const std::string& stringArgs,
327                boost::container::flat_set<std::string>& listArgs)
328 {
329     std::istringstream args;
330     std::string arg;
331 
332     args.str(stringArgs);
333 
334     while (!args.eof())
335     {
336         args >> arg;
337         if (!arg.empty())
338         {
339             listArgs.insert(arg);
340         }
341     }
342 }
343 
344 void addObjectMapResult(
345     std::vector<interface_map_type::value_type>& objectMap,
346     const std::string& objectPath,
347     const std::pair<std::string, boost::container::flat_set<std::string>>&
348         interfaceMap)
349 {
350     // Adds an object path/service name/interface list entry to
351     // the results of GetSubTree and GetAncestors.
352     // If an entry for the object path already exists, just add the
353     // service name and interfaces to that entry, otherwise create
354     // a new entry.
355     auto entry = std::find_if(
356         objectMap.begin(), objectMap.end(),
357         [&objectPath](const auto& i) { return objectPath == i.first; });
358 
359     if (entry != objectMap.end())
360     {
361         entry->second.emplace(interfaceMap);
362     }
363     else
364     {
365         interface_map_type::value_type object;
366         object.first = objectPath;
367         object.second.emplace(interfaceMap);
368         objectMap.push_back(object);
369     }
370 }
371 
372 // Remove parents of the passed in path that:
373 // 1) Only have the 3 default interfaces on them
374 //    - Means D-Bus created these, not application code,
375 //      with the Properties, Introspectable, and Peer ifaces
376 // 2) Have no other child for this owner
377 void removeUnneededParents(const std::string& objectPath,
378                            const std::string& owner,
379                            interface_map_type& interface_map)
380 {
381     auto parent = objectPath;
382 
383     while (true)
384     {
385         auto pos = parent.find_last_of('/');
386         if ((pos == std::string::npos) || (pos == 0))
387         {
388             break;
389         }
390         parent = parent.substr(0, pos);
391 
392         auto parent_it = interface_map.find(parent);
393         if (parent_it == interface_map.end())
394         {
395             break;
396         }
397 
398         auto ifaces_it = parent_it->second.find(owner);
399         if (ifaces_it == parent_it->second.end())
400         {
401             break;
402         }
403 
404         if (ifaces_it->second.size() != 3)
405         {
406             break;
407         }
408 
409         auto child_path = parent + '/';
410 
411         // Remove this parent if there isn't a remaining child on this owner
412         auto child = std::find_if(
413             interface_map.begin(), interface_map.end(),
414             [&owner, &child_path](const auto& entry) {
415                 return boost::starts_with(entry.first, child_path) &&
416                        (entry.second.find(owner) != entry.second.end());
417             });
418 
419         if (child == interface_map.end())
420         {
421             parent_it->second.erase(ifaces_it);
422             if (parent_it->second.empty())
423             {
424                 interface_map.erase(parent_it);
425             }
426         }
427         else
428         {
429             break;
430         }
431     }
432 }
433 
434 int main(int argc, char** argv)
435 {
436     auto options = ArgumentParser(argc, argv);
437     boost::asio::io_service io;
438     std::shared_ptr<sdbusplus::asio::connection> system_bus =
439         std::make_shared<sdbusplus::asio::connection>(io);
440 
441     splitArgs(options["service-namespaces"], service_whitelist);
442     splitArgs(options["service-blacklists"], service_blacklist);
443 
444     // TODO(Ed) Remove this once all service files are updated to not use this.
445     // For now, simply squash the input, and ignore it.
446     boost::container::flat_set<std::string> iface_whitelist;
447     splitArgs(options["interface-namespaces"], iface_whitelist);
448 
449     system_bus->request_name(MAPPER_BUSNAME);
450     sdbusplus::asio::object_server server(system_bus);
451 
452     // Construct a signal set registered for process termination.
453     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
454     signals.async_wait([&io](const boost::system::error_code& error,
455                              int signal_number) { io.stop(); });
456 
457     interface_map_type interface_map;
458     boost::container::flat_map<std::string, std::string> name_owners;
459 
460     std::function<void(sdbusplus::message::message & message)>
461         nameChangeHandler = [&interface_map, &io, &name_owners, &server,
462                              system_bus](sdbusplus::message::message& message) {
463             std::string name;      // well-known
464             std::string old_owner; // unique-name
465             std::string new_owner; // unique-name
466 
467             message.read(name, old_owner, new_owner);
468 
469             if (!old_owner.empty())
470             {
471                 processNameChangeDelete(name_owners, name, old_owner,
472                                         interface_map, associationMaps, server);
473             }
474 
475             if (!new_owner.empty())
476             {
477 #ifdef DEBUG
478                 auto transaction = std::make_shared<
479                     std::chrono::time_point<std::chrono::steady_clock>>(
480                     std::chrono::steady_clock::now());
481 #endif
482                 // New daemon added
483                 if (needToIntrospect(name, service_whitelist,
484                                      service_blacklist))
485                 {
486                     name_owners[new_owner] = name;
487                     start_new_introspect(system_bus.get(), io, interface_map,
488                                          name, associationMaps,
489 #ifdef DEBUG
490                                          transaction,
491 #endif
492                                          server);
493                 }
494             }
495         };
496 
497     sdbusplus::bus::match::match nameOwnerChanged(
498         static_cast<sdbusplus::bus::bus&>(*system_bus),
499         sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
500 
501     std::function<void(sdbusplus::message::message & message)>
502         interfacesAddedHandler = [&interface_map, &name_owners, &server](
503                                      sdbusplus::message::message& message) {
504             sdbusplus::message::object_path obj_path;
505             InterfacesAdded interfaces_added;
506             message.read(obj_path, interfaces_added);
507             std::string well_known;
508             if (!getWellKnown(name_owners, message.get_sender(), well_known))
509             {
510                 return; // only introspect well-known
511             }
512             if (needToIntrospect(well_known, service_whitelist,
513                                  service_blacklist))
514             {
515                 processInterfaceAdded(interface_map, obj_path, interfaces_added,
516                                       well_known, associationMaps, server);
517             }
518         };
519 
520     sdbusplus::bus::match::match interfacesAdded(
521         static_cast<sdbusplus::bus::bus&>(*system_bus),
522         sdbusplus::bus::match::rules::interfacesAdded(),
523         interfacesAddedHandler);
524 
525     std::function<void(sdbusplus::message::message & message)>
526         interfacesRemovedHandler = [&interface_map, &name_owners, &server](
527                                        sdbusplus::message::message& message) {
528             sdbusplus::message::object_path obj_path;
529             std::vector<std::string> interfaces_removed;
530             message.read(obj_path, interfaces_removed);
531             auto connection_map = interface_map.find(obj_path.str);
532             if (connection_map == interface_map.end())
533             {
534                 return;
535             }
536 
537             std::string sender;
538             if (!getWellKnown(name_owners, message.get_sender(), sender))
539             {
540                 return;
541             }
542             for (const std::string& interface : interfaces_removed)
543             {
544                 auto interface_set = connection_map->second.find(sender);
545                 if (interface_set == connection_map->second.end())
546                 {
547                     continue;
548                 }
549 
550                 if (interface == assocDefsInterface)
551                 {
552                     removeAssociation(obj_path.str, sender, server,
553                                       associationMaps);
554                 }
555 
556                 interface_set->second.erase(interface);
557 
558                 if (interface_set->second.empty())
559                 {
560                     // If this was the last interface on this connection,
561                     // erase the connection
562                     connection_map->second.erase(interface_set);
563 
564                     // Instead of checking if every single path is the endpoint
565                     // of an association that needs to be moved to pending,
566                     // only check when the only remaining owner of this path is
567                     // ourself, which would be because we still own the
568                     // association path.
569                     if ((connection_map->second.size() == 1) &&
570                         (connection_map->second.begin()->first ==
571                          MAPPER_BUSNAME))
572                     {
573                         // Remove the 2 association D-Bus paths and move the
574                         // association to pending.
575                         moveAssociationToPending(obj_path.str, associationMaps,
576                                                  server);
577                     }
578                 }
579             }
580             // If this was the last connection on this object path,
581             // erase the object path
582             if (connection_map->second.empty())
583             {
584                 interface_map.erase(connection_map);
585             }
586 
587             removeUnneededParents(obj_path.str, sender, interface_map);
588         };
589 
590     sdbusplus::bus::match::match interfacesRemoved(
591         static_cast<sdbusplus::bus::bus&>(*system_bus),
592         sdbusplus::bus::match::rules::interfacesRemoved(),
593         interfacesRemovedHandler);
594 
595     std::function<void(sdbusplus::message::message & message)>
596         associationChangedHandler = [&server, &name_owners, &interface_map](
597                                         sdbusplus::message::message& message) {
598             std::string objectName;
599             boost::container::flat_map<
600                 std::string,
601                 sdbusplus::message::variant<std::vector<Association>>>
602                 values;
603             message.read(objectName, values);
604             auto prop = values.find(assocDefsProperty);
605             if (prop != values.end())
606             {
607                 std::vector<Association> associations =
608                     sdbusplus::message::variant_ns::get<
609                         std::vector<Association>>(prop->second);
610 
611                 std::string well_known;
612                 if (!getWellKnown(name_owners, message.get_sender(),
613                                   well_known))
614                 {
615                     return;
616                 }
617                 associationChanged(server, associations, message.get_path(),
618                                    well_known, interface_map, associationMaps);
619             }
620         };
621     sdbusplus::bus::match::match assocChangedMatch(
622         static_cast<sdbusplus::bus::bus&>(*system_bus),
623         sdbusplus::bus::match::rules::interface(
624             "org.freedesktop.DBus.Properties") +
625             sdbusplus::bus::match::rules::member("PropertiesChanged") +
626             sdbusplus::bus::match::rules::argN(0, assocDefsInterface),
627         associationChangedHandler);
628 
629     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
630         server.add_interface(MAPPER_PATH, MAPPER_INTERFACE);
631 
632     iface->register_method(
633         "GetAncestors", [&interface_map](std::string& req_path,
634                                          std::vector<std::string>& interfaces) {
635             // Interfaces need to be sorted for intersect to function
636             std::sort(interfaces.begin(), interfaces.end());
637 
638             if (boost::ends_with(req_path, "/"))
639             {
640                 req_path.pop_back();
641             }
642             if (req_path.size() &&
643                 interface_map.find(req_path) == interface_map.end())
644             {
645                 throw NotFoundException();
646             }
647 
648             std::vector<interface_map_type::value_type> ret;
649             for (auto& object_path : interface_map)
650             {
651                 auto& this_path = object_path.first;
652                 if (boost::starts_with(req_path, this_path) &&
653                     (req_path != this_path))
654                 {
655                     if (interfaces.empty())
656                     {
657                         ret.emplace_back(object_path);
658                     }
659                     else
660                     {
661                         for (auto& interface_map : object_path.second)
662                         {
663 
664                             if (intersect(interfaces.begin(), interfaces.end(),
665                                           interface_map.second.begin(),
666                                           interface_map.second.end()))
667                             {
668                                 addObjectMapResult(ret, this_path,
669                                                    interface_map);
670                             }
671                         }
672                     }
673                 }
674             }
675 
676             return ret;
677         });
678 
679     iface->register_method(
680         "GetObject", [&interface_map](const std::string& path,
681                                       std::vector<std::string>& interfaces) {
682             boost::container::flat_map<std::string,
683                                        boost::container::flat_set<std::string>>
684                 results;
685 
686             // Interfaces need to be sorted for intersect to function
687             std::sort(interfaces.begin(), interfaces.end());
688             auto path_ref = interface_map.find(path);
689             if (path_ref == interface_map.end())
690             {
691                 throw NotFoundException();
692             }
693             if (interfaces.empty())
694             {
695                 return path_ref->second;
696             }
697             for (auto& interface_map : path_ref->second)
698             {
699                 if (intersect(interfaces.begin(), interfaces.end(),
700                               interface_map.second.begin(),
701                               interface_map.second.end()))
702                 {
703                     results.emplace(interface_map.first, interface_map.second);
704                 }
705             }
706 
707             if (results.empty())
708             {
709                 throw NotFoundException();
710             }
711 
712             return results;
713         });
714 
715     iface->register_method(
716         "GetSubTree", [&interface_map](std::string& req_path, int32_t depth,
717                                        std::vector<std::string>& interfaces) {
718             if (depth <= 0)
719             {
720                 depth = std::numeric_limits<int32_t>::max();
721             }
722             // Interfaces need to be sorted for intersect to function
723             std::sort(interfaces.begin(), interfaces.end());
724             std::vector<interface_map_type::value_type> ret;
725 
726             if (boost::ends_with(req_path, "/"))
727             {
728                 req_path.pop_back();
729             }
730             if (req_path.size() &&
731                 interface_map.find(req_path) == interface_map.end())
732             {
733                 throw NotFoundException();
734             }
735 
736             for (auto& object_path : interface_map)
737             {
738                 auto& this_path = object_path.first;
739 
740                 if (this_path == req_path)
741                 {
742                     continue;
743                 }
744 
745                 if (boost::starts_with(this_path, req_path))
746                 {
747                     // count the number of slashes past the search term
748                     int32_t this_depth =
749                         std::count(this_path.begin() + req_path.size(),
750                                    this_path.end(), '/');
751                     if (this_depth <= depth)
752                     {
753                         for (auto& interface_map : object_path.second)
754                         {
755                             if (intersect(interfaces.begin(), interfaces.end(),
756                                           interface_map.second.begin(),
757                                           interface_map.second.end()) ||
758                                 interfaces.empty())
759                             {
760                                 addObjectMapResult(ret, this_path,
761                                                    interface_map);
762                             }
763                         }
764                     }
765                 }
766             }
767 
768             return ret;
769         });
770 
771     iface->register_method(
772         "GetSubTreePaths",
773         [&interface_map](std::string& req_path, int32_t depth,
774                          std::vector<std::string>& interfaces) {
775             if (depth <= 0)
776             {
777                 depth = std::numeric_limits<int32_t>::max();
778             }
779             // Interfaces need to be sorted for intersect to function
780             std::sort(interfaces.begin(), interfaces.end());
781             std::vector<std::string> ret;
782 
783             if (boost::ends_with(req_path, "/"))
784             {
785                 req_path.pop_back();
786             }
787             if (req_path.size() &&
788                 interface_map.find(req_path) == interface_map.end())
789             {
790                 throw NotFoundException();
791             }
792 
793             for (auto& object_path : interface_map)
794             {
795                 auto& this_path = object_path.first;
796 
797                 if (this_path == req_path)
798                 {
799                     continue;
800                 }
801 
802                 if (boost::starts_with(this_path, req_path))
803                 {
804                     // count the number of slashes past the search term
805                     int this_depth =
806                         std::count(this_path.begin() + req_path.size(),
807                                    this_path.end(), '/');
808                     if (this_depth <= depth)
809                     {
810                         bool add = interfaces.empty();
811                         for (auto& interface_map : object_path.second)
812                         {
813                             if (intersect(interfaces.begin(), interfaces.end(),
814                                           interface_map.second.begin(),
815                                           interface_map.second.end()))
816                             {
817                                 add = true;
818                                 break;
819                             }
820                         }
821                         if (add)
822                         {
823                             // TODO(ed) this is a copy
824                             ret.emplace_back(this_path);
825                         }
826                     }
827                 }
828             }
829 
830             return ret;
831         });
832 
833     iface->initialize();
834 
835     io.post([&]() {
836         doListNames(io, interface_map, system_bus.get(), name_owners,
837                     associationMaps, server);
838     });
839 
840     io.run();
841 }
842