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