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