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 using AssociationInterfaces = boost::container::flat_map< 40 std::string, 41 std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, Endpoints>>; 42 43 AssociationInterfaces 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 WhiteBlackList service_whitelist; 69 static WhiteBlackList 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 AssociationOwnersType& assocOwners, 371 AssociationInterfaces& assocInterfaces) 372 { 373 // Use associationOwners to find the association paths and endpoints 374 // that the passed in object path and service own. Remove all of 375 // these endpoints from the actual association D-Bus objects, and if 376 // the endpoints property is then empty, the whole association object 377 // can be removed. Note there can be multiple services that own an 378 // association, and also that sourcePath is the path of the object 379 // that contains the org.openbmc.Associations interface and not the 380 // association path itself. 381 382 // Find the services that have associations for this object path 383 auto owners = assocOwners.find(sourcePath); 384 if (owners == assocOwners.end()) 385 { 386 return; 387 } 388 389 // Find the association paths and endpoints owned by this object 390 // path for this service. 391 auto assocs = owners->second.find(owner); 392 if (assocs == owners->second.end()) 393 { 394 return; 395 } 396 397 for (const auto& [assocPath, endpointsToRemove] : assocs->second) 398 { 399 // Get the association D-Bus object for this assocPath 400 auto target = assocInterfaces.find(assocPath); 401 if (target == assocInterfaces.end()) 402 { 403 continue; 404 } 405 406 // Remove the entries in the endpoints D-Bus property for this 407 // path/owner/association-path. 408 auto& existingEndpoints = std::get<endpointsPos>(target->second); 409 for (const auto& endpointToRemove : endpointsToRemove) 410 { 411 auto e = std::find(existingEndpoints.begin(), 412 existingEndpoints.end(), endpointToRemove); 413 414 if (e != existingEndpoints.end()) 415 { 416 existingEndpoints.erase(e); 417 } 418 } 419 420 // Remove the association from D-Bus if there are no more endpoints, 421 // otherwise just update the endpoints property. 422 if (existingEndpoints.empty()) 423 { 424 server.remove_interface(std::get<ifacePos>(target->second)); 425 std::get<ifacePos>(target->second) = nullptr; 426 std::get<endpointsPos>(target->second).clear(); 427 } 428 else 429 { 430 std::get<ifacePos>(target->second) 431 ->set_property("endpoints", existingEndpoints); 432 } 433 } 434 435 // Remove the associationOwners entries for this owning path/service. 436 owners->second.erase(assocs); 437 if (owners->second.empty()) 438 { 439 associationOwners.erase(owners); 440 } 441 } 442 443 void do_associations(sdbusplus::asio::connection* system_bus, 444 sdbusplus::asio::object_server& objectServer, 445 const std::string& processName, const std::string& path) 446 { 447 system_bus->async_method_call( 448 [&objectServer, path, processName]( 449 const boost::system::error_code ec, 450 const sdbusplus::message::variant<std::vector<Association>>& 451 variantAssociations) { 452 if (ec) 453 { 454 std::cerr << "Error getting associations from " << path << "\n"; 455 } 456 std::vector<Association> associations = 457 sdbusplus::message::variant_ns::get<std::vector<Association>>( 458 variantAssociations); 459 associationChanged(objectServer, associations, path, processName); 460 }, 461 processName, path, "org.freedesktop.DBus.Properties", "Get", 462 ASSOCIATIONS_INTERFACE, "associations"); 463 } 464 465 void do_introspect(sdbusplus::asio::connection* system_bus, 466 std::shared_ptr<InProgressIntrospect> transaction, 467 interface_map_type& interface_map, 468 sdbusplus::asio::object_server& objectServer, 469 std::string path) 470 { 471 system_bus->async_method_call( 472 [&interface_map, &objectServer, transaction, path, 473 system_bus](const boost::system::error_code ec, 474 const std::string& introspect_xml) { 475 if (ec) 476 { 477 std::cerr << "Introspect call failed with error: " << ec << ", " 478 << ec.message() 479 << " on process: " << transaction->process_name 480 << " path: " << path << "\n"; 481 return; 482 } 483 484 tinyxml2::XMLDocument doc; 485 486 tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str()); 487 if (e != tinyxml2::XMLError::XML_SUCCESS) 488 { 489 std::cerr << "XML parsing failed\n"; 490 return; 491 } 492 493 tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); 494 if (pRoot == nullptr) 495 { 496 std::cerr << "XML document did not contain any data\n"; 497 return; 498 } 499 auto& thisPathMap = interface_map[path]; 500 tinyxml2::XMLElement* pElement = 501 pRoot->FirstChildElement("interface"); 502 while (pElement != nullptr) 503 { 504 const char* iface_name = pElement->Attribute("name"); 505 if (iface_name == nullptr) 506 { 507 continue; 508 } 509 510 std::string iface{iface_name}; 511 512 thisPathMap[transaction->process_name].emplace(iface_name); 513 514 if (std::strcmp(iface_name, ASSOCIATIONS_INTERFACE) == 0) 515 { 516 do_associations(system_bus, objectServer, 517 transaction->process_name, path); 518 } 519 520 pElement = pElement->NextSiblingElement("interface"); 521 } 522 523 pElement = pRoot->FirstChildElement("node"); 524 while (pElement != nullptr) 525 { 526 const char* child_path = pElement->Attribute("name"); 527 if (child_path != nullptr) 528 { 529 std::string parent_path(path); 530 if (parent_path == "/") 531 { 532 parent_path.clear(); 533 } 534 535 do_introspect(system_bus, transaction, interface_map, 536 objectServer, parent_path + "/" + child_path); 537 } 538 pElement = pElement->NextSiblingElement("node"); 539 } 540 }, 541 transaction->process_name, path, "org.freedesktop.DBus.Introspectable", 542 "Introspect"); 543 } 544 545 void start_new_introspect( 546 sdbusplus::asio::connection* system_bus, boost::asio::io_service& io, 547 interface_map_type& interface_map, const std::string& process_name, 548 #ifdef DEBUG 549 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 550 global_start_time, 551 #endif 552 sdbusplus::asio::object_server& objectServer) 553 { 554 if (needToIntrospect(process_name, service_whitelist, service_blacklist)) 555 { 556 std::shared_ptr<InProgressIntrospect> transaction = 557 std::make_shared<InProgressIntrospect>(system_bus, io, process_name 558 #ifdef DEBUG 559 , 560 global_start_time 561 #endif 562 ); 563 564 do_introspect(system_bus, transaction, interface_map, objectServer, 565 "/"); 566 } 567 } 568 569 // TODO(ed) replace with std::set_intersection once c++17 is available 570 template <class InputIt1, class InputIt2> 571 bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) 572 { 573 while (first1 != last1 && first2 != last2) 574 { 575 if (*first1 < *first2) 576 { 577 ++first1; 578 continue; 579 } 580 if (*first2 < *first1) 581 { 582 ++first2; 583 continue; 584 } 585 return true; 586 } 587 return false; 588 } 589 590 void doListNames( 591 boost::asio::io_service& io, interface_map_type& interface_map, 592 sdbusplus::asio::connection* system_bus, 593 boost::container::flat_map<std::string, std::string>& name_owners, 594 sdbusplus::asio::object_server& objectServer) 595 { 596 system_bus->async_method_call( 597 [&io, &interface_map, &name_owners, &objectServer, 598 system_bus](const boost::system::error_code ec, 599 std::vector<std::string> process_names) { 600 if (ec) 601 { 602 std::cerr << "Error getting names: " << ec << "\n"; 603 std::exit(EXIT_FAILURE); 604 return; 605 } 606 // Try to make startup consistent 607 std::sort(process_names.begin(), process_names.end()); 608 #ifdef DEBUG 609 std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> 610 global_start_time = std::make_shared< 611 std::chrono::time_point<std::chrono::steady_clock>>( 612 std::chrono::steady_clock::now()); 613 #endif 614 for (const std::string& process_name : process_names) 615 { 616 if (needToIntrospect(process_name, service_whitelist, 617 service_blacklist)) 618 { 619 start_new_introspect(system_bus, io, interface_map, 620 process_name, 621 #ifdef DEBUG 622 global_start_time, 623 #endif 624 objectServer); 625 update_owners(system_bus, name_owners, process_name); 626 } 627 } 628 }, 629 "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", 630 "ListNames"); 631 } 632 633 void splitArgs(const std::string& stringArgs, 634 boost::container::flat_set<std::string>& listArgs) 635 { 636 std::istringstream args; 637 std::string arg; 638 639 args.str(stringArgs); 640 641 while (!args.eof()) 642 { 643 args >> arg; 644 if (!arg.empty()) 645 { 646 listArgs.insert(arg); 647 } 648 } 649 } 650 651 void addObjectMapResult( 652 std::vector<interface_map_type::value_type>& objectMap, 653 const std::string& objectPath, 654 const std::pair<std::string, boost::container::flat_set<std::string>>& 655 interfaceMap) 656 { 657 // Adds an object path/service name/interface list entry to 658 // the results of GetSubTree and GetAncestors. 659 // If an entry for the object path already exists, just add the 660 // service name and interfaces to that entry, otherwise create 661 // a new entry. 662 auto entry = std::find_if( 663 objectMap.begin(), objectMap.end(), 664 [&objectPath](const auto& i) { return objectPath == i.first; }); 665 666 if (entry != objectMap.end()) 667 { 668 entry->second.emplace(interfaceMap); 669 } 670 else 671 { 672 interface_map_type::value_type object; 673 object.first = objectPath; 674 object.second.emplace(interfaceMap); 675 objectMap.push_back(object); 676 } 677 } 678 679 // Remove parents of the passed in path that: 680 // 1) Only have the 3 default interfaces on them 681 // - Means D-Bus created these, not application code, 682 // with the Properties, Introspectable, and Peer ifaces 683 // 2) Have no other child for this owner 684 void removeUnneededParents(const std::string& objectPath, 685 const std::string& owner, 686 interface_map_type& interface_map) 687 { 688 auto parent = objectPath; 689 690 while (true) 691 { 692 auto pos = parent.find_last_of('/'); 693 if ((pos == std::string::npos) || (pos == 0)) 694 { 695 break; 696 } 697 parent = parent.substr(0, pos); 698 699 auto parent_it = interface_map.find(parent); 700 if (parent_it == interface_map.end()) 701 { 702 break; 703 } 704 705 auto ifaces_it = parent_it->second.find(owner); 706 if (ifaces_it == parent_it->second.end()) 707 { 708 break; 709 } 710 711 if (ifaces_it->second.size() != 3) 712 { 713 break; 714 } 715 716 auto child_path = parent + '/'; 717 718 // Remove this parent if there isn't a remaining child on this owner 719 auto child = std::find_if( 720 interface_map.begin(), interface_map.end(), 721 [&owner, &child_path](const auto& entry) { 722 return boost::starts_with(entry.first, child_path) && 723 (entry.second.find(owner) != entry.second.end()); 724 }); 725 726 if (child == interface_map.end()) 727 { 728 parent_it->second.erase(ifaces_it); 729 if (parent_it->second.empty()) 730 { 731 interface_map.erase(parent_it); 732 } 733 } 734 else 735 { 736 break; 737 } 738 } 739 } 740 741 int main(int argc, char** argv) 742 { 743 auto options = ArgumentParser(argc, argv); 744 boost::asio::io_service io; 745 std::shared_ptr<sdbusplus::asio::connection> system_bus = 746 std::make_shared<sdbusplus::asio::connection>(io); 747 748 splitArgs(options["service-namespaces"], service_whitelist); 749 splitArgs(options["service-blacklists"], service_blacklist); 750 751 // TODO(Ed) Remove this once all service files are updated to not use this. 752 // For now, simply squash the input, and ignore it. 753 boost::container::flat_set<std::string> iface_whitelist; 754 splitArgs(options["interface-namespaces"], iface_whitelist); 755 756 system_bus->request_name(OBJECT_MAPPER_DBUS_NAME); 757 sdbusplus::asio::object_server server(system_bus); 758 759 // Construct a signal set registered for process termination. 760 boost::asio::signal_set signals(io, SIGINT, SIGTERM); 761 signals.async_wait([&io](const boost::system::error_code& error, 762 int signal_number) { io.stop(); }); 763 764 interface_map_type interface_map; 765 boost::container::flat_map<std::string, std::string> name_owners; 766 767 std::function<void(sdbusplus::message::message & message)> 768 nameChangeHandler = [&interface_map, &io, &name_owners, &server, 769 system_bus](sdbusplus::message::message& message) { 770 std::string name; 771 std::string old_owner; 772 std::string new_owner; 773 774 message.read(name, old_owner, new_owner); 775 776 if (!old_owner.empty()) 777 { 778 if (boost::starts_with(old_owner, ":")) 779 { 780 auto it = name_owners.find(old_owner); 781 if (it != name_owners.end()) 782 { 783 name_owners.erase(it); 784 } 785 } 786 // Connection removed 787 interface_map_type::iterator path_it = interface_map.begin(); 788 while (path_it != interface_map.end()) 789 { 790 // If an associations interface is being removed, 791 // also need to remove the corresponding associations 792 // objects and properties. 793 auto ifaces = path_it->second.find(name); 794 if (ifaces != path_it->second.end()) 795 { 796 auto assoc = std::find(ifaces->second.begin(), 797 ifaces->second.end(), 798 ASSOCIATIONS_INTERFACE); 799 800 if (assoc != ifaces->second.end()) 801 { 802 removeAssociation(path_it->first, name, server, 803 associationOwners, 804 associationInterfaces); 805 } 806 } 807 808 path_it->second.erase(name); 809 if (path_it->second.empty()) 810 { 811 // If the last connection to the object is gone, 812 // delete the top level object 813 path_it = interface_map.erase(path_it); 814 continue; 815 } 816 path_it++; 817 } 818 } 819 820 if (!new_owner.empty()) 821 { 822 #ifdef DEBUG 823 auto transaction = std::make_shared< 824 std::chrono::time_point<std::chrono::steady_clock>>( 825 std::chrono::steady_clock::now()); 826 #endif 827 // New daemon added 828 if (needToIntrospect(name, service_whitelist, 829 service_blacklist)) 830 { 831 name_owners[new_owner] = name; 832 start_new_introspect(system_bus.get(), io, interface_map, 833 name, 834 #ifdef DEBUG 835 transaction, 836 #endif 837 server); 838 } 839 } 840 }; 841 842 sdbusplus::bus::match::match nameOwnerChanged( 843 static_cast<sdbusplus::bus::bus&>(*system_bus), 844 sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler); 845 846 std::function<void(sdbusplus::message::message & message)> 847 interfacesAddedHandler = [&interface_map, &name_owners, &server]( 848 sdbusplus::message::message& message) { 849 sdbusplus::message::object_path obj_path; 850 std::vector<std::pair< 851 std::string, std::vector<std::pair< 852 std::string, sdbusplus::message::variant< 853 std::vector<Association>>>>>> 854 interfaces_added; 855 message.read(obj_path, interfaces_added); 856 std::string well_known; 857 if (!getWellKnown(name_owners, message.get_sender(), well_known)) 858 { 859 return; // only introspect well-known 860 } 861 if (needToIntrospect(well_known, service_whitelist, 862 service_blacklist)) 863 { 864 auto& iface_list = interface_map[obj_path.str]; 865 866 for (const auto& interface_pair : interfaces_added) 867 { 868 iface_list[well_known].emplace(interface_pair.first); 869 870 if (interface_pair.first == ASSOCIATIONS_INTERFACE) 871 { 872 const sdbusplus::message::variant< 873 std::vector<Association>>* variantAssociations = 874 nullptr; 875 for (const auto& interface : interface_pair.second) 876 { 877 if (interface.first == "associations") 878 { 879 variantAssociations = &(interface.second); 880 } 881 } 882 if (variantAssociations == nullptr) 883 { 884 std::cerr << "Illegal association found on " 885 << well_known << "\n"; 886 continue; 887 } 888 std::vector<Association> associations = 889 sdbusplus::message::variant_ns::get< 890 std::vector<Association>>(*variantAssociations); 891 associationChanged(server, associations, obj_path.str, 892 well_known); 893 } 894 } 895 896 // To handle the case where an object path is being created 897 // with 2 or more new path segments, check if the parent paths 898 // of this path are already in the interface map, and add them 899 // if they aren't with just the default freedesktop interfaces. 900 // This would be done via introspection if they would have 901 // already existed at startup. While we could also introspect 902 // them now to do the work, we know there aren't any other 903 // interfaces or we would have gotten signals for them as well, 904 // so take a shortcut to speed things up. 905 // 906 // This is all needed so that mapper operations can be done 907 // on the new parent paths. 908 using iface_map_iterator = interface_map_type::iterator; 909 using iface_map_value_type = boost::container::flat_map< 910 std::string, boost::container::flat_set<std::string>>; 911 using name_map_iterator = iface_map_value_type::iterator; 912 913 static const boost::container::flat_set<std::string> 914 default_ifaces{"org.freedesktop.DBus.Introspectable", 915 "org.freedesktop.DBus.Peer", 916 "org.freedesktop.DBus.Properties"}; 917 918 std::string parent = obj_path.str; 919 auto pos = parent.find_last_of('/'); 920 921 while (pos != std::string::npos) 922 { 923 parent = parent.substr(0, pos); 924 925 std::pair<iface_map_iterator, bool> parentEntry = 926 interface_map.insert( 927 std::make_pair(parent, iface_map_value_type{})); 928 929 std::pair<name_map_iterator, bool> ifaceEntry = 930 parentEntry.first->second.insert( 931 std::make_pair(well_known, default_ifaces)); 932 933 if (!ifaceEntry.second) 934 { 935 // Entry was already there for this name so done. 936 break; 937 } 938 939 pos = parent.find_last_of('/'); 940 } 941 } 942 }; 943 944 sdbusplus::bus::match::match interfacesAdded( 945 static_cast<sdbusplus::bus::bus&>(*system_bus), 946 sdbusplus::bus::match::rules::interfacesAdded(), 947 interfacesAddedHandler); 948 949 std::function<void(sdbusplus::message::message & message)> 950 interfacesRemovedHandler = [&interface_map, &name_owners, &server]( 951 sdbusplus::message::message& message) { 952 sdbusplus::message::object_path obj_path; 953 std::vector<std::string> interfaces_removed; 954 message.read(obj_path, interfaces_removed); 955 auto connection_map = interface_map.find(obj_path.str); 956 if (connection_map == interface_map.end()) 957 { 958 return; 959 } 960 961 std::string sender; 962 if (!getWellKnown(name_owners, message.get_sender(), sender)) 963 { 964 return; 965 } 966 for (const std::string& interface : interfaces_removed) 967 { 968 auto interface_set = connection_map->second.find(sender); 969 if (interface_set == connection_map->second.end()) 970 { 971 continue; 972 } 973 974 if (interface == ASSOCIATIONS_INTERFACE) 975 { 976 removeAssociation(obj_path.str, sender, server, 977 associationOwners, associationInterfaces); 978 } 979 980 interface_set->second.erase(interface); 981 // If this was the last interface on this connection, 982 // erase the connection 983 if (interface_set->second.empty()) 984 { 985 connection_map->second.erase(interface_set); 986 } 987 } 988 // If this was the last connection on this object path, 989 // erase the object path 990 if (connection_map->second.empty()) 991 { 992 interface_map.erase(connection_map); 993 } 994 995 removeUnneededParents(obj_path.str, sender, interface_map); 996 }; 997 998 sdbusplus::bus::match::match interfacesRemoved( 999 static_cast<sdbusplus::bus::bus&>(*system_bus), 1000 sdbusplus::bus::match::rules::interfacesRemoved(), 1001 interfacesRemovedHandler); 1002 1003 std::function<void(sdbusplus::message::message & message)> 1004 associationChangedHandler = 1005 [&server, &name_owners](sdbusplus::message::message& message) { 1006 std::string objectName; 1007 boost::container::flat_map< 1008 std::string, 1009 sdbusplus::message::variant<std::vector<Association>>> 1010 values; 1011 message.read(objectName, values); 1012 auto findAssociations = values.find("associations"); 1013 if (findAssociations != values.end()) 1014 { 1015 std::vector<Association> associations = 1016 sdbusplus::message::variant_ns::get< 1017 std::vector<Association>>(findAssociations->second); 1018 1019 std::string well_known; 1020 if (!getWellKnown(name_owners, message.get_sender(), 1021 well_known)) 1022 { 1023 return; 1024 } 1025 associationChanged(server, associations, message.get_path(), 1026 well_known); 1027 } 1028 }; 1029 sdbusplus::bus::match::match associationChanged( 1030 static_cast<sdbusplus::bus::bus&>(*system_bus), 1031 sdbusplus::bus::match::rules::interface( 1032 "org.freedesktop.DBus.Properties") + 1033 sdbusplus::bus::match::rules::member("PropertiesChanged") + 1034 sdbusplus::bus::match::rules::argN(0, ASSOCIATIONS_INTERFACE), 1035 associationChangedHandler); 1036 1037 std::shared_ptr<sdbusplus::asio::dbus_interface> iface = 1038 server.add_interface("/xyz/openbmc_project/object_mapper", 1039 "xyz.openbmc_project.ObjectMapper"); 1040 1041 iface->register_method( 1042 "GetAncestors", [&interface_map](std::string& req_path, 1043 std::vector<std::string>& interfaces) { 1044 // Interfaces need to be sorted for intersect to function 1045 std::sort(interfaces.begin(), interfaces.end()); 1046 1047 if (boost::ends_with(req_path, "/")) 1048 { 1049 req_path.pop_back(); 1050 } 1051 if (req_path.size() && 1052 interface_map.find(req_path) == interface_map.end()) 1053 { 1054 throw NotFoundException(); 1055 } 1056 1057 std::vector<interface_map_type::value_type> ret; 1058 for (auto& object_path : interface_map) 1059 { 1060 auto& this_path = object_path.first; 1061 if (boost::starts_with(req_path, this_path) && 1062 (req_path != this_path)) 1063 { 1064 if (interfaces.empty()) 1065 { 1066 ret.emplace_back(object_path); 1067 } 1068 else 1069 { 1070 for (auto& interface_map : object_path.second) 1071 { 1072 1073 if (intersect(interfaces.begin(), interfaces.end(), 1074 interface_map.second.begin(), 1075 interface_map.second.end())) 1076 { 1077 addObjectMapResult(ret, this_path, 1078 interface_map); 1079 } 1080 } 1081 } 1082 } 1083 } 1084 1085 return ret; 1086 }); 1087 1088 iface->register_method( 1089 "GetObject", [&interface_map](const std::string& path, 1090 std::vector<std::string>& interfaces) { 1091 boost::container::flat_map<std::string, 1092 boost::container::flat_set<std::string>> 1093 results; 1094 1095 // Interfaces need to be sorted for intersect to function 1096 std::sort(interfaces.begin(), interfaces.end()); 1097 auto path_ref = interface_map.find(path); 1098 if (path_ref == interface_map.end()) 1099 { 1100 throw NotFoundException(); 1101 } 1102 if (interfaces.empty()) 1103 { 1104 return path_ref->second; 1105 } 1106 for (auto& interface_map : path_ref->second) 1107 { 1108 if (intersect(interfaces.begin(), interfaces.end(), 1109 interface_map.second.begin(), 1110 interface_map.second.end())) 1111 { 1112 results.emplace(interface_map.first, interface_map.second); 1113 } 1114 } 1115 1116 if (results.empty()) 1117 { 1118 throw NotFoundException(); 1119 } 1120 1121 return results; 1122 }); 1123 1124 iface->register_method( 1125 "GetSubTree", [&interface_map](std::string& req_path, int32_t depth, 1126 std::vector<std::string>& interfaces) { 1127 if (depth <= 0) 1128 { 1129 depth = std::numeric_limits<int32_t>::max(); 1130 } 1131 // Interfaces need to be sorted for intersect to function 1132 std::sort(interfaces.begin(), interfaces.end()); 1133 std::vector<interface_map_type::value_type> ret; 1134 1135 if (boost::ends_with(req_path, "/")) 1136 { 1137 req_path.pop_back(); 1138 } 1139 if (req_path.size() && 1140 interface_map.find(req_path) == interface_map.end()) 1141 { 1142 throw NotFoundException(); 1143 } 1144 1145 for (auto& object_path : interface_map) 1146 { 1147 auto& this_path = object_path.first; 1148 1149 if (this_path == req_path) 1150 { 1151 continue; 1152 } 1153 1154 if (boost::starts_with(this_path, req_path)) 1155 { 1156 // count the number of slashes past the search term 1157 int32_t this_depth = 1158 std::count(this_path.begin() + req_path.size(), 1159 this_path.end(), '/'); 1160 if (this_depth <= depth) 1161 { 1162 for (auto& interface_map : object_path.second) 1163 { 1164 if (intersect(interfaces.begin(), interfaces.end(), 1165 interface_map.second.begin(), 1166 interface_map.second.end()) || 1167 interfaces.empty()) 1168 { 1169 addObjectMapResult(ret, this_path, 1170 interface_map); 1171 } 1172 } 1173 } 1174 } 1175 } 1176 1177 return ret; 1178 }); 1179 1180 iface->register_method( 1181 "GetSubTreePaths", 1182 [&interface_map](std::string& req_path, int32_t depth, 1183 std::vector<std::string>& interfaces) { 1184 if (depth <= 0) 1185 { 1186 depth = std::numeric_limits<int32_t>::max(); 1187 } 1188 // Interfaces need to be sorted for intersect to function 1189 std::sort(interfaces.begin(), interfaces.end()); 1190 std::vector<std::string> ret; 1191 1192 if (boost::ends_with(req_path, "/")) 1193 { 1194 req_path.pop_back(); 1195 } 1196 if (req_path.size() && 1197 interface_map.find(req_path) == interface_map.end()) 1198 { 1199 throw NotFoundException(); 1200 } 1201 1202 for (auto& object_path : interface_map) 1203 { 1204 auto& this_path = object_path.first; 1205 1206 if (this_path == req_path) 1207 { 1208 continue; 1209 } 1210 1211 if (boost::starts_with(this_path, req_path)) 1212 { 1213 // count the number of slashes past the search term 1214 int this_depth = 1215 std::count(this_path.begin() + req_path.size(), 1216 this_path.end(), '/'); 1217 if (this_depth <= depth) 1218 { 1219 bool add = interfaces.empty(); 1220 for (auto& interface_map : object_path.second) 1221 { 1222 if (intersect(interfaces.begin(), interfaces.end(), 1223 interface_map.second.begin(), 1224 interface_map.second.end())) 1225 { 1226 add = true; 1227 break; 1228 } 1229 } 1230 if (add) 1231 { 1232 // TODO(ed) this is a copy 1233 ret.emplace_back(this_path); 1234 } 1235 } 1236 } 1237 } 1238 1239 return ret; 1240 }); 1241 1242 iface->initialize(); 1243 1244 io.post([&]() { 1245 doListNames(io, interface_map, system_bus.get(), name_owners, server); 1246 }); 1247 1248 io.run(); 1249 } 1250