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