#include "associations.hpp" #include "processing.hpp" #include "src/argument.hpp" #include "types.hpp" #include #include #include #include #include #include #include #include #include #include #include AssociationMaps associationMaps; static WhiteBlackList service_whitelist; static WhiteBlackList service_blacklist; /** 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 "xyz.openbmc_project.Common.Error.ResourceNotFound"; }; const char* description() const noexcept override { return "path or object not found"; }; const char* what() const noexcept override { return "xyz.openbmc_project.Common.Error.ResourceNotFound: " "The resource is not found."; }; int get_errno() const noexcept override { return ENOENT; } }; void update_owners(sdbusplus::asio::connection* conn, boost::container::flat_map& 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_context& io, const std::string& process_name, AssociationMaps& am #ifdef DEBUG , std::shared_ptr> global_start_time #endif ) : system_bus(system_bus), io(io), process_name(process_name), assocMaps(am) #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 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_context& io; std::string process_name; AssociationMaps& assocMaps; #ifdef DEBUG std::shared_ptr> global_start_time; std::chrono::time_point process_start_time; #endif }; void do_associations(sdbusplus::asio::connection* system_bus, interface_map_type& interfaceMap, sdbusplus::asio::object_server& objectServer, const std::string& processName, const std::string& path, int timeoutRetries = 0) { constexpr int maxTimeoutRetries = 3; system_bus->async_method_call( [&objectServer, path, processName, &interfaceMap, system_bus, timeoutRetries]( const boost::system::error_code ec, const std::variant>& variantAssociations) { if (ec) { if (ec.value() == boost::system::errc::timed_out && timeoutRetries < maxTimeoutRetries) { do_associations(system_bus, interfaceMap, objectServer, processName, path, timeoutRetries + 1); return; } std::cerr << "Error getting associations from " << path << "\n"; } std::vector associations = std::get>(variantAssociations); associationChanged(objectServer, associations, path, processName, interfaceMap, associationMaps); }, processName, path, "org.freedesktop.DBus.Properties", "Get", assocDefsInterface, assocDefsProperty); } void do_introspect(sdbusplus::asio::connection* system_bus, std::shared_ptr transaction, interface_map_type& interface_map, sdbusplus::asio::object_server& objectServer, std::string path, int timeoutRetries = 0) { constexpr int maxTimeoutRetries = 3; system_bus->async_method_call( [&interface_map, &objectServer, transaction, path, system_bus, timeoutRetries](const boost::system::error_code ec, const std::string& introspect_xml) { if (ec) { if (ec.value() == boost::system::errc::timed_out && timeoutRetries < maxTimeoutRetries) { do_introspect(system_bus, transaction, interface_map, objectServer, path, timeoutRetries + 1); return; } 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]; tinyxml2::XMLElement* pElement = pRoot->FirstChildElement("interface"); while (pElement != nullptr) { const char* iface_name = pElement->Attribute("name"); if (iface_name == nullptr) { continue; } thisPathMap[transaction->process_name].emplace(iface_name); if (std::strcmp(iface_name, assocDefsInterface) == 0) { do_associations(system_bus, interface_map, objectServer, transaction->process_name, path); } pElement = pElement->NextSiblingElement("interface"); } // Check if this new path has a pending association that can // now be completed. checkIfPendingAssociation(path, interface_map, transaction->assocMaps, objectServer); 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"); } void start_new_introspect( sdbusplus::asio::connection* system_bus, boost::asio::io_context& io, interface_map_type& interface_map, const std::string& process_name, AssociationMaps& assocMaps, #ifdef DEBUG std::shared_ptr> global_start_time, #endif sdbusplus::asio::object_server& objectServer) { if (needToIntrospect(process_name, service_whitelist, service_blacklist)) { std::shared_ptr transaction = std::make_shared(system_bus, io, process_name, assocMaps #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 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_context& io, interface_map_type& interface_map, sdbusplus::asio::connection* system_bus, boost::container::flat_map& name_owners, AssociationMaps& assocMaps, sdbusplus::asio::object_server& objectServer) { system_bus->async_method_call( [&io, &interface_map, &name_owners, &objectServer, system_bus, &assocMaps](const boost::system::error_code ec, std::vector 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> global_start_time = std::make_shared< std::chrono::time_point>( std::chrono::steady_clock::now()); #endif for (const std::string& process_name : process_names) { if (needToIntrospect(process_name, service_whitelist, service_blacklist)) { start_new_introspect(system_bus, io, interface_map, process_name, assocMaps, #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& listArgs) { std::istringstream args; std::string arg; args.str(stringArgs); while (!args.eof()) { args >> arg; if (!arg.empty()) { listArgs.insert(arg); } } } void addObjectMapResult( std::vector& objectMap, const std::string& objectPath, const std::pair>& interfaceMap) { // Adds an object path/service name/interface list entry to // the results of GetSubTree and GetAncestors. // 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( objectMap.begin(), objectMap.end(), [&objectPath](const auto& i) { return objectPath == i.first; }); if (entry != objectMap.end()) { entry->second.emplace(interfaceMap); } else { interface_map_type::value_type object; object.first = objectPath; object.second.emplace(interfaceMap); objectMap.push_back(object); } } // Remove parents of the passed in path that: // 1) Only have the 3 default interfaces on them // - Means D-Bus created these, not application code, // with the Properties, Introspectable, and Peer ifaces // 2) Have no other child for this owner void removeUnneededParents(const std::string& objectPath, const std::string& owner, interface_map_type& interface_map) { auto parent = objectPath; while (true) { auto pos = parent.find_last_of('/'); if ((pos == std::string::npos) || (pos == 0)) { break; } parent = parent.substr(0, pos); auto parent_it = interface_map.find(parent); if (parent_it == interface_map.end()) { break; } auto ifaces_it = parent_it->second.find(owner); if (ifaces_it == parent_it->second.end()) { break; } if (ifaces_it->second.size() != 3) { break; } auto child_path = parent + '/'; // Remove this parent if there isn't a remaining child on this owner auto child = std::find_if( interface_map.begin(), interface_map.end(), [&owner, &child_path](const auto& entry) { return boost::starts_with(entry.first, child_path) && (entry.second.find(owner) != entry.second.end()); }); if (child == interface_map.end()) { parent_it->second.erase(ifaces_it); if (parent_it->second.empty()) { interface_map.erase(parent_it); } } else { break; } } } int main(int argc, char** argv) { auto options = ArgumentParser(argc, argv); boost::asio::io_context io; std::shared_ptr system_bus = std::make_shared(io); splitArgs(options["service-namespaces"], service_whitelist); splitArgs(options["service-blacklists"], service_blacklist); // TODO(Ed) Remove this once all service files are updated to not use this. // For now, simply squash the input, and ignore it. boost::container::flat_set iface_whitelist; splitArgs(options["interface-namespaces"], iface_whitelist); 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&, int) { io.stop(); }); interface_map_type interface_map; boost::container::flat_map name_owners; std::function nameChangeHandler = [&interface_map, &io, &name_owners, &server, system_bus](sdbusplus::message::message& message) { std::string name; // well-known std::string old_owner; // unique-name std::string new_owner; // unique-name message.read(name, old_owner, new_owner); if (!old_owner.empty()) { processNameChangeDelete(name_owners, name, old_owner, interface_map, associationMaps, server); } if (!new_owner.empty()) { #ifdef DEBUG auto transaction = std::make_shared< std::chrono::time_point>( std::chrono::steady_clock::now()); #endif // New daemon added if (needToIntrospect(name, service_whitelist, service_blacklist)) { name_owners[new_owner] = name; start_new_introspect(system_bus.get(), io, interface_map, name, associationMaps, #ifdef DEBUG transaction, #endif server); } } }; sdbusplus::bus::match::match nameOwnerChanged( static_cast(*system_bus), sdbusplus::bus::match::rules::nameOwnerChanged(), nameChangeHandler); std::function interfacesAddedHandler = [&interface_map, &name_owners, &server]( sdbusplus::message::message& message) { sdbusplus::message::object_path obj_path; InterfacesAdded interfaces_added; message.read(obj_path, interfaces_added); std::string well_known; if (!getWellKnown(name_owners, message.get_sender(), well_known)) { return; // only introspect well-known } if (needToIntrospect(well_known, service_whitelist, service_blacklist)) { processInterfaceAdded(interface_map, obj_path, interfaces_added, well_known, associationMaps, server); } }; sdbusplus::bus::match::match interfacesAdded( static_cast(*system_bus), sdbusplus::bus::match::rules::interfacesAdded(), interfacesAddedHandler); std::function interfacesRemovedHandler = [&interface_map, &name_owners, &server]( sdbusplus::message::message& message) { sdbusplus::message::object_path obj_path; std::vector 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 (!getWellKnown(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 == assocDefsInterface) { removeAssociation(obj_path.str, sender, server, associationMaps); } interface_set->second.erase(interface); if (interface_set->second.empty()) { // If this was the last interface on this connection, // erase the connection connection_map->second.erase(interface_set); // Instead of checking if every single path is the endpoint // of an association that needs to be moved to pending, // only check when the only remaining owner of this path is // ourself, which would be because we still own the // association path. if ((connection_map->second.size() == 1) && (connection_map->second.begin()->first == "xyz.openbmc_project.ObjectMapper")) { // Remove the 2 association D-Bus paths and move the // association to pending. moveAssociationToPending(obj_path.str, associationMaps, server); } } } // If this was the last connection on this object path, // erase the object path if (connection_map->second.empty()) { interface_map.erase(connection_map); } removeUnneededParents(obj_path.str, sender, interface_map); }; sdbusplus::bus::match::match interfacesRemoved( static_cast(*system_bus), sdbusplus::bus::match::rules::interfacesRemoved(), interfacesRemovedHandler); std::function associationChangedHandler = [&server, &name_owners, &interface_map]( sdbusplus::message::message& message) { std::string objectName; boost::container::flat_map>> values; message.read(objectName, values); auto prop = values.find(assocDefsProperty); if (prop != values.end()) { std::vector associations = std::get>(prop->second); std::string well_known; if (!getWellKnown(name_owners, message.get_sender(), well_known)) { return; } associationChanged(server, associations, message.get_path(), well_known, interface_map, associationMaps); } }; sdbusplus::bus::match::match assocChangedMatch( static_cast(*system_bus), sdbusplus::bus::match::rules::interface( "org.freedesktop.DBus.Properties") + sdbusplus::bus::match::rules::member("PropertiesChanged") + sdbusplus::bus::match::rules::argN(0, assocDefsInterface), associationChangedHandler); std::shared_ptr iface = server.add_interface("/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper"); iface->register_method( "GetAncestors", [&interface_map](std::string& req_path, std::vector& interfaces) { // Interfaces need to be sorted for intersect to function std::sort(interfaces.begin(), interfaces.end()); if (boost::ends_with(req_path, "/")) { req_path.pop_back(); } if (req_path.size() && interface_map.find(req_path) == interface_map.end()) { throw NotFoundException(); } std::vector 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())) { addObjectMapResult(ret, this_path, interface_map); } } } } } return ret; }); iface->register_method( "GetObject", [&interface_map](const std::string& path, std::vector& interfaces) { boost::container::flat_map> 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& interfaces) { if (depth <= 0) { depth = std::numeric_limits::max(); } // Interfaces need to be sorted for intersect to function std::sort(interfaces.begin(), interfaces.end()); std::vector ret; if (boost::ends_with(req_path, "/")) { req_path.pop_back(); } if (req_path.size() && interface_map.find(req_path) == interface_map.end()) { throw NotFoundException(); } 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()) { addObjectMapResult(ret, this_path, interface_map); } } } } } return ret; }); iface->register_method( "GetSubTreePaths", [&interface_map](std::string& req_path, int32_t depth, std::vector& interfaces) { if (depth <= 0) { depth = std::numeric_limits::max(); } // Interfaces need to be sorted for intersect to function std::sort(interfaces.begin(), interfaces.end()); std::vector ret; if (boost::ends_with(req_path, "/")) { req_path.pop_back(); } if (req_path.size() && interface_map.find(req_path) == interface_map.end()) { throw NotFoundException(); } 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); } } } } return ret; }); iface->initialize(); io.post([&]() { doListNames(io, interface_map, system_bus.get(), name_owners, associationMaps, server); }); system_bus->request_name("xyz.openbmc_project.ObjectMapper"); io.run(); }