|  | #include "src/argument.hpp" | 
|  |  | 
|  | #include <tinyxml2.h> | 
|  |  | 
|  | #include <atomic> | 
|  | #include <boost/algorithm/string/predicate.hpp> | 
|  | #include <boost/container/flat_map.hpp> | 
|  | #include <boost/container/flat_set.hpp> | 
|  | #include <chrono> | 
|  | #include <iomanip> | 
|  | #include <iostream> | 
|  | #include <sdbusplus/asio/connection.hpp> | 
|  | #include <sdbusplus/asio/object_server.hpp> | 
|  |  | 
|  | constexpr const char* OBJECT_MAPPER_DBUS_NAME = | 
|  | "xyz.openbmc_project.ObjectMapper"; | 
|  | constexpr const char* ASSOCIATIONS_INTERFACE = "org.openbmc.Associations"; | 
|  | constexpr const char* XYZ_ASSOCIATION_INTERFACE = | 
|  | "xyz.openbmc_project.Association"; | 
|  |  | 
|  | // interface_map_type is the underlying datastructure the mapper uses. | 
|  | // The 3 levels of map are | 
|  | // object paths | 
|  | //   connection names | 
|  | //      interface names | 
|  | using interface_map_type = boost::container::flat_map< | 
|  | std::string, boost::container::flat_map< | 
|  | std::string, boost::container::flat_set<std::string>>>; | 
|  |  | 
|  | using Association = std::tuple<std::string, std::string, std::string>; | 
|  |  | 
|  | //  Associations and some metadata are stored in associationInterfaces. | 
|  | //  The fields are: | 
|  | //   * ifacePos - holds the D-Bus interface object | 
|  | //   * endpointPos - holds the endpoints array that shadows the property | 
|  | //   * sourcePos - holds the owning source path of the association | 
|  | static constexpr auto ifacePos = 0; | 
|  | static constexpr auto endpointsPos = 1; | 
|  | static constexpr auto sourcePos = 2; | 
|  | using Endpoints = std::vector<std::string>; | 
|  | boost::container::flat_map< | 
|  | std::string, std::tuple<std::shared_ptr<sdbusplus::asio::dbus_interface>, | 
|  | Endpoints, std::string>> | 
|  | associationInterfaces; | 
|  |  | 
|  | static boost::container::flat_set<std::string> service_whitelist; | 
|  | static boost::container::flat_set<std::string> service_blacklist; | 
|  | static boost::container::flat_set<std::string> iface_whitelist; | 
|  |  | 
|  | /** Exception thrown when a path is not found in the object list. */ | 
|  | struct NotFoundException final : public sdbusplus::exception_t | 
|  | { | 
|  | const char* name() const noexcept override | 
|  | { | 
|  | return "org.freedesktop.DBus.Error.FileNotFound"; | 
|  | }; | 
|  | const char* description() const noexcept override | 
|  | { | 
|  | return "path or object not found"; | 
|  | }; | 
|  | const char* what() const noexcept override | 
|  | { | 
|  | return "org.freedesktop.DBus.Error.FileNotFound: " | 
|  | "The requested object was not found"; | 
|  | }; | 
|  | }; | 
|  |  | 
|  | bool get_well_known( | 
|  | boost::container::flat_map<std::string, std::string>& owners, | 
|  | const std::string& request, std::string& well_known) | 
|  | { | 
|  | // If it's already a well known name, just return | 
|  | if (!boost::starts_with(request, ":")) | 
|  | { | 
|  | well_known = request; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | auto it = owners.find(request); | 
|  | if (it == owners.end()) | 
|  | { | 
|  | return false; | 
|  | } | 
|  | well_known = it->second; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void update_owners(sdbusplus::asio::connection* conn, | 
|  | boost::container::flat_map<std::string, std::string>& owners, | 
|  | const std::string& new_object) | 
|  | { | 
|  | if (boost::starts_with(new_object, ":")) | 
|  | { | 
|  | return; | 
|  | } | 
|  | conn->async_method_call( | 
|  | [&, new_object](const boost::system::error_code ec, | 
|  | const std::string& nameOwner) { | 
|  | if (ec) | 
|  | { | 
|  | std::cerr << "Error getting owner of " << new_object << " : " | 
|  | << ec << "\n"; | 
|  | return; | 
|  | } | 
|  | owners[nameOwner] = new_object; | 
|  | }, | 
|  | "org.freedesktop.DBus", "/", "org.freedesktop.DBus", "GetNameOwner", | 
|  | new_object); | 
|  | } | 
|  |  | 
|  | void send_introspection_complete_signal(sdbusplus::asio::connection* system_bus, | 
|  | const std::string& process_name) | 
|  | { | 
|  | // TODO(ed) This signal doesn't get exposed properly in the | 
|  | // introspect right now.  Find out how to register signals in | 
|  | // sdbusplus | 
|  | sdbusplus::message::message m = system_bus->new_signal( | 
|  | "/xyz/openbmc_project/object_mapper", | 
|  | "xyz.openbmc_project.ObjectMapper.Private", "IntrospectionComplete"); | 
|  | m.append(process_name); | 
|  | m.signal_send(); | 
|  | } | 
|  |  | 
|  | struct InProgressIntrospect | 
|  | { | 
|  | InProgressIntrospect( | 
|  | sdbusplus::asio::connection* system_bus, boost::asio::io_service& io, | 
|  | const std::string& process_name | 
|  | #ifdef DEBUG | 
|  | , | 
|  | std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> | 
|  | global_start_time | 
|  | #endif | 
|  | ) : | 
|  | system_bus(system_bus), | 
|  | io(io), process_name(process_name) | 
|  | #ifdef DEBUG | 
|  | , | 
|  | global_start_time(global_start_time), | 
|  | process_start_time(std::chrono::steady_clock::now()) | 
|  | #endif | 
|  | { | 
|  | } | 
|  | ~InProgressIntrospect() | 
|  | { | 
|  | send_introspection_complete_signal(system_bus, process_name); | 
|  |  | 
|  | #ifdef DEBUG | 
|  | std::chrono::duration<float> diff = | 
|  | std::chrono::steady_clock::now() - process_start_time; | 
|  | std::cout << std::setw(50) << process_name << " scan took " | 
|  | << diff.count() << " seconds\n"; | 
|  |  | 
|  | // If we're the last outstanding caller globally, calculate the | 
|  | // time it took | 
|  | if (global_start_time != nullptr && global_start_time.use_count() == 1) | 
|  | { | 
|  | diff = std::chrono::steady_clock::now() - *global_start_time; | 
|  | std::cout << "Total scan took " << diff.count() | 
|  | << " seconds to complete\n"; | 
|  | } | 
|  | #endif | 
|  | } | 
|  | sdbusplus::asio::connection* system_bus; | 
|  | boost::asio::io_service& io; | 
|  | std::string process_name; | 
|  | #ifdef DEBUG | 
|  | std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> | 
|  | global_start_time; | 
|  | std::chrono::time_point<std::chrono::steady_clock> process_start_time; | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static const boost::container::flat_set<std::string> ignored_interfaces{ | 
|  | "org.freedesktop.DBus.Introspectable", "org.freedesktop.DBus.Peer", | 
|  | "org.freedesktop.DBus.Properties"}; | 
|  |  | 
|  | void do_getmanagedobjects(sdbusplus::asio::connection* system_bus, | 
|  | std::shared_ptr<InProgressIntrospect> transaction, | 
|  | interface_map_type& interface_map, std::string path) | 
|  | { | 
|  | // note, the variant type doesn't matter, as we don't actually track | 
|  | // property names as of yet.  variant<bool> seemed like the most simple. | 
|  | using ManagedObjectType = std::vector<std::pair< | 
|  | sdbusplus::message::object_path, | 
|  | boost::container::flat_map< | 
|  | std::string, boost::container::flat_map< | 
|  | std::string, sdbusplus::message::variant<bool>>>>>; | 
|  |  | 
|  | system_bus->async_method_call( | 
|  | [&interface_map, system_bus, transaction, | 
|  | path](const boost::system::error_code ec, | 
|  | const ManagedObjectType& objects) { | 
|  | if (ec) | 
|  | { | 
|  | std::cerr << "GetMangedObjects call failed" << ec << "\n"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | interface_map.reserve(interface_map.size() + objects.size()); | 
|  | for (const std::pair< | 
|  | sdbusplus::message::object_path, | 
|  | boost::container::flat_map< | 
|  | std::string, | 
|  | boost::container::flat_map< | 
|  | std::string, sdbusplus::message::variant<bool>>>>& | 
|  | object : objects) | 
|  | { | 
|  | const std::string& path_name = object.first.str; | 
|  | auto& this_path_map = | 
|  | interface_map[path_name][transaction->process_name]; | 
|  | for (auto& interface_it : object.second) | 
|  | { | 
|  | this_path_map.insert(interface_it.first); | 
|  | } | 
|  | } | 
|  | }, | 
|  | transaction->process_name, path, "org.freedesktop.DBus.ObjectManager", | 
|  | "GetManagedObjects"); | 
|  | } | 
|  |  | 
|  | void addAssociation(sdbusplus::asio::object_server& objectServer, | 
|  | const std::vector<Association>& associations, | 
|  | const std::string& path) | 
|  | { | 
|  | boost::container::flat_map<std::string, | 
|  | boost::container::flat_set<std::string>> | 
|  | objects; | 
|  | for (const Association& association : associations) | 
|  | { | 
|  | std::string forward; | 
|  | std::string reverse; | 
|  | std::string endpoint; | 
|  | std::tie(forward, reverse, endpoint) = association; | 
|  |  | 
|  | if (forward.size()) | 
|  | { | 
|  | objects[path + "/" + forward].emplace(endpoint); | 
|  | } | 
|  | if (reverse.size()) | 
|  | { | 
|  | if (endpoint.empty()) | 
|  | { | 
|  | std::cerr << "Found invalid association on path " << path | 
|  | << "\n"; | 
|  | continue; | 
|  | } | 
|  | objects[endpoint + "/" + reverse].emplace(path); | 
|  | } | 
|  | } | 
|  | for (const auto& object : objects) | 
|  | { | 
|  | // the mapper exposes the new association interface but intakes | 
|  | // the old | 
|  |  | 
|  | auto& iface = associationInterfaces[object.first]; | 
|  | auto& i = std::get<ifacePos>(iface); | 
|  | auto& endpoints = std::get<endpointsPos>(iface); | 
|  | std::get<sourcePos>(iface) = path; | 
|  |  | 
|  | // Only add new endpoints | 
|  | for (auto& e : object.second) | 
|  | { | 
|  | if (std::find(endpoints.begin(), endpoints.end(), e) == | 
|  | endpoints.end()) | 
|  | { | 
|  | endpoints.push_back(e); | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the interface already exists, only need to update | 
|  | // the property value, otherwise create it | 
|  | if (i) | 
|  | { | 
|  | i->set_property("endpoints", endpoints); | 
|  | } | 
|  | else | 
|  | { | 
|  | i = objectServer.add_interface(object.first, | 
|  | XYZ_ASSOCIATION_INTERFACE); | 
|  | i->register_property("endpoints", endpoints); | 
|  | i->initialize(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void removeAssociation(const std::string& sourcePath, | 
|  | sdbusplus::asio::object_server& server) | 
|  | { | 
|  | // The sourcePath passed in can be: | 
|  | // a) the source of the association object, in which case | 
|  | //    that whole object needs to be deleted, and/or | 
|  | // b) just an entry in the endpoints property under some | 
|  | //    other path, in which case it just needs to be removed | 
|  | //    from the property. | 
|  | for (auto& assoc : associationInterfaces) | 
|  | { | 
|  | if (sourcePath == std::get<sourcePos>(assoc.second)) | 
|  | { | 
|  | server.remove_interface(std::get<ifacePos>(assoc.second)); | 
|  | std::get<ifacePos>(assoc.second) = nullptr; | 
|  | std::get<endpointsPos>(assoc.second).clear(); | 
|  | std::get<sourcePos>(assoc.second).clear(); | 
|  | } | 
|  | else | 
|  | { | 
|  | auto& endpoints = std::get<endpointsPos>(assoc.second); | 
|  | auto toRemove = | 
|  | std::find(endpoints.begin(), endpoints.end(), sourcePath); | 
|  | if (toRemove != endpoints.end()) | 
|  | { | 
|  | endpoints.erase(toRemove); | 
|  |  | 
|  | if (endpoints.empty()) | 
|  | { | 
|  | // Remove the interface object too if no longer needed | 
|  | server.remove_interface(std::get<ifacePos>(assoc.second)); | 
|  | std::get<ifacePos>(assoc.second) = nullptr; | 
|  | std::get<sourcePos>(assoc.second).clear(); | 
|  | } | 
|  | else | 
|  | { | 
|  | std::get<ifacePos>(assoc.second) | 
|  | ->set_property("endpoints", endpoints); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void do_associations(sdbusplus::asio::connection* system_bus, | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | const std::string& processName, const std::string& path) | 
|  | { | 
|  | system_bus->async_method_call( | 
|  | [&objectServer, | 
|  | path](const boost::system::error_code ec, | 
|  | const sdbusplus::message::variant<std::vector<Association>>& | 
|  | variantAssociations) { | 
|  | if (ec) | 
|  | { | 
|  | std::cerr << "Error getting associations from " << path << "\n"; | 
|  | } | 
|  | std::vector<Association> associations = | 
|  | sdbusplus::message::variant_ns::get<std::vector<Association>>( | 
|  | variantAssociations); | 
|  | addAssociation(objectServer, associations, path); | 
|  | }, | 
|  | processName, path, "org.freedesktop.DBus.Properties", "Get", | 
|  | ASSOCIATIONS_INTERFACE, "associations"); | 
|  | } | 
|  |  | 
|  | void do_introspect(sdbusplus::asio::connection* system_bus, | 
|  | std::shared_ptr<InProgressIntrospect> transaction, | 
|  | interface_map_type& interface_map, | 
|  | sdbusplus::asio::object_server& objectServer, | 
|  | std::string path) | 
|  | { | 
|  | system_bus->async_method_call( | 
|  | [&interface_map, &objectServer, transaction, path, | 
|  | system_bus](const boost::system::error_code ec, | 
|  | const std::string& introspect_xml) { | 
|  | if (ec) | 
|  | { | 
|  | std::cerr << "Introspect call failed with error: " << ec << ", " | 
|  | << ec.message() | 
|  | << " on process: " << transaction->process_name | 
|  | << " path: " << path << "\n"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | tinyxml2::XMLDocument doc; | 
|  |  | 
|  | tinyxml2::XMLError e = doc.Parse(introspect_xml.c_str()); | 
|  | if (e != tinyxml2::XMLError::XML_SUCCESS) | 
|  | { | 
|  | std::cerr << "XML parsing failed\n"; | 
|  | return; | 
|  | } | 
|  |  | 
|  | tinyxml2::XMLNode* pRoot = doc.FirstChildElement("node"); | 
|  | if (pRoot == nullptr) | 
|  | { | 
|  | std::cerr << "XML document did not contain any data\n"; | 
|  | return; | 
|  | } | 
|  | auto& thisPathMap = interface_map[path]; | 
|  | bool handling_via_objectmanager = false; | 
|  | tinyxml2::XMLElement* pElement = | 
|  | pRoot->FirstChildElement("interface"); | 
|  | while (pElement != nullptr) | 
|  | { | 
|  | const char* iface_name = pElement->Attribute("name"); | 
|  | if (iface_name == nullptr) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string iface{iface_name}; | 
|  |  | 
|  | if (((ignored_interfaces.find(iface) == | 
|  | ignored_interfaces.end()) && | 
|  | (std::find_if(iface_whitelist.begin(), | 
|  | iface_whitelist.end(), | 
|  | [iface](const auto& prefix) { | 
|  | return boost::starts_with(iface, prefix); | 
|  | }) != iface_whitelist.end())) || | 
|  | (iface == "org.freedesktop.DBus.ObjectManager")) | 
|  | { | 
|  | thisPathMap[transaction->process_name].emplace(iface_name); | 
|  | } | 
|  | if (std::strcmp(iface_name, ASSOCIATIONS_INTERFACE) == 0) | 
|  | { | 
|  | do_associations(system_bus, objectServer, | 
|  | transaction->process_name, path); | 
|  | } | 
|  | else if (std::strcmp(iface_name, | 
|  | "org.freedesktop.DBus.ObjectManager") == 0) | 
|  | { | 
|  | // TODO(ed) in the current implementation, | 
|  | // introspect is actually faster than | 
|  | // getmanagedObjects, but I suspect it will be | 
|  | // faster when needing to deal with | 
|  | // associations, so leave the code here for now | 
|  |  | 
|  | // handling_via_objectmanager = true; | 
|  | // do_getmanagedobjects(system_bus, transaction, | 
|  | //                     interface_map, path); | 
|  | } | 
|  |  | 
|  | pElement = pElement->NextSiblingElement("interface"); | 
|  | } | 
|  |  | 
|  | if (!handling_via_objectmanager) | 
|  | { | 
|  | pElement = pRoot->FirstChildElement("node"); | 
|  | while (pElement != nullptr) | 
|  | { | 
|  | const char* child_path = pElement->Attribute("name"); | 
|  | if (child_path != nullptr) | 
|  | { | 
|  | std::string parent_path(path); | 
|  | if (parent_path == "/") | 
|  | { | 
|  | parent_path.clear(); | 
|  | } | 
|  |  | 
|  | do_introspect(system_bus, transaction, interface_map, | 
|  | objectServer, | 
|  | parent_path + "/" + child_path); | 
|  | } | 
|  | pElement = pElement->NextSiblingElement("node"); | 
|  | } | 
|  | } | 
|  | }, | 
|  | transaction->process_name, path, "org.freedesktop.DBus.Introspectable", | 
|  | "Introspect"); | 
|  | } | 
|  |  | 
|  | bool need_to_introspect(const std::string& process_name) | 
|  | { | 
|  | auto inWhitelist = | 
|  | std::find_if(service_whitelist.begin(), service_whitelist.end(), | 
|  | [&process_name](const auto& prefix) { | 
|  | return boost::starts_with(process_name, prefix); | 
|  | }) != service_whitelist.end(); | 
|  |  | 
|  | // This holds full service names, not prefixes | 
|  | auto inBlacklist = | 
|  | service_blacklist.find(process_name) != service_blacklist.end(); | 
|  |  | 
|  | return inWhitelist && !inBlacklist; | 
|  | } | 
|  |  | 
|  | void start_new_introspect( | 
|  | sdbusplus::asio::connection* system_bus, boost::asio::io_service& io, | 
|  | interface_map_type& interface_map, const std::string& process_name, | 
|  | #ifdef DEBUG | 
|  | std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> | 
|  | global_start_time, | 
|  | #endif | 
|  | sdbusplus::asio::object_server& objectServer) | 
|  | { | 
|  | if (need_to_introspect(process_name)) | 
|  | { | 
|  | std::shared_ptr<InProgressIntrospect> transaction = | 
|  | std::make_shared<InProgressIntrospect>(system_bus, io, process_name | 
|  | #ifdef DEBUG | 
|  | , | 
|  | global_start_time | 
|  | #endif | 
|  | ); | 
|  |  | 
|  | do_introspect(system_bus, transaction, interface_map, objectServer, | 
|  | "/"); | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(ed) replace with std::set_intersection once c++17 is available | 
|  | template <class InputIt1, class InputIt2> | 
|  | bool intersect(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) | 
|  | { | 
|  | while (first1 != last1 && first2 != last2) | 
|  | { | 
|  | if (*first1 < *first2) | 
|  | { | 
|  | ++first1; | 
|  | continue; | 
|  | } | 
|  | if (*first2 < *first1) | 
|  | { | 
|  | ++first2; | 
|  | continue; | 
|  | } | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void doListNames( | 
|  | boost::asio::io_service& io, interface_map_type& interface_map, | 
|  | sdbusplus::asio::connection* system_bus, | 
|  | boost::container::flat_map<std::string, std::string>& name_owners, | 
|  | sdbusplus::asio::object_server& objectServer) | 
|  | { | 
|  | system_bus->async_method_call( | 
|  | [&io, &interface_map, &name_owners, &objectServer, | 
|  | system_bus](const boost::system::error_code ec, | 
|  | std::vector<std::string> process_names) { | 
|  | if (ec) | 
|  | { | 
|  | std::cerr << "Error getting names: " << ec << "\n"; | 
|  | std::exit(EXIT_FAILURE); | 
|  | return; | 
|  | } | 
|  | // Try to make startup consistent | 
|  | std::sort(process_names.begin(), process_names.end()); | 
|  | #ifdef DEBUG | 
|  | std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> | 
|  | global_start_time = std::make_shared< | 
|  | std::chrono::time_point<std::chrono::steady_clock>>( | 
|  | std::chrono::steady_clock::now()); | 
|  | #endif | 
|  | for (const std::string& process_name : process_names) | 
|  | { | 
|  | if (need_to_introspect(process_name)) | 
|  | { | 
|  | start_new_introspect(system_bus, io, interface_map, | 
|  | process_name, | 
|  | #ifdef DEBUG | 
|  | global_start_time, | 
|  | #endif | 
|  | objectServer); | 
|  | update_owners(system_bus, name_owners, process_name); | 
|  | } | 
|  | } | 
|  | }, | 
|  | "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", | 
|  | "ListNames"); | 
|  | } | 
|  |  | 
|  | void splitArgs(const std::string& stringArgs, | 
|  | boost::container::flat_set<std::string>& listArgs) | 
|  | { | 
|  | std::istringstream args; | 
|  | std::string arg; | 
|  |  | 
|  | args.str(stringArgs); | 
|  |  | 
|  | while (!args.eof()) | 
|  | { | 
|  | args >> arg; | 
|  | if (!arg.empty()) | 
|  | { | 
|  | listArgs.insert(arg); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void addSubtreeResult( | 
|  | std::vector<interface_map_type::value_type>& subtree, | 
|  | const std::string& objectPath, | 
|  | const std::pair<std::string, boost::container::flat_set<std::string>>& | 
|  | interfaceMap) | 
|  | { | 
|  | // Adds an object path/service name/interface list entry to | 
|  | // the results of GetSubTree. | 
|  | // If an entry for the object path already exists, just add the | 
|  | // service name and interfaces to that entry, otherwise create | 
|  | // a new entry. | 
|  | auto entry = std::find_if( | 
|  | subtree.begin(), subtree.end(), | 
|  | [&objectPath](const auto& i) { return objectPath == i.first; }); | 
|  |  | 
|  | if (entry != subtree.end()) | 
|  | { | 
|  | entry->second.emplace(interfaceMap); | 
|  | } | 
|  | else | 
|  | { | 
|  | interface_map_type::value_type object; | 
|  | object.first = objectPath; | 
|  | object.second.emplace(interfaceMap); | 
|  | subtree.push_back(object); | 
|  | } | 
|  | } | 
|  |  | 
|  | int main(int argc, char** argv) | 
|  | { | 
|  | auto options = ArgumentParser(argc, argv); | 
|  | boost::asio::io_service io; | 
|  | std::shared_ptr<sdbusplus::asio::connection> system_bus = | 
|  | std::make_shared<sdbusplus::asio::connection>(io); | 
|  |  | 
|  | splitArgs(options["service-namespaces"], service_whitelist); | 
|  | splitArgs(options["interface-namespaces"], iface_whitelist); | 
|  | splitArgs(options["service-blacklists"], service_blacklist); | 
|  |  | 
|  | system_bus->request_name(OBJECT_MAPPER_DBUS_NAME); | 
|  | sdbusplus::asio::object_server server(system_bus); | 
|  |  | 
|  | // Construct a signal set registered for process termination. | 
|  | boost::asio::signal_set signals(io, SIGINT, SIGTERM); | 
|  | signals.async_wait([&io](const boost::system::error_code& error, | 
|  | int signal_number) { io.stop(); }); | 
|  |  | 
|  | interface_map_type interface_map; | 
|  | boost::container::flat_map<std::string, std::string> name_owners; | 
|  |  | 
|  | std::function<void(sdbusplus::message::message & message)> | 
|  | nameChangeHandler = [&interface_map, &io, &name_owners, &server, | 
|  | system_bus](sdbusplus::message::message& message) { | 
|  | std::string name; | 
|  | std::string old_owner; | 
|  | std::string new_owner; | 
|  |  | 
|  | message.read(name, old_owner, new_owner); | 
|  |  | 
|  | if (!old_owner.empty()) | 
|  | { | 
|  | if (boost::starts_with(old_owner, ":")) | 
|  | { | 
|  | auto it = name_owners.find(old_owner); | 
|  | if (it != name_owners.end()) | 
|  | { | 
|  | name_owners.erase(it); | 
|  | } | 
|  | } | 
|  | // Connection removed | 
|  | interface_map_type::iterator path_it = interface_map.begin(); | 
|  | while (path_it != interface_map.end()) | 
|  | { | 
|  | // If an associations interface is being removed, | 
|  | // also need to remove the corresponding associations | 
|  | // objects and properties. | 
|  | auto ifaces = path_it->second.find(name); | 
|  | if (ifaces != path_it->second.end()) | 
|  | { | 
|  | auto assoc = std::find(ifaces->second.begin(), | 
|  | ifaces->second.end(), | 
|  | ASSOCIATIONS_INTERFACE); | 
|  |  | 
|  | if (assoc != ifaces->second.end()) | 
|  | { | 
|  | removeAssociation(path_it->first, server); | 
|  | } | 
|  | } | 
|  |  | 
|  | path_it->second.erase(name); | 
|  | if (path_it->second.empty()) | 
|  | { | 
|  | // If the last connection to the object is gone, | 
|  | // delete the top level object | 
|  | path_it = interface_map.erase(path_it); | 
|  | continue; | 
|  | } | 
|  | path_it++; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!new_owner.empty()) | 
|  | { | 
|  | #ifdef DEBUG | 
|  | auto transaction = std::make_shared< | 
|  | std::chrono::time_point<std::chrono::steady_clock>>( | 
|  | std::chrono::steady_clock::now()); | 
|  | #endif | 
|  | // New daemon added | 
|  | if (need_to_introspect(name)) | 
|  | { | 
|  | name_owners[new_owner] = name; | 
|  | start_new_introspect(system_bus.get(), io, interface_map, | 
|  | name, | 
|  | #ifdef DEBUG | 
|  | transaction, | 
|  | #endif | 
|  | server); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | sdbusplus::bus::match::match nameOwnerChanged( | 
|  | static_cast<sdbusplus::bus::bus&>(*system_bus), | 
|  | sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler); | 
|  |  | 
|  | std::function<void(sdbusplus::message::message & message)> | 
|  | interfacesAddedHandler = [&interface_map, &name_owners, &server]( | 
|  | sdbusplus::message::message& message) { | 
|  | sdbusplus::message::object_path obj_path; | 
|  | std::vector<std::pair< | 
|  | std::string, std::vector<std::pair< | 
|  | std::string, sdbusplus::message::variant< | 
|  | std::vector<Association>>>>>> | 
|  | interfaces_added; | 
|  | message.read(obj_path, interfaces_added); | 
|  | std::string well_known; | 
|  | if (!get_well_known(name_owners, message.get_sender(), well_known)) | 
|  | { | 
|  | return; // only introspect well-known | 
|  | } | 
|  | if (need_to_introspect(well_known)) | 
|  | { | 
|  | auto& iface_list = interface_map[obj_path.str]; | 
|  |  | 
|  | for (const auto& interface_pair : interfaces_added) | 
|  | { | 
|  | iface_list[well_known].emplace(interface_pair.first); | 
|  |  | 
|  | if (interface_pair.first == ASSOCIATIONS_INTERFACE) | 
|  | { | 
|  | const sdbusplus::message::variant< | 
|  | std::vector<Association>>* variantAssociations = | 
|  | nullptr; | 
|  | for (const auto& interface : interface_pair.second) | 
|  | { | 
|  | if (interface.first == "associations") | 
|  | { | 
|  | variantAssociations = &(interface.second); | 
|  | } | 
|  | } | 
|  | if (variantAssociations == nullptr) | 
|  | { | 
|  | std::cerr << "Illegal association found on " | 
|  | << well_known << "\n"; | 
|  | continue; | 
|  | } | 
|  | std::vector<Association> associations = | 
|  | sdbusplus::message::variant_ns::get< | 
|  | std::vector<Association>>(*variantAssociations); | 
|  | addAssociation(server, associations, obj_path.str); | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | sdbusplus::bus::match::match interfacesAdded( | 
|  | static_cast<sdbusplus::bus::bus&>(*system_bus), | 
|  | sdbusplus::bus::match::rules::interfacesAdded(), | 
|  | interfacesAddedHandler); | 
|  |  | 
|  | std::function<void(sdbusplus::message::message & message)> | 
|  | interfacesRemovedHandler = [&interface_map, &name_owners, &server]( | 
|  | sdbusplus::message::message& message) { | 
|  | sdbusplus::message::object_path obj_path; | 
|  | std::vector<std::string> interfaces_removed; | 
|  | message.read(obj_path, interfaces_removed); | 
|  | auto connection_map = interface_map.find(obj_path.str); | 
|  | if (connection_map == interface_map.end()) | 
|  | { | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string sender; | 
|  | if (!get_well_known(name_owners, message.get_sender(), sender)) | 
|  | { | 
|  | return; | 
|  | } | 
|  | for (const std::string& interface : interfaces_removed) | 
|  | { | 
|  | auto interface_set = connection_map->second.find(sender); | 
|  | if (interface_set == connection_map->second.end()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (interface == ASSOCIATIONS_INTERFACE) | 
|  | { | 
|  | removeAssociation(obj_path.str, server); | 
|  | } | 
|  |  | 
|  | interface_set->second.erase(interface); | 
|  | // If this was the last interface on this connection, | 
|  | // erase the connection | 
|  | if (interface_set->second.empty()) | 
|  | { | 
|  | connection_map->second.erase(interface_set); | 
|  | } | 
|  | } | 
|  | // If this was the last connection on this object path, | 
|  | // erase the object path | 
|  | if (connection_map->second.empty()) | 
|  | { | 
|  | interface_map.erase(connection_map); | 
|  | } | 
|  | }; | 
|  |  | 
|  | sdbusplus::bus::match::match interfacesRemoved( | 
|  | static_cast<sdbusplus::bus::bus&>(*system_bus), | 
|  | sdbusplus::bus::match::rules::interfacesRemoved(), | 
|  | interfacesRemovedHandler); | 
|  |  | 
|  | std::function<void(sdbusplus::message::message & message)> | 
|  | associationChangedHandler = | 
|  | [&server](sdbusplus::message::message& message) { | 
|  | std::string objectName; | 
|  | boost::container::flat_map< | 
|  | std::string, | 
|  | sdbusplus::message::variant<std::vector<Association>>> | 
|  | values; | 
|  | message.read(objectName, values); | 
|  | auto findAssociations = values.find("associations"); | 
|  | if (findAssociations != values.end()) | 
|  | { | 
|  | std::vector<Association> associations = | 
|  | sdbusplus::message::variant_ns::get< | 
|  | std::vector<Association>>(findAssociations->second); | 
|  | addAssociation(server, associations, message.get_path()); | 
|  | } | 
|  | }; | 
|  | sdbusplus::bus::match::match associationChanged( | 
|  | static_cast<sdbusplus::bus::bus&>(*system_bus), | 
|  | sdbusplus::bus::match::rules::interface( | 
|  | "org.freedesktop.DBus.Properties") + | 
|  | sdbusplus::bus::match::rules::member("PropertiesChanged") + | 
|  | sdbusplus::bus::match::rules::argN(0, ASSOCIATIONS_INTERFACE), | 
|  | associationChangedHandler); | 
|  |  | 
|  | std::shared_ptr<sdbusplus::asio::dbus_interface> iface = | 
|  | server.add_interface("/xyz/openbmc_project/object_mapper", | 
|  | "xyz.openbmc_project.ObjectMapper"); | 
|  |  | 
|  | iface->register_method( | 
|  | "GetAncestors", [&interface_map](const std::string& req_path, | 
|  | std::vector<std::string>& interfaces) { | 
|  | // Interfaces need to be sorted for intersect to function | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  |  | 
|  | std::vector<interface_map_type::value_type> ret; | 
|  | for (auto& object_path : interface_map) | 
|  | { | 
|  | auto& this_path = object_path.first; | 
|  | if (boost::starts_with(req_path, this_path) && | 
|  | (req_path != this_path)) | 
|  | { | 
|  | if (interfaces.empty()) | 
|  | { | 
|  | ret.emplace_back(object_path); | 
|  | } | 
|  | else | 
|  | { | 
|  | for (auto& interface_map : object_path.second) | 
|  | { | 
|  |  | 
|  | if (intersect(interfaces.begin(), interfaces.end(), | 
|  | interface_map.second.begin(), | 
|  | interface_map.second.end())) | 
|  | { | 
|  | ret.emplace_back(object_path); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (ret.empty()) | 
|  | { | 
|  | throw NotFoundException(); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | }); | 
|  |  | 
|  | iface->register_method( | 
|  | "GetObject", [&interface_map](const std::string& path, | 
|  | std::vector<std::string>& interfaces) { | 
|  | boost::container::flat_map<std::string, | 
|  | boost::container::flat_set<std::string>> | 
|  | results; | 
|  |  | 
|  | // Interfaces need to be sorted for intersect to function | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | auto path_ref = interface_map.find(path); | 
|  | if (path_ref == interface_map.end()) | 
|  | { | 
|  | throw NotFoundException(); | 
|  | } | 
|  | if (interfaces.empty()) | 
|  | { | 
|  | return path_ref->second; | 
|  | } | 
|  | for (auto& interface_map : path_ref->second) | 
|  | { | 
|  | if (intersect(interfaces.begin(), interfaces.end(), | 
|  | interface_map.second.begin(), | 
|  | interface_map.second.end())) | 
|  | { | 
|  | results.emplace(interface_map.first, interface_map.second); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (results.empty()) | 
|  | { | 
|  | throw NotFoundException(); | 
|  | } | 
|  |  | 
|  | return results; | 
|  | }); | 
|  |  | 
|  | iface->register_method( | 
|  | "GetSubTree", [&interface_map](std::string& req_path, int32_t depth, | 
|  | std::vector<std::string>& interfaces) { | 
|  | if (depth <= 0) | 
|  | { | 
|  | depth = std::numeric_limits<int32_t>::max(); | 
|  | } | 
|  | // Interfaces need to be sorted for intersect to function | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | std::vector<interface_map_type::value_type> ret; | 
|  |  | 
|  | if (req_path.size() > 1 && req_path.back() == '/') | 
|  | { | 
|  | req_path.pop_back(); | 
|  | } | 
|  |  | 
|  | for (auto& object_path : interface_map) | 
|  | { | 
|  | auto& this_path = object_path.first; | 
|  |  | 
|  | if (this_path == req_path) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (boost::starts_with(this_path, req_path)) | 
|  | { | 
|  | // count the number of slashes past the search term | 
|  | int32_t this_depth = | 
|  | std::count(this_path.begin() + req_path.size(), | 
|  | this_path.end(), '/'); | 
|  | if (this_depth <= depth) | 
|  | { | 
|  | for (auto& interface_map : object_path.second) | 
|  | { | 
|  | if (intersect(interfaces.begin(), interfaces.end(), | 
|  | interface_map.second.begin(), | 
|  | interface_map.second.end()) || | 
|  | interfaces.empty()) | 
|  | { | 
|  | addSubtreeResult(ret, this_path, interface_map); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (ret.empty()) | 
|  | { | 
|  | throw NotFoundException(); | 
|  | } | 
|  | return ret; | 
|  | }); | 
|  |  | 
|  | iface->register_method( | 
|  | "GetSubTreePaths", | 
|  | [&interface_map](std::string& req_path, int32_t depth, | 
|  | std::vector<std::string>& interfaces) { | 
|  | if (depth <= 0) | 
|  | { | 
|  | depth = std::numeric_limits<int32_t>::max(); | 
|  | } | 
|  | // Interfaces need to be sorted for intersect to function | 
|  | std::sort(interfaces.begin(), interfaces.end()); | 
|  | std::vector<std::string> ret; | 
|  |  | 
|  | if (req_path.size() > 1 && req_path.back() == '/') | 
|  | { | 
|  | req_path.pop_back(); | 
|  | } | 
|  |  | 
|  | for (auto& object_path : interface_map) | 
|  | { | 
|  | auto& this_path = object_path.first; | 
|  |  | 
|  | if (this_path == req_path) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (boost::starts_with(this_path, req_path)) | 
|  | { | 
|  | // count the number of slashes past the search term | 
|  | int this_depth = | 
|  | std::count(this_path.begin() + req_path.size(), | 
|  | this_path.end(), '/'); | 
|  | if (this_depth <= depth) | 
|  | { | 
|  | bool add = interfaces.empty(); | 
|  | for (auto& interface_map : object_path.second) | 
|  | { | 
|  | if (intersect(interfaces.begin(), interfaces.end(), | 
|  | interface_map.second.begin(), | 
|  | interface_map.second.end())) | 
|  | { | 
|  | add = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (add) | 
|  | { | 
|  | // TODO(ed) this is a copy | 
|  | ret.emplace_back(this_path); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (ret.empty()) | 
|  | { | 
|  | throw NotFoundException(); | 
|  | } | 
|  | return ret; | 
|  | }); | 
|  |  | 
|  | iface->initialize(); | 
|  |  | 
|  | io.post([&]() { | 
|  | doListNames(io, interface_map, system_bus.get(), name_owners, server); | 
|  | }); | 
|  |  | 
|  | io.run(); | 
|  | } |