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