xref: /openbmc/phosphor-objmgr/src/main.cpp (revision 64354ef2)
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     sdbusplus::asio::object_server server(system_bus);
456 
457     // Construct a signal set registered for process termination.
458     boost::asio::signal_set signals(io, SIGINT, SIGTERM);
459     signals.async_wait([&io](const boost::system::error_code& error,
460                              int signal_number) { io.stop(); });
461 
462     interface_map_type interface_map;
463     boost::container::flat_map<std::string, std::string> name_owners;
464 
465     std::function<void(sdbusplus::message::message & message)>
466         nameChangeHandler = [&interface_map, &io, &name_owners, &server,
467                              system_bus](sdbusplus::message::message& message) {
468             std::string name;      // well-known
469             std::string old_owner; // unique-name
470             std::string new_owner; // unique-name
471 
472             message.read(name, old_owner, new_owner);
473 
474             if (!old_owner.empty())
475             {
476                 processNameChangeDelete(name_owners, name, old_owner,
477                                         interface_map, associationMaps, server);
478             }
479 
480             if (!new_owner.empty())
481             {
482 #ifdef DEBUG
483                 auto transaction = std::make_shared<
484                     std::chrono::time_point<std::chrono::steady_clock>>(
485                     std::chrono::steady_clock::now());
486 #endif
487                 // New daemon added
488                 if (needToIntrospect(name, service_whitelist,
489                                      service_blacklist))
490                 {
491                     name_owners[new_owner] = name;
492                     start_new_introspect(system_bus.get(), io, interface_map,
493                                          name, associationMaps,
494 #ifdef DEBUG
495                                          transaction,
496 #endif
497                                          server);
498                 }
499             }
500         };
501 
502     sdbusplus::bus::match::match nameOwnerChanged(
503         static_cast<sdbusplus::bus::bus&>(*system_bus),
504         sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler);
505 
506     std::function<void(sdbusplus::message::message & message)>
507         interfacesAddedHandler = [&interface_map, &name_owners, &server](
508                                      sdbusplus::message::message& message) {
509             sdbusplus::message::object_path obj_path;
510             InterfacesAdded interfaces_added;
511             message.read(obj_path, interfaces_added);
512             std::string well_known;
513             if (!getWellKnown(name_owners, message.get_sender(), well_known))
514             {
515                 return; // only introspect well-known
516             }
517             if (needToIntrospect(well_known, service_whitelist,
518                                  service_blacklist))
519             {
520                 processInterfaceAdded(interface_map, obj_path, interfaces_added,
521                                       well_known, associationMaps, server);
522             }
523         };
524 
525     sdbusplus::bus::match::match interfacesAdded(
526         static_cast<sdbusplus::bus::bus&>(*system_bus),
527         sdbusplus::bus::match::rules::interfacesAdded(),
528         interfacesAddedHandler);
529 
530     std::function<void(sdbusplus::message::message & message)>
531         interfacesRemovedHandler = [&interface_map, &name_owners, &server](
532                                        sdbusplus::message::message& message) {
533             sdbusplus::message::object_path obj_path;
534             std::vector<std::string> interfaces_removed;
535             message.read(obj_path, interfaces_removed);
536             auto connection_map = interface_map.find(obj_path.str);
537             if (connection_map == interface_map.end())
538             {
539                 return;
540             }
541 
542             std::string sender;
543             if (!getWellKnown(name_owners, message.get_sender(), sender))
544             {
545                 return;
546             }
547             for (const std::string& interface : interfaces_removed)
548             {
549                 auto interface_set = connection_map->second.find(sender);
550                 if (interface_set == connection_map->second.end())
551                 {
552                     continue;
553                 }
554 
555                 if (interface == assocDefsInterface)
556                 {
557                     removeAssociation(obj_path.str, sender, server,
558                                       associationMaps);
559                 }
560 
561                 interface_set->second.erase(interface);
562 
563                 if (interface_set->second.empty())
564                 {
565                     // If this was the last interface on this connection,
566                     // erase the connection
567                     connection_map->second.erase(interface_set);
568 
569                     // Instead of checking if every single path is the endpoint
570                     // of an association that needs to be moved to pending,
571                     // only check when the only remaining owner of this path is
572                     // ourself, which would be because we still own the
573                     // association path.
574                     if ((connection_map->second.size() == 1) &&
575                         (connection_map->second.begin()->first ==
576                          MAPPER_BUSNAME))
577                     {
578                         // Remove the 2 association D-Bus paths and move the
579                         // association to pending.
580                         moveAssociationToPending(obj_path.str, associationMaps,
581                                                  server);
582                     }
583                 }
584             }
585             // If this was the last connection on this object path,
586             // erase the object path
587             if (connection_map->second.empty())
588             {
589                 interface_map.erase(connection_map);
590             }
591 
592             removeUnneededParents(obj_path.str, sender, interface_map);
593         };
594 
595     sdbusplus::bus::match::match interfacesRemoved(
596         static_cast<sdbusplus::bus::bus&>(*system_bus),
597         sdbusplus::bus::match::rules::interfacesRemoved(),
598         interfacesRemovedHandler);
599 
600     std::function<void(sdbusplus::message::message & message)>
601         associationChangedHandler = [&server, &name_owners, &interface_map](
602                                         sdbusplus::message::message& message) {
603             std::string objectName;
604             boost::container::flat_map<std::string,
605                                        std::variant<std::vector<Association>>>
606                 values;
607             message.read(objectName, values);
608             auto prop = values.find(assocDefsProperty);
609             if (prop != values.end())
610             {
611                 std::vector<Association> associations =
612                     std::get<std::vector<Association>>(prop->second);
613 
614                 std::string well_known;
615                 if (!getWellKnown(name_owners, message.get_sender(),
616                                   well_known))
617                 {
618                     return;
619                 }
620                 associationChanged(server, associations, message.get_path(),
621                                    well_known, interface_map, associationMaps);
622             }
623         };
624     sdbusplus::bus::match::match assocChangedMatch(
625         static_cast<sdbusplus::bus::bus&>(*system_bus),
626         sdbusplus::bus::match::rules::interface(
627             "org.freedesktop.DBus.Properties") +
628             sdbusplus::bus::match::rules::member("PropertiesChanged") +
629             sdbusplus::bus::match::rules::argN(0, assocDefsInterface),
630         associationChangedHandler);
631 
632     std::shared_ptr<sdbusplus::asio::dbus_interface> iface =
633         server.add_interface(MAPPER_PATH, MAPPER_INTERFACE);
634 
635     iface->register_method(
636         "GetAncestors", [&interface_map](std::string& req_path,
637                                          std::vector<std::string>& interfaces) {
638             // Interfaces need to be sorted for intersect to function
639             std::sort(interfaces.begin(), interfaces.end());
640 
641             if (boost::ends_with(req_path, "/"))
642             {
643                 req_path.pop_back();
644             }
645             if (req_path.size() &&
646                 interface_map.find(req_path) == interface_map.end())
647             {
648                 throw NotFoundException();
649             }
650 
651             std::vector<interface_map_type::value_type> ret;
652             for (auto& object_path : interface_map)
653             {
654                 auto& this_path = object_path.first;
655                 if (boost::starts_with(req_path, this_path) &&
656                     (req_path != this_path))
657                 {
658                     if (interfaces.empty())
659                     {
660                         ret.emplace_back(object_path);
661                     }
662                     else
663                     {
664                         for (auto& interface_map : object_path.second)
665                         {
666 
667                             if (intersect(interfaces.begin(), interfaces.end(),
668                                           interface_map.second.begin(),
669                                           interface_map.second.end()))
670                             {
671                                 addObjectMapResult(ret, this_path,
672                                                    interface_map);
673                             }
674                         }
675                     }
676                 }
677             }
678 
679             return ret;
680         });
681 
682     iface->register_method(
683         "GetObject", [&interface_map](const std::string& path,
684                                       std::vector<std::string>& interfaces) {
685             boost::container::flat_map<std::string,
686                                        boost::container::flat_set<std::string>>
687                 results;
688 
689             // Interfaces need to be sorted for intersect to function
690             std::sort(interfaces.begin(), interfaces.end());
691             auto path_ref = interface_map.find(path);
692             if (path_ref == interface_map.end())
693             {
694                 throw NotFoundException();
695             }
696             if (interfaces.empty())
697             {
698                 return path_ref->second;
699             }
700             for (auto& interface_map : path_ref->second)
701             {
702                 if (intersect(interfaces.begin(), interfaces.end(),
703                               interface_map.second.begin(),
704                               interface_map.second.end()))
705                 {
706                     results.emplace(interface_map.first, interface_map.second);
707                 }
708             }
709 
710             if (results.empty())
711             {
712                 throw NotFoundException();
713             }
714 
715             return results;
716         });
717 
718     iface->register_method(
719         "GetSubTree", [&interface_map](std::string& req_path, int32_t depth,
720                                        std::vector<std::string>& interfaces) {
721             if (depth <= 0)
722             {
723                 depth = std::numeric_limits<int32_t>::max();
724             }
725             // Interfaces need to be sorted for intersect to function
726             std::sort(interfaces.begin(), interfaces.end());
727             std::vector<interface_map_type::value_type> ret;
728 
729             if (boost::ends_with(req_path, "/"))
730             {
731                 req_path.pop_back();
732             }
733             if (req_path.size() &&
734                 interface_map.find(req_path) == interface_map.end())
735             {
736                 throw NotFoundException();
737             }
738 
739             for (auto& object_path : interface_map)
740             {
741                 auto& this_path = object_path.first;
742 
743                 if (this_path == req_path)
744                 {
745                     continue;
746                 }
747 
748                 if (boost::starts_with(this_path, req_path))
749                 {
750                     // count the number of slashes past the search term
751                     int32_t this_depth =
752                         std::count(this_path.begin() + req_path.size(),
753                                    this_path.end(), '/');
754                     if (this_depth <= depth)
755                     {
756                         for (auto& interface_map : object_path.second)
757                         {
758                             if (intersect(interfaces.begin(), interfaces.end(),
759                                           interface_map.second.begin(),
760                                           interface_map.second.end()) ||
761                                 interfaces.empty())
762                             {
763                                 addObjectMapResult(ret, this_path,
764                                                    interface_map);
765                             }
766                         }
767                     }
768                 }
769             }
770 
771             return ret;
772         });
773 
774     iface->register_method(
775         "GetSubTreePaths",
776         [&interface_map](std::string& req_path, int32_t depth,
777                          std::vector<std::string>& interfaces) {
778             if (depth <= 0)
779             {
780                 depth = std::numeric_limits<int32_t>::max();
781             }
782             // Interfaces need to be sorted for intersect to function
783             std::sort(interfaces.begin(), interfaces.end());
784             std::vector<std::string> ret;
785 
786             if (boost::ends_with(req_path, "/"))
787             {
788                 req_path.pop_back();
789             }
790             if (req_path.size() &&
791                 interface_map.find(req_path) == interface_map.end())
792             {
793                 throw NotFoundException();
794             }
795 
796             for (auto& object_path : interface_map)
797             {
798                 auto& this_path = object_path.first;
799 
800                 if (this_path == req_path)
801                 {
802                     continue;
803                 }
804 
805                 if (boost::starts_with(this_path, req_path))
806                 {
807                     // count the number of slashes past the search term
808                     int this_depth =
809                         std::count(this_path.begin() + req_path.size(),
810                                    this_path.end(), '/');
811                     if (this_depth <= depth)
812                     {
813                         bool add = interfaces.empty();
814                         for (auto& interface_map : object_path.second)
815                         {
816                             if (intersect(interfaces.begin(), interfaces.end(),
817                                           interface_map.second.begin(),
818                                           interface_map.second.end()))
819                             {
820                                 add = true;
821                                 break;
822                             }
823                         }
824                         if (add)
825                         {
826                             // TODO(ed) this is a copy
827                             ret.emplace_back(this_path);
828                         }
829                     }
830                 }
831             }
832 
833             return ret;
834         });
835 
836     iface->initialize();
837 
838     io.post([&]() {
839         doListNames(io, interface_map, system_bus.get(), name_owners,
840                     associationMaps, server);
841     });
842 
843     system_bus->request_name(MAPPER_BUSNAME);
844 
845     io.run();
846 }
847