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