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