1 #include "src/argument.hpp" 2 3 #include <tinyxml2.h> 4 5 #include <atomic> 6 #include <boost/algorithm/string/predicate.hpp> 7 #include <boost/container/flat_map.hpp> 8 #include <boost/container/flat_set.hpp> 9 #include <chrono> 10 #include <iomanip> 11 #include <iostream> 12 #include <sdbusplus/asio/connection.hpp> 13 #include <sdbusplus/asio/object_server.hpp> 14 15 constexpr const char* OBJECT_MAPPER_DBUS_NAME = 16 "xyz.openbmc_project.ObjectMapper"; 17 constexpr const char* ASSOCIATIONS_INTERFACE = "org.openbmc.Associations"; 18 constexpr const char* XYZ_ASSOCIATION_INTERFACE = 19 "xyz.openbmc_project.Association"; 20 21 // interface_map_type is the underlying datastructure the mapper uses. 22 // The 3 levels of map are 23 // object paths 24 // connection names 25 // interface names 26 using interface_map_type = boost::container::flat_map< 27 std::string, boost::container::flat_map< 28 std::string, boost::container::flat_set<std::string>>>; 29 30 using Association = std::tuple<std::string, std::string, std::string>; 31 32 // Associations and some metadata are stored in associationInterfaces. 33 // The fields are: 34 // * ifacePos - holds the D-Bus interface object 35 // * endpointsPos - holds the endpoints array that shadows the property 36 static constexpr auto ifacePos = 0; 37 static constexpr auto endpointsPos = 1; 38 using Endpoints = std::vector<std::string>; 39 boost::container::flat_map< 40 std::string, 41 std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>> 42 associationInterfaces; 43 44 // The associationOwners map contains information about creators of 45 // associations, so that when a org.openbmc.Association interface is 46 // removed or its 'associations' property is changed, the mapper owned 47 // association objects can be correctly handled. It is a map of the 48 // object path of the org.openbmc.Association owner to a map of the 49 // service the path is owned by, to a map of the association objects to 50 // their endpoint paths: 51 // map[ownerPath : map[service : map[assocPath : [endpoint paths]]] 52 // For example: 53 // [/logging/entry/1 : 54 // [xyz.openbmc_project.Logging : 55 // [/logging/entry/1/callout : [/system/cpu0], 56 // /system/cpu0/fault : [/logging/entry/1]]]] 57 58 using AssociationPaths = 59 boost::container::flat_map<std::string, 60 boost::container::flat_set<std::string>>; 61 62 using AssociationOwnersType = boost::container::flat_map< 63 std::string, boost::container::flat_map<std::string, AssociationPaths>>; 64 65 AssociationOwnersType associationOwners; 66 67 static boost::container::flat_set<std::string> service_whitelist; 68 static boost::container::flat_set<std::string> service_blacklist; 69 70 /** Exception thrown when a path is not found in the object list. */ 71 struct NotFoundException final : public sdbusplus::exception_t 72 { 73 const char* name() const noexcept override 74 { 75 return "org.freedesktop.DBus.Error.FileNotFound"; 76 }; 77 const char* description() const noexcept override 78 { 79 return "path or object not found"; 80 }; 81 const char* what() const noexcept override 82 { 83 return "org.freedesktop.DBus.Error.FileNotFound: " 84 "The requested object was not found"; 85 }; 86 }; 87 88 bool get_well_known( 89 boost::container::flat_map<std::string, std::string>& owners, 90 const std::string& request, std::string& well_known) 91 { 92 // If it's already a well known name, just return 93 if (!boost::starts_with(request, ":")) 94 { 95 well_known = request; 96 return true; 97 } 98 99 auto it = owners.find(request); 100 if (it == owners.end()) 101 { 102 return false; 103 } 104 well_known = it->second; 105 return true; 106 } 107 108 void update_owners(sdbusplus::asio::connection* conn, 109 boost::container::flat_map<std::string, std::string>& owners, 110 const std::string& new_object) 111 { 112 if (boost::starts_with(new_object, ":")) 113 { 114 return; 115 } 116 conn->async_method_call( 117 [&, new_object](const boost::system::error_code ec, 118 const std::string& nameOwner) { 119 if (ec) 120 { 121 std::cerr << "Error getting owner of " << new_object << " : " 122 << ec << "\n"; 123 return; 124 } 125 owners[nameOwner] = new_object; 126 }, 127 "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner", 128 new_object); 129 } 130 131 void send_introspection_complete_signal(sdbusplus::asio::connection* system_bus, 132 const std::string& process_name) 133 { 134 // TODO(ed) This signal doesn't get exposed properly in the 135 // introspect right now. Find out how to register signals in 136 // sdbusplus 137 sdbusplus::message::message m = system_bus->new_signal( 138 "/xyz/openbmc_project/object_mapper", 139 "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete"); 140 m.append(process_name); 141 m.signal_send(); 142 } 143 144 struct InProgressIntrospect 145 { 146 InProgressIntrospect( 147 sdbusplus::asio::connection* system_bus, boost::asio::io_service& io, 148 const std::string& process_name 149 #ifdef DEBUG 150 , 151 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 152 global_start_time 153 #endif 154 ) : 155 system_bus(system_bus), 156 io(io), process_name(process_name) 157 #ifdef DEBUG 158 , 159 global_start_time(global_start_time), 160 process_start_time(std::chrono::steady_clock::now()) 161 #endif 162 { 163 } 164 ~InProgressIntrospect() 165 { 166 send_introspection_complete_signal(system_bus, process_name); 167 168 #ifdef DEBUG 169 std::chrono::duration<float> diff = 170 std::chrono::steady_clock::now() - process_start_time; 171 std::cout << std::setw(50) << process_name << " scan took " 172 << diff.count() << " seconds\n"; 173 174 // If we're the last outstanding caller globally, calculate the 175 // time it took 176 if (global_start_time != nullptr && global_start_time.use_count() == 1) 177 { 178 diff = std::chrono::steady_clock::now() - *global_start_time; 179 std::cout << "Total scan took " << diff.count() 180 << " seconds to complete\n"; 181 } 182 #endif 183 } 184 sdbusplus::asio::connection* system_bus; 185 boost::asio::io_service& io; 186 std::string process_name; 187 #ifdef DEBUG 188 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 189 global_start_time; 190 std::chrono::time_point<std::chrono::steady_clock> process_start_time; 191 #endif 192 }; 193 194 // Called when either a new org.openbmc.Associations interface was 195 // created, or the associations property on that interface changed. 196 void addAssociation(sdbusplus::asio::object_server& objectServer, 197 const std::vector<Association>& associations, 198 const std::string& path, const std::string& owner) 199 { 200 AssociationPaths objects; 201 202 for (const Association& association : associations) 203 { 204 std::string forward; 205 std::string reverse; 206 std::string endpoint; 207 std::tie(forward, reverse, endpoint) = association; 208 209 if (forward.size()) 210 { 211 objects[path + "/" + forward].emplace(endpoint); 212 } 213 if (reverse.size()) 214 { 215 if (endpoint.empty()) 216 { 217 std::cerr << "Found invalid association on path " << path 218 << "\n"; 219 continue; 220 } 221 objects[endpoint + "/" + reverse].emplace(path); 222 } 223 } 224 for (const auto& object : objects) 225 { 226 // the mapper exposes the new association interface but intakes 227 // the old 228 229 auto& iface = associationInterfaces[object.first]; 230 auto& i = std::get<ifacePos>(iface); 231 auto& endpoints = std::get<endpointsPos>(iface); 232 233 // Only add new endpoints 234 for (auto& e : object.second) 235 { 236 if (std::find(endpoints.begin(), endpoints.end(), e) == 237 endpoints.end()) 238 { 239 endpoints.push_back(e); 240 } 241 } 242 243 // If the interface already exists, only need to update 244 // the property value, otherwise create it 245 if (i) 246 { 247 i->set_property("endpoints", endpoints); 248 } 249 else 250 { 251 i = objectServer.add_interface(object.first, 252 XYZ_ASSOCIATION_INTERFACE); 253 i->register_property("endpoints", endpoints); 254 i->initialize(); 255 } 256 } 257 258 // Update associationOwners with the latest info 259 auto a = associationOwners.find(path); 260 if (a != associationOwners.end()) 261 { 262 auto o = a->second.find(owner); 263 if (o != a->second.end()) 264 { 265 o->second = std::move(objects); 266 } 267 else 268 { 269 a->second.emplace(owner, std::move(objects)); 270 } 271 } 272 else 273 { 274 boost::container::flat_map<std::string, AssociationPaths> owners; 275 owners.emplace(owner, std::move(objects)); 276 associationOwners.emplace(path, owners); 277 } 278 } 279 280 void removeAssociation(const std::string& sourcePath, const std::string& owner, 281 sdbusplus::asio::object_server& server) 282 { 283 // Use associationOwners to find the association paths and endpoints 284 // that the passed in object path and service own. Remove all of 285 // these endpoints from the actual association D-Bus objects, and if 286 // the endpoints property is then empty, the whole association object 287 // can be removed. Note there can be multiple services that own an 288 // association, and also that sourcePath is the path of the object 289 // that contains the org.openbmc.Associations interface and not the 290 // association path itself. 291 292 // Find the services that have associations for this object path 293 auto owners = associationOwners.find(sourcePath); 294 if (owners == associationOwners.end()) 295 { 296 return; 297 } 298 299 // Find the association paths and endpoints owned by this object 300 // path for this service. 301 auto assocs = owners->second.find(owner); 302 if (assocs == owners->second.end()) 303 { 304 return; 305 } 306 307 for (const auto& [assocPath, endpointsToRemove] : assocs->second) 308 { 309 // Get the association D-Bus object for this assocPath 310 auto target = associationInterfaces.find(assocPath); 311 if (target == associationInterfaces.end()) 312 { 313 continue; 314 } 315 316 // Remove the entries in the endpoints D-Bus property for this 317 // path/owner/association-path. 318 auto& existingEndpoints = std::get<endpointsPos>(target->second); 319 for (const auto& endpointToRemove : endpointsToRemove) 320 { 321 auto e = std::find(existingEndpoints.begin(), 322 existingEndpoints.end(), endpointToRemove); 323 324 if (e != existingEndpoints.end()) 325 { 326 existingEndpoints.erase(e); 327 } 328 } 329 330 // Remove the association from D-Bus if there are no more endpoints, 331 // otherwise just update the endpoints property. 332 if (existingEndpoints.empty()) 333 { 334 server.remove_interface(std::get<ifacePos>(target->second)); 335 std::get<ifacePos>(target->second) = nullptr; 336 std::get<endpointsPos>(target->second).clear(); 337 } 338 else 339 { 340 std::get<ifacePos>(target->second) 341 ->set_property("endpoints", existingEndpoints); 342 } 343 } 344 345 // Remove the associationOwners entries for this owning path/service. 346 owners->second.erase(assocs); 347 if (owners->second.empty()) 348 { 349 associationOwners.erase(owners); 350 } 351 } 352 353 void do_associations(sdbusplus::asio::connection* system_bus, 354 sdbusplus::asio::object_server& objectServer, 355 const std::string& processName, const std::string& path) 356 { 357 system_bus->async_method_call( 358 [&objectServer, path, processName]( 359 const boost::system::error_code ec, 360 const sdbusplus::message::variant<std::vector<Association>>& 361 variantAssociations) { 362 if (ec) 363 { 364 std::cerr << "Error getting associations from " << path << "\n"; 365 } 366 std::vector<Association> associations = 367 sdbusplus::message::variant_ns::get<std::vector<Association>>( 368 variantAssociations); 369 addAssociation(objectServer, associations, path, processName); 370 }, 371 processName, path, "org.freedesktop.DBus.Properties", "Get", 372 ASSOCIATIONS_INTERFACE, "associations"); 373 } 374 375 void do_introspect(sdbusplus::asio::connection* system_bus, 376 std::shared_ptr<InProgressIntrospect> transaction, 377 interface_map_type& interface_map, 378 sdbusplus::asio::object_server& objectServer, 379 std::string path) 380 { 381 system_bus->async_method_call( 382 [&interface_map, &objectServer, transaction, path, 383 system_bus](const boost::system::error_code ec, 384 const std::string& introspect_xml) { 385 if (ec) 386 { 387 std::cerr << "Introspect call failed with error: " << ec << ", " 388 << ec.message() 389 << " on process: " << transaction->process_name 390 << " path: " << path << "\n"; 391 return; 392 } 393 394 tinyxml2::XMLDocument doc; 395 396 tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str()); 397 if (e != tinyxml2::XMLError::XML_SUCCESS) 398 { 399 std::cerr << "XML parsing failed\n"; 400 return; 401 } 402 403 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); 404 if (pRoot == nullptr) 405 { 406 std::cerr << "XML document did not contain any data\n"; 407 return; 408 } 409 auto& thisPathMap = interface_map[path]; 410 tinyxml2::XMLElement* pElement = 411 pRoot->FirstChildElement("interface"); 412 while (pElement != nullptr) 413 { 414 const char* iface_name = pElement->Attribute("name"); 415 if (iface_name == nullptr) 416 { 417 continue; 418 } 419 420 std::string iface{iface_name}; 421 422 thisPathMap[transaction->process_name].emplace(iface_name); 423 424 if (std::strcmp(iface_name, ASSOCIATIONS_INTERFACE) == 0) 425 { 426 do_associations(system_bus, objectServer, 427 transaction->process_name, path); 428 } 429 430 pElement = pElement->NextSiblingElement("interface"); 431 } 432 433 pElement = pRoot->FirstChildElement("node"); 434 while (pElement != nullptr) 435 { 436 const char* child_path = pElement->Attribute("name"); 437 if (child_path != nullptr) 438 { 439 std::string parent_path(path); 440 if (parent_path == "/") 441 { 442 parent_path.clear(); 443 } 444 445 do_introspect(system_bus, transaction, interface_map, 446 objectServer, parent_path + "/" + child_path); 447 } 448 pElement = pElement->NextSiblingElement("node"); 449 } 450 }, 451 transaction->process_name, path, "org.freedesktop.DBus.Introspectable", 452 "Introspect"); 453 } 454 455 bool need_to_introspect(const std::string& process_name) 456 { 457 auto inWhitelist = 458 std::find_if(service_whitelist.begin(), service_whitelist.end(), 459 [&process_name](const auto& prefix) { 460 return boost::starts_with(process_name, prefix); 461 }) != service_whitelist.end(); 462 463 // This holds full service names, not prefixes 464 auto inBlacklist = 465 service_blacklist.find(process_name) != service_blacklist.end(); 466 467 return inWhitelist && !inBlacklist; 468 } 469 470 void start_new_introspect( 471 sdbusplus::asio::connection* system_bus, boost::asio::io_service& io, 472 interface_map_type& interface_map, const std::string& process_name, 473 #ifdef DEBUG 474 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 475 global_start_time, 476 #endif 477 sdbusplus::asio::object_server& objectServer) 478 { 479 if (need_to_introspect(process_name)) 480 { 481 std::shared_ptr<InProgressIntrospect> transaction = 482 std::make_shared<InProgressIntrospect>(system_bus, io, process_name 483 #ifdef DEBUG 484 , 485 global_start_time 486 #endif 487 ); 488 489 do_introspect(system_bus, transaction, interface_map, objectServer, 490 "/"); 491 } 492 } 493 494 // TODO(ed) replace with std::set_intersection once c++17 is available 495 template <class InputIt1, class InputIt2> 496 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) 497 { 498 while (first1 != last1 && first2 != last2) 499 { 500 if (*first1 < *first2) 501 { 502 ++first1; 503 continue; 504 } 505 if (*first2 < *first1) 506 { 507 ++first2; 508 continue; 509 } 510 return true; 511 } 512 return false; 513 } 514 515 void doListNames( 516 boost::asio::io_service& io, interface_map_type& interface_map, 517 sdbusplus::asio::connection* system_bus, 518 boost::container::flat_map<std::string, std::string>& name_owners, 519 sdbusplus::asio::object_server& objectServer) 520 { 521 system_bus->async_method_call( 522 [&io, &interface_map, &name_owners, &objectServer, 523 system_bus](const boost::system::error_code ec, 524 std::vector<std::string> process_names) { 525 if (ec) 526 { 527 std::cerr << "Error getting names: " << ec << "\n"; 528 std::exit(EXIT_FAILURE); 529 return; 530 } 531 // Try to make startup consistent 532 std::sort(process_names.begin(), process_names.end()); 533 #ifdef DEBUG 534 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 535 global_start_time = std::make_shared< 536 std::chrono::time_point<std::chrono::steady_clock>>( 537 std::chrono::steady_clock::now()); 538 #endif 539 for (const std::string& process_name : process_names) 540 { 541 if (need_to_introspect(process_name)) 542 { 543 start_new_introspect(system_bus, io, interface_map, 544 process_name, 545 #ifdef DEBUG 546 global_start_time, 547 #endif 548 objectServer); 549 update_owners(system_bus, name_owners, process_name); 550 } 551 } 552 }, 553 "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", 554 "ListNames"); 555 } 556 557 void splitArgs(const std::string& stringArgs, 558 boost::container::flat_set<std::string>& listArgs) 559 { 560 std::istringstream args; 561 std::string arg; 562 563 args.str(stringArgs); 564 565 while (!args.eof()) 566 { 567 args >> arg; 568 if (!arg.empty()) 569 { 570 listArgs.insert(arg); 571 } 572 } 573 } 574 575 void addObjectMapResult( 576 std::vector<interface_map_type::value_type>& objectMap, 577 const std::string& objectPath, 578 const std::pair<std::string, boost::container::flat_set<std::string>>& 579 interfaceMap) 580 { 581 // Adds an object path/service name/interface list entry to 582 // the results of GetSubTree and GetAncestors. 583 // If an entry for the object path already exists, just add the 584 // service name and interfaces to that entry, otherwise create 585 // a new entry. 586 auto entry = std::find_if( 587 objectMap.begin(), objectMap.end(), 588 [&objectPath](const auto& i) { return objectPath == i.first; }); 589 590 if (entry != objectMap.end()) 591 { 592 entry->second.emplace(interfaceMap); 593 } 594 else 595 { 596 interface_map_type::value_type object; 597 object.first = objectPath; 598 object.second.emplace(interfaceMap); 599 objectMap.push_back(object); 600 } 601 } 602 603 // Remove parents of the passed in path that: 604 // 1) Only have the 3 default interfaces on them 605 // - Means D-Bus created these, not application code, 606 // with the Properties, Introspectable, and Peer ifaces 607 // 2) Have no other child for this owner 608 void removeUnneededParents(const std::string& objectPath, 609 const std::string& owner, 610 interface_map_type& interface_map) 611 { 612 auto parent = objectPath; 613 614 while (true) 615 { 616 auto pos = parent.find_last_of('/'); 617 if ((pos == std::string::npos) || (pos == 0)) 618 { 619 break; 620 } 621 parent = parent.substr(0, pos); 622 623 auto parent_it = interface_map.find(parent); 624 if (parent_it == interface_map.end()) 625 { 626 break; 627 } 628 629 auto ifaces_it = parent_it->second.find(owner); 630 if (ifaces_it == parent_it->second.end()) 631 { 632 break; 633 } 634 635 if (ifaces_it->second.size() != 3) 636 { 637 break; 638 } 639 640 auto child_path = parent + '/'; 641 642 // Remove this parent if there isn't a remaining child on this owner 643 auto child = std::find_if( 644 interface_map.begin(), interface_map.end(), 645 [&owner, &child_path](const auto& entry) { 646 return boost::starts_with(entry.first, child_path) && 647 (entry.second.find(owner) != entry.second.end()); 648 }); 649 650 if (child == interface_map.end()) 651 { 652 parent_it->second.erase(ifaces_it); 653 if (parent_it->second.empty()) 654 { 655 interface_map.erase(parent_it); 656 } 657 } 658 else 659 { 660 break; 661 } 662 } 663 } 664 665 int main(int argc, char** argv) 666 { 667 auto options = ArgumentParser(argc, argv); 668 boost::asio::io_service io; 669 std::shared_ptr<sdbusplus::asio::connection> system_bus = 670 std::make_shared<sdbusplus::asio::connection>(io); 671 672 splitArgs(options["service-namespaces"], service_whitelist); 673 splitArgs(options["service-blacklists"], service_blacklist); 674 675 // TODO(Ed) Remove this once all service files are updated to not use this. 676 // For now, simply squash the input, and ignore it. 677 boost::container::flat_set<std::string> iface_whitelist; 678 splitArgs(options["interface-namespaces"], iface_whitelist); 679 680 system_bus->request_name(OBJECT_MAPPER_DBUS_NAME); 681 sdbusplus::asio::object_server server(system_bus); 682 683 // Construct a signal set registered for process termination. 684 boost::asio::signal_set signals(io, SIGINT, SIGTERM); 685 signals.async_wait([&io](const boost::system::error_code& error, 686 int signal_number) { io.stop(); }); 687 688 interface_map_type interface_map; 689 boost::container::flat_map<std::string, std::string> name_owners; 690 691 std::function<void(sdbusplus::message::message & message)> 692 nameChangeHandler = [&interface_map, &io, &name_owners, &server, 693 system_bus](sdbusplus::message::message& message) { 694 std::string name; 695 std::string old_owner; 696 std::string new_owner; 697 698 message.read(name, old_owner, new_owner); 699 700 if (!old_owner.empty()) 701 { 702 if (boost::starts_with(old_owner, ":")) 703 { 704 auto it = name_owners.find(old_owner); 705 if (it != name_owners.end()) 706 { 707 name_owners.erase(it); 708 } 709 } 710 // Connection removed 711 interface_map_type::iterator path_it = interface_map.begin(); 712 while (path_it != interface_map.end()) 713 { 714 // If an associations interface is being removed, 715 // also need to remove the corresponding associations 716 // objects and properties. 717 auto ifaces = path_it->second.find(name); 718 if (ifaces != path_it->second.end()) 719 { 720 auto assoc = std::find(ifaces->second.begin(), 721 ifaces->second.end(), 722 ASSOCIATIONS_INTERFACE); 723 724 if (assoc != ifaces->second.end()) 725 { 726 removeAssociation(path_it->first, name, server); 727 } 728 } 729 730 path_it->second.erase(name); 731 if (path_it->second.empty()) 732 { 733 // If the last connection to the object is gone, 734 // delete the top level object 735 path_it = interface_map.erase(path_it); 736 continue; 737 } 738 path_it++; 739 } 740 } 741 742 if (!new_owner.empty()) 743 { 744 #ifdef DEBUG 745 auto transaction = std::make_shared< 746 std::chrono::time_point<std::chrono::steady_clock>>( 747 std::chrono::steady_clock::now()); 748 #endif 749 // New daemon added 750 if (need_to_introspect(name)) 751 { 752 name_owners[new_owner] = name; 753 start_new_introspect(system_bus.get(), io, interface_map, 754 name, 755 #ifdef DEBUG 756 transaction, 757 #endif 758 server); 759 } 760 } 761 }; 762 763 sdbusplus::bus::match::match nameOwnerChanged( 764 static_cast<sdbusplus::bus::bus&>(*system_bus), 765 sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler); 766 767 std::function<void(sdbusplus::message::message & message)> 768 interfacesAddedHandler = [&interface_map, &name_owners, &server]( 769 sdbusplus::message::message& message) { 770 sdbusplus::message::object_path obj_path; 771 std::vector<std::pair< 772 std::string, std::vector<std::pair< 773 std::string, sdbusplus::message::variant< 774 std::vector<Association>>>>>> 775 interfaces_added; 776 message.read(obj_path, interfaces_added); 777 std::string well_known; 778 if (!get_well_known(name_owners, message.get_sender(), well_known)) 779 { 780 return; // only introspect well-known 781 } 782 if (need_to_introspect(well_known)) 783 { 784 auto& iface_list = interface_map[obj_path.str]; 785 786 for (const auto& interface_pair : interfaces_added) 787 { 788 iface_list[well_known].emplace(interface_pair.first); 789 790 if (interface_pair.first == ASSOCIATIONS_INTERFACE) 791 { 792 const sdbusplus::message::variant< 793 std::vector<Association>>* variantAssociations = 794 nullptr; 795 for (const auto& interface : interface_pair.second) 796 { 797 if (interface.first == "associations") 798 { 799 variantAssociations = &(interface.second); 800 } 801 } 802 if (variantAssociations == nullptr) 803 { 804 std::cerr << "Illegal association found on " 805 << well_known << "\n"; 806 continue; 807 } 808 std::vector<Association> associations = 809 sdbusplus::message::variant_ns::get< 810 std::vector<Association>>(*variantAssociations); 811 addAssociation(server, associations, obj_path.str, 812 well_known); 813 } 814 } 815 816 // To handle the case where an object path is being created 817 // with 2 or more new path segments, check if the parent paths 818 // of this path are already in the interface map, and add them 819 // if they aren't with just the default freedesktop interfaces. 820 // This would be done via introspection if they would have 821 // already existed at startup. While we could also introspect 822 // them now to do the work, we know there aren't any other 823 // interfaces or we would have gotten signals for them as well, 824 // so take a shortcut to speed things up. 825 // 826 // This is all needed so that mapper operations can be done 827 // on the new parent paths. 828 using iface_map_iterator = interface_map_type::iterator; 829 using iface_map_value_type = boost::container::flat_map< 830 std::string, boost::container::flat_set<std::string>>; 831 using name_map_iterator = iface_map_value_type::iterator; 832 833 static const boost::container::flat_set<std::string> 834 default_ifaces{"org.freedesktop.DBus.Introspectable", 835 "org.freedesktop.DBus.Peer", 836 "org.freedesktop.DBus.Properties"}; 837 838 std::string parent = obj_path.str; 839 auto pos = parent.find_last_of('/'); 840 841 while (pos != std::string::npos) 842 { 843 parent = parent.substr(0, pos); 844 845 std::pair<iface_map_iterator, bool> parentEntry = 846 interface_map.insert( 847 std::make_pair(parent, iface_map_value_type{})); 848 849 std::pair<name_map_iterator, bool> ifaceEntry = 850 parentEntry.first->second.insert( 851 std::make_pair(well_known, default_ifaces)); 852 853 if (!ifaceEntry.second) 854 { 855 // Entry was already there for this name so done. 856 break; 857 } 858 859 pos = parent.find_last_of('/'); 860 } 861 } 862 }; 863 864 sdbusplus::bus::match::match interfacesAdded( 865 static_cast<sdbusplus::bus::bus&>(*system_bus), 866 sdbusplus::bus::match::rules::interfacesAdded(), 867 interfacesAddedHandler); 868 869 std::function<void(sdbusplus::message::message & message)> 870 interfacesRemovedHandler = [&interface_map, &name_owners, &server]( 871 sdbusplus::message::message& message) { 872 sdbusplus::message::object_path obj_path; 873 std::vector<std::string> interfaces_removed; 874 message.read(obj_path, interfaces_removed); 875 auto connection_map = interface_map.find(obj_path.str); 876 if (connection_map == interface_map.end()) 877 { 878 return; 879 } 880 881 std::string sender; 882 if (!get_well_known(name_owners, message.get_sender(), sender)) 883 { 884 return; 885 } 886 for (const std::string& interface : interfaces_removed) 887 { 888 auto interface_set = connection_map->second.find(sender); 889 if (interface_set == connection_map->second.end()) 890 { 891 continue; 892 } 893 894 if (interface == ASSOCIATIONS_INTERFACE) 895 { 896 removeAssociation(obj_path.str, sender, server); 897 } 898 899 interface_set->second.erase(interface); 900 // If this was the last interface on this connection, 901 // erase the connection 902 if (interface_set->second.empty()) 903 { 904 connection_map->second.erase(interface_set); 905 } 906 } 907 // If this was the last connection on this object path, 908 // erase the object path 909 if (connection_map->second.empty()) 910 { 911 interface_map.erase(connection_map); 912 } 913 914 removeUnneededParents(obj_path.str, sender, interface_map); 915 }; 916 917 sdbusplus::bus::match::match interfacesRemoved( 918 static_cast<sdbusplus::bus::bus&>(*system_bus), 919 sdbusplus::bus::match::rules::interfacesRemoved(), 920 interfacesRemovedHandler); 921 922 std::function<void(sdbusplus::message::message & message)> 923 associationChangedHandler = 924 [&server, &name_owners](sdbusplus::message::message& message) { 925 std::string objectName; 926 boost::container::flat_map< 927 std::string, 928 sdbusplus::message::variant<std::vector<Association>>> 929 values; 930 message.read(objectName, values); 931 auto findAssociations = values.find("associations"); 932 if (findAssociations != values.end()) 933 { 934 std::vector<Association> associations = 935 sdbusplus::message::variant_ns::get< 936 std::vector<Association>>(findAssociations->second); 937 938 std::string well_known; 939 if (!get_well_known(name_owners, message.get_sender(), 940 well_known)) 941 { 942 return; 943 } 944 addAssociation(server, associations, message.get_path(), 945 well_known); 946 } 947 }; 948 sdbusplus::bus::match::match associationChanged( 949 static_cast<sdbusplus::bus::bus&>(*system_bus), 950 sdbusplus::bus::match::rules::interface( 951 "org.freedesktop.DBus.Properties") + 952 sdbusplus::bus::match::rules::member("PropertiesChanged") + 953 sdbusplus::bus::match::rules::argN(0, ASSOCIATIONS_INTERFACE), 954 associationChangedHandler); 955 956 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 957 server.add_interface("/xyz/openbmc_project/object_mapper", 958 "xyz.openbmc_project.ObjectMapper"); 959 960 iface->register_method( 961 "GetAncestors", [&interface_map](std::string& req_path, 962 std::vector<std::string>& interfaces) { 963 // Interfaces need to be sorted for intersect to function 964 std::sort(interfaces.begin(), interfaces.end()); 965 966 if (boost::ends_with(req_path, "/")) 967 { 968 req_path.pop_back(); 969 } 970 if (req_path.size() && 971 interface_map.find(req_path) == interface_map.end()) 972 { 973 throw NotFoundException(); 974 } 975 976 std::vector<interface_map_type::value_type> ret; 977 for (auto& object_path : interface_map) 978 { 979 auto& this_path = object_path.first; 980 if (boost::starts_with(req_path, this_path) && 981 (req_path != this_path)) 982 { 983 if (interfaces.empty()) 984 { 985 ret.emplace_back(object_path); 986 } 987 else 988 { 989 for (auto& interface_map : object_path.second) 990 { 991 992 if (intersect(interfaces.begin(), interfaces.end(), 993 interface_map.second.begin(), 994 interface_map.second.end())) 995 { 996 addObjectMapResult(ret, this_path, 997 interface_map); 998 } 999 } 1000 } 1001 } 1002 } 1003 1004 return ret; 1005 }); 1006 1007 iface->register_method( 1008 "GetObject", [&interface_map](const std::string& path, 1009 std::vector<std::string>& interfaces) { 1010 boost::container::flat_map<std::string, 1011 boost::container::flat_set<std::string>> 1012 results; 1013 1014 // Interfaces need to be sorted for intersect to function 1015 std::sort(interfaces.begin(), interfaces.end()); 1016 auto path_ref = interface_map.find(path); 1017 if (path_ref == interface_map.end()) 1018 { 1019 throw NotFoundException(); 1020 } 1021 if (interfaces.empty()) 1022 { 1023 return path_ref->second; 1024 } 1025 for (auto& interface_map : path_ref->second) 1026 { 1027 if (intersect(interfaces.begin(), interfaces.end(), 1028 interface_map.second.begin(), 1029 interface_map.second.end())) 1030 { 1031 results.emplace(interface_map.first, interface_map.second); 1032 } 1033 } 1034 1035 if (results.empty()) 1036 { 1037 throw NotFoundException(); 1038 } 1039 1040 return results; 1041 }); 1042 1043 iface->register_method( 1044 "GetSubTree", [&interface_map](std::string& req_path, int32_t depth, 1045 std::vector<std::string>& interfaces) { 1046 if (depth <= 0) 1047 { 1048 depth = std::numeric_limits<int32_t>::max(); 1049 } 1050 // Interfaces need to be sorted for intersect to function 1051 std::sort(interfaces.begin(), interfaces.end()); 1052 std::vector<interface_map_type::value_type> ret; 1053 1054 if (boost::ends_with(req_path, "/")) 1055 { 1056 req_path.pop_back(); 1057 } 1058 if (req_path.size() && 1059 interface_map.find(req_path) == interface_map.end()) 1060 { 1061 throw NotFoundException(); 1062 } 1063 1064 for (auto& object_path : interface_map) 1065 { 1066 auto& this_path = object_path.first; 1067 1068 if (this_path == req_path) 1069 { 1070 continue; 1071 } 1072 1073 if (boost::starts_with(this_path, req_path)) 1074 { 1075 // count the number of slashes past the search term 1076 int32_t this_depth = 1077 std::count(this_path.begin() + req_path.size(), 1078 this_path.end(), '/'); 1079 if (this_depth <= depth) 1080 { 1081 for (auto& interface_map : object_path.second) 1082 { 1083 if (intersect(interfaces.begin(), interfaces.end(), 1084 interface_map.second.begin(), 1085 interface_map.second.end()) || 1086 interfaces.empty()) 1087 { 1088 addObjectMapResult(ret, this_path, 1089 interface_map); 1090 } 1091 } 1092 } 1093 } 1094 } 1095 1096 return ret; 1097 }); 1098 1099 iface->register_method( 1100 "GetSubTreePaths", 1101 [&interface_map](std::string& req_path, int32_t depth, 1102 std::vector<std::string>& interfaces) { 1103 if (depth <= 0) 1104 { 1105 depth = std::numeric_limits<int32_t>::max(); 1106 } 1107 // Interfaces need to be sorted for intersect to function 1108 std::sort(interfaces.begin(), interfaces.end()); 1109 std::vector<std::string> ret; 1110 1111 if (boost::ends_with(req_path, "/")) 1112 { 1113 req_path.pop_back(); 1114 } 1115 if (req_path.size() && 1116 interface_map.find(req_path) == interface_map.end()) 1117 { 1118 throw NotFoundException(); 1119 } 1120 1121 for (auto& object_path : interface_map) 1122 { 1123 auto& this_path = object_path.first; 1124 1125 if (this_path == req_path) 1126 { 1127 continue; 1128 } 1129 1130 if (boost::starts_with(this_path, req_path)) 1131 { 1132 // count the number of slashes past the search term 1133 int this_depth = 1134 std::count(this_path.begin() + req_path.size(), 1135 this_path.end(), '/'); 1136 if (this_depth <= depth) 1137 { 1138 bool add = interfaces.empty(); 1139 for (auto& interface_map : object_path.second) 1140 { 1141 if (intersect(interfaces.begin(), interfaces.end(), 1142 interface_map.second.begin(), 1143 interface_map.second.end())) 1144 { 1145 add = true; 1146 break; 1147 } 1148 } 1149 if (add) 1150 { 1151 // TODO(ed) this is a copy 1152 ret.emplace_back(this_path); 1153 } 1154 } 1155 } 1156 } 1157 1158 return ret; 1159 }); 1160 1161 iface->initialize(); 1162 1163 io.post([&]() { 1164 doListNames(io, interface_map, system_bus.get(), name_owners, server); 1165 }); 1166 1167 io.run(); 1168 } 1169