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