diff options
Diffstat (limited to 'src/main.cpp')
-rw-r--r-- | src/main.cpp | 837 |
1 files changed, 837 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..e00e920 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,837 @@ +#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"; + +// 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>; + +boost::container::flat_map<std::string, + std::shared_ptr<sdbusplus::asio::dbus_interface>> + associationInterfaces; + +/** 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, + std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> + global_start_time) : + system_bus(system_bus), + io(io), process_name(process_name), + global_start_time(global_start_time), + process_start_time(std::chrono::steady_clock::now()) + { + } + ~InProgressIntrospect() + { + send_introspection_complete_signal(system_bus, process_name); + 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"; + } + } + sdbusplus::asio::connection* system_bus; + boost::asio::io_service& io; + std::string process_name; + + 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; +}; + +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]; + iface = objectServer.add_interface(object.first, + "xyz.openbmc_project.Association"); + iface->register_property("endpoints", + std::vector<std::string>(object.second.begin(), + object.second.end())); + iface->initialize(); + } +} + +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; + } + + if (ignored_interfaces.find(std::string(iface_name)) == + ignored_interfaces.end()) + { + 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) +{ + return boost::starts_with(process_name, "xyz.openbmc_project.") || + boost::starts_with(process_name, "org.openbmc.") || + boost::starts_with(process_name, "com.intel."); +} + +void start_new_introspect( + sdbusplus::asio::connection* system_bus, boost::asio::io_service& io, + interface_map_type& interface_map, const std::string& process_name, + std::shared_ptr<std::chrono::time_point<std::chrono::steady_clock>> + global_start_time, + sdbusplus::asio::object_server& objectServer) +{ + if (need_to_introspect(process_name)) + { + + std::cerr << "starting introspect on " << process_name << "\n"; + std::shared_ptr<InProgressIntrospect> transaction = + std::make_shared<InProgressIntrospect>(system_bus, io, process_name, + global_start_time); + + 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; + } + std::cerr << "ListNames returned " << process_names.size() + << " entries\n"; + // Try to make startup consistent + std::sort(process_names.begin(), process_names.end()); + 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()); + for (const std::string& process_name : process_names) + { + if (need_to_introspect(process_name)) + { + start_new_introspect(system_bus, io, interface_map, + process_name, global_start_time, + objectServer); + update_owners(system_bus, name_owners, process_name); + } + } + }, + "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", + "ListNames"); +} + +int main(int argc, char** argv) +{ + std::cerr << "started\n"; + boost::asio::io_service io; + std::shared_ptr<sdbusplus::asio::connection> system_bus = + std::make_shared<sdbusplus::asio::connection>(io); + + 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()) + { + 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()) + { + auto transaction = std::make_shared< + std::chrono::time_point<std::chrono::steady_clock>>( + std::chrono::steady_clock::now()); + // New daemon added + if (need_to_introspect(name)) + { + name_owners[new_owner] = name; + start_new_introspect(system_bus.get(), io, interface_map, + name, transaction, 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 std::pair< + std::string, + std::vector<std::pair<std::string, + sdbusplus::message::variant< + std::vector<Association>>>>>& + 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) + { + auto findAssociation = + associationInterfaces.find(interface); + if (findAssociation != associationInterfaces.end()) + { + server.remove_interface(findAssociation->second); + findAssociation->second = nullptr; + } + } + + 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)) + { + 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) { + // 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())) + { + return path_ref->second; + } + } + // Unable to find intersection, return default constructed + // object + throw NotFoundException(); + }); + + iface->register_method( + "GetSubTree", + [&interface_map](const 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; + + for (auto& object_path : interface_map) + { + auto& this_path = object_path.first; + 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) + { + 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(object_path); + } + } + } + } + if (ret.empty()) + { + throw NotFoundException(); + } + return ret; + }); + + iface->register_method( + "GetSubTreePaths", + [&interface_map](const 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; + for (auto& object_path : interface_map) + { + auto& this_path = object_path.first; + 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); + }); + + std::cerr << "starting event loop\n"; + io.run(); +} |