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