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