diff options
Diffstat (limited to 'src/sbus/router')
-rw-r--r-- | src/sbus/router/sbus_router.c | 387 | ||||
-rw-r--r-- | src/sbus/router/sbus_router_handler.c | 324 | ||||
-rw-r--r-- | src/sbus/router/sbus_router_hash.c | 547 |
3 files changed, 1258 insertions, 0 deletions
diff --git a/src/sbus/router/sbus_router.c b/src/sbus/router/sbus_router.c new file mode 100644 index 0000000..ed8464b --- /dev/null +++ b/src/sbus/router/sbus_router.c @@ -0,0 +1,387 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "sbus/sbus_private.h" + +static errno_t +sbus_router_register_std(struct sbus_router *router) +{ + errno_t ret; + + ret = sbus_register_introspection(router); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to register org.freedesktop.DBus.Introspectable.\n"); + return ret; + } + + ret = sbus_register_properties(router); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, + "Unable to register org.freedesktop.DBus.Properties.\n"); + return ret; + } + + return EOK; +} + +errno_t +sbus_router_add_path(struct sbus_router *router, + const char *path, + struct sbus_interface *iface) +{ + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, "Registering interface %s on path %s\n", + iface->name, path); + + ret = sbus_router_paths_add(router->paths, path, iface); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add new path [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} + +errno_t +sbus_router_add_path_map(struct sbus_router *router, + struct sbus_path *map) +{ + errno_t ret; + int i; + + for (i = 0; map[i].path != NULL; i++) { + ret = sbus_router_add_path(router, map[i].path, map[i].iface); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +char * +sbus_router_signal_rule(TALLOC_CTX *mem_ctx, + const char *interface, + const char *signal_name) +{ + return talloc_asprintf(mem_ctx, "type='signal',interface='%s',member='%s'", + interface, signal_name); +} + +errno_t +sbus_router_signal_parse(TALLOC_CTX *mem_ctx, + const char *qualified_signal, + char **_interface, + char **_signal_name) +{ + char *signal_name; + char *dot; + char *dup; + + dup = talloc_strdup(mem_ctx, qualified_signal); + if (dup == NULL) { + return ENOMEM; + } + + /* Split the duplicate into interface and signal name parts. */ + dot = strrchr(dup, '.'); + if (dot == NULL) { + talloc_free(dup); + return EINVAL; + } + *dot = '\0'; + + signal_name = talloc_strdup(mem_ctx, dot + 1); + if (signal_name == NULL) { + talloc_free(dup); + return ENOMEM; + } + + *_interface = dup; + *_signal_name = signal_name; + + return EOK; +} + +static void +sbus_router_signal_match(struct sbus_router *router, + DBusConnection *conn, + const char *interface, + const char *signal_name) +{ + char *rule; + + rule = sbus_router_signal_rule(NULL, interface, signal_name); + if (rule == NULL) { + /* There is nothing we can do. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory!\n"); + return; + } + + /* If error is not NULL D-Bus will block. There is nothing to do anyway, + * so we just won't detect errors here. */ + dbus_bus_add_match(conn, rule, NULL); + talloc_free(rule); +} + +errno_t +sbus_router_listen(struct sbus_connection *conn, + struct sbus_listener *listener) +{ + bool signal_known; + errno_t ret; + + /* We can't register signal listener on this connection. */ + if (conn->type == SBUS_CONNECTION_CLIENT) { + return EOK; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Registering signal listener %s.%s on path %s\n", + listener->interface, listener->signal_name, + (listener->object_path == NULL ? "<ALL>" : listener->object_path)); + + ret = sbus_router_listeners_add(conn->router->listeners, + listener->interface, + listener->signal_name, + listener, &signal_known); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add new listener [%d]: %s\n", + ret, sss_strerror(ret)); + return ret; + } + + if (signal_known) { + /* This signal listener is already registered. */ + return EOK; + } + + sbus_router_signal_match(conn->router, conn->connection, + listener->interface, listener->signal_name); + + return ret; +} + +errno_t +sbus_router_listen_map(struct sbus_connection *conn, + struct sbus_listener *map) +{ + errno_t ret; + int i; + + for (i = 0; map[i].interface != NULL; i++) { + ret = sbus_router_listen(conn, &map[i]); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +errno_t +sbus_router_add_node(struct sbus_connection *conn, + struct sbus_node *node) +{ + errno_t ret; + + if (node->path == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Bug: path cannot be NULL!\n"); + return ERR_INTERNAL; + } + + DEBUG(SSSDBG_TRACE_ALL, "Adding new node: %s\n", node->path); + + ret = sbus_router_nodes_add(conn->router->nodes, node); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to add node %s [%d]: %s\n", + node->path, ret, sss_strerror(ret)); + } + + return ret; +} + +errno_t +sbus_router_add_node_map(struct sbus_connection *conn, + struct sbus_node *map) +{ + errno_t ret; + int i; + + for (i = 0; map[i].path != NULL; i++) { + ret = sbus_router_add_node(conn, &map[i]); + if (ret != EOK) { + return ret; + } + } + + return EOK; +} + +static bool +sbus_router_filter_add(struct sbus_router *router) +{ + dbus_bool_t dbret; + + /* Add a connection filter that is used to process input messages. */ + dbret = dbus_connection_add_filter(router->conn->connection, + sbus_connection_filter, + router->conn, NULL); + if (dbret == false) { + return false; + } + + return true; +} + +int sbus_router_destructor(struct sbus_router *router) +{ + dbus_connection_remove_filter(router->conn->connection, + sbus_connection_filter, router->conn); + + return 0; +} + +struct sbus_router * +sbus_router_init(TALLOC_CTX *mem_ctx, + struct sbus_connection *conn) +{ + struct sbus_router *router; + errno_t ret; + bool bret; + + router = talloc_zero(mem_ctx, struct sbus_router); + if (router == NULL) { + return NULL; + } + + router->conn = conn; + + router->paths = sbus_router_paths_init(router); + if (router->paths == NULL) { + goto fail; + } + + router->nodes = sbus_router_nodes_init(router); + if (router->paths == NULL) { + goto fail; + } + + /* Register standard interfaces. */ + ret = sbus_router_register_std(router); + if (ret != EOK) { + goto fail; + } + + /* This is a server-side router. */ + if (conn == NULL) { + return router; + } + + router->listeners = sbus_router_listeners_init(router, conn); + if (router->listeners == NULL) { + goto fail; + } + + bret = sbus_router_filter_add(router); + if (!bret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to register message filter!\n"); + goto fail; + } + + talloc_set_destructor(router, sbus_router_destructor); + + return router; + +fail: + talloc_free(router); + return NULL; +} + +static errno_t +sbus_router_reset_listeners(struct sbus_connection *conn) +{ + TALLOC_CTX *tmp_ctx; + hash_key_t *keys; + char *interface; + char *name; + unsigned long count; + unsigned long i; + errno_t ret; + int hret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + hret = hash_keys(conn->router->listeners, &count, &keys); + if (hret != HASH_SUCCESS) { + ret = ENOMEM; + goto done; + } + + talloc_steal(tmp_ctx, keys); + + for (i = 0; i < count; i++) { + ret = sbus_router_signal_parse(tmp_ctx, keys[i].str, &interface, &name); + if (ret != EOK) { + goto done; + } + + sbus_router_signal_match(conn->router, conn->connection, + interface, name); + } + + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +errno_t +sbus_router_reset(struct sbus_connection *conn) +{ + errno_t ret; + bool bret; + + bret = sbus_router_filter_add(conn->router); + if (!bret) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to register message filter!\n"); + return EFAULT; + } + + ret = sbus_router_reset_listeners(conn); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to reset router listeners " + "[%d]: %s\n", ret, sss_strerror(ret)); + return ret; + } + + return EOK; +} diff --git a/src/sbus/router/sbus_router_handler.c b/src/sbus/router/sbus_router_handler.c new file mode 100644 index 0000000..7b6c244 --- /dev/null +++ b/src/sbus/router/sbus_router_handler.c @@ -0,0 +1,324 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <string.h> +#include <talloc.h> +#include <tevent.h> +#include <sys/types.h> +#include <dbus/dbus.h> + +#include "util/util.h" +#include "util/dlinklist.h" +#include "util/sss_chain_id.h" +#include "sbus/sbus_private.h" + +struct sbus_message_meta { + int type; + const char *destination; + const char *interface; + const char *member; + const char *sender; + const char *path; +}; + +static void +sbus_message_meta_read(DBusMessage *message, + struct sbus_message_meta *meta) +{ + meta->type = dbus_message_get_type(message); + meta->destination = dbus_message_get_destination(message); + meta->interface = dbus_message_get_interface(message); + meta->member = dbus_message_get_member(message); + meta->sender = dbus_message_get_sender(message); + meta->path = dbus_message_get_path(message); +} + +struct sbus_issue_request_state { + struct sbus_connection *conn; + DBusMessageIter message_iter; + DBusMessage *message; + enum sbus_request_type type; +}; + +static void sbus_issue_request_done(struct tevent_req *subreq); + +static errno_t +sbus_issue_request(TALLOC_CTX *mem_ctx, + struct sbus_message_meta *meta, + struct sbus_connection *conn, + DBusMessage *message, + enum sbus_request_type type, + const struct sbus_invoker *invoker, + const struct sbus_handler *handler) +{ + struct sbus_issue_request_state *state; + struct sbus_request *request; + struct tevent_req *subreq; + errno_t ret; + + state = talloc_zero(mem_ctx, struct sbus_issue_request_state); + if (state == NULL) { + return ENOMEM; + } + + state->conn = conn; + state->message = dbus_message_ref(message); + state->type = type; + + ret = sbus_message_bound(state, state->message); + if (ret != EOK) { + dbus_message_unref(state->message); + goto done; + } + + dbus_message_iter_init(message, &state->message_iter); + + request = sbus_request_create(state, conn, type, meta->destination, + meta->interface, meta->member, meta->path); + if (request == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create request data!\n"); + ret = ENOMEM; + goto done; + } + + subreq = sbus_incoming_request_send(state, conn->ev, conn, request, + invoker, handler, meta->sender, + &state->message_iter, message); + if (subreq == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to create request!\n"); + ret = ENOMEM; + goto done; + } + + tevent_req_set_callback(subreq, sbus_issue_request_done, state); + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(state); + } + + return ret; +} + +static void sbus_issue_request_done(struct tevent_req *subreq) +{ + struct sbus_issue_request_state *state; + struct sbus_message_meta meta; + const char *error_name; + const char *error_msg; + uint64_t old_chain_id; + DBusMessage *reply; + errno_t ret; + + /* This is a top level request and a place where we loose tracking of the + * correct chain id. We got here from sbus_incoming_request_done + * which may finish multiple identical requests at once but we know chain + * id only of the one requests that actually run its handler. + * + * Therefore we need to set the id to 0 since it is not known at this + * moment, but it is ok. */ + old_chain_id = sss_chain_id_set(0); + + state = tevent_req_callback_data(subreq, struct sbus_issue_request_state); + sbus_message_meta_read(state->message, &meta); + + ret = sbus_incoming_request_recv(state, subreq, &reply); + talloc_zfree(subreq); + + if (ret == EOK) { + DEBUG(SSSDBG_TRACE_FUNC, "%s.%s: Success\n", + meta.interface, meta.member); + } else { + int msg_level = SSSDBG_OP_FAILURE; + if (ret == ERR_MISSING_DP_TARGET) msg_level = SSSDBG_FUNC_DATA; + DEBUG(msg_level, "%s.%s: Error [%d]: %s\n", + meta.interface, meta.member, ret, sss_strerror(ret)); + } + + /* Signals do not send a reply. */ + if (state->type == SBUS_REQUEST_SIGNAL) { + goto done; + } + + if (ret == EOK) { + /* sbus_reply decreases the refcount of @reply. This usuall means that + * refcount drops to zero and the message is freed. However, under + * special circumstances the refcount is increased inside libdbus, + * the refcount will be 1 when we leave the function and we drop it + * to zero in talloc_free(state) later in this function. This will + * leave an invalid message to be send inside dbus connection and + * eventually crash. + * + * Increasing the refcount here makes sure that the refcount is always + * correct. */ + dbus_message_ref(reply); + sbus_reply(state->conn, reply); + } else { + sbus_errno_to_error(state, ret, &error_name, &error_msg); + sbus_reply_error(state->conn, state->message, error_name, error_msg); + } + +done: + if (ret == ERR_SBUS_KILL_CONNECTION) { + DEBUG(SSSDBG_TRACE_FUNC, "Handler requested to kill the connection!\n"); + sbus_connection_free(state->conn); + } + + talloc_free(state); + + sss_chain_id_set(old_chain_id); +} + +DBusHandlerResult +sbus_method_handler(struct sbus_connection *conn, + struct sbus_router *router, + struct sbus_message_meta *meta, + DBusMessage *message) +{ + const struct sbus_method *method; + struct sbus_interface *iface; + TALLOC_CTX *error_ctx; + const char *error_name; + const char *error_msg; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Received D-Bus method %s.%s on %s\n", + meta->interface, meta->member, meta->path); + + /* Mark this connection as active. */ + sbus_connection_mark_active(conn); + + iface = sbus_router_paths_lookup(router->paths, meta->path, + meta->interface); + if (iface == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown interface!\n"); + sbus_reply_error(conn, message, DBUS_ERROR_UNKNOWN_INTERFACE, + meta->interface); + return DBUS_HANDLER_RESULT_HANDLED; + } + + method = sbus_interface_find_method(iface, meta->member); + if (method == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unknown method!\n"); + sbus_reply_error(conn, message, DBUS_ERROR_UNKNOWN_METHOD, + meta->member); + return DBUS_HANDLER_RESULT_HANDLED; + } + + sbus_annotation_warn(iface, method); + + ret = sbus_issue_request(conn, meta, conn, message, SBUS_REQUEST_METHOD, + &method->invoker, &method->handler); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to issue request [%d]: %s\n", + ret, sss_strerror(ret)); + if (ret == ENOMEM) { + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + error_ctx = talloc_new(NULL); + if (error_ctx == NULL) { + return DBUS_HANDLER_RESULT_NEED_MEMORY; + } + + sbus_errno_to_error(error_ctx, ret, &error_name, &error_msg); + sbus_reply_error(conn, message, error_name, error_msg); + talloc_free(error_ctx); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusHandlerResult +sbus_signal_handler(struct sbus_connection *conn, + struct sbus_router *router, + struct sbus_message_meta *meta, + DBusMessage *message) +{ + struct sbus_listener_list *list; + struct sbus_listener_list *item; + errno_t ret; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Received D-Bus signal %s.%s on %s\n", + meta->interface, meta->member, meta->path); + + list = sbus_router_listeners_lookup(router->listeners, meta->interface, + meta->member); + if (list == NULL) { + /* Most probably not fully initialized yet */ + DEBUG(SSSDBG_FUNC_DATA, "We do not listen to this signal!\n"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + DLIST_FOR_EACH(item, list) { + ret = sbus_issue_request(conn, meta, conn, message, + SBUS_REQUEST_SIGNAL, + &item->listener->invoker, + &item->listener->handler); + if (ret != EOK) { + /* Nothing to do, try the next one. */ + DEBUG(SSSDBG_CRIT_FAILURE, "Unable to issue request [%d]: %s\n", + ret, sss_strerror(ret)); + } + } + + return DBUS_HANDLER_RESULT_HANDLED; +} + +DBusHandlerResult +sbus_router_filter(struct sbus_connection *conn, + struct sbus_router *router, + DBusMessage *message) +{ + struct sbus_message_meta meta; + + sbus_message_meta_read(message, &meta); + + switch (meta.type) { + case DBUS_MESSAGE_TYPE_SIGNAL: + return sbus_signal_handler(conn, router, &meta, message); + case DBUS_MESSAGE_TYPE_METHOD_CALL: + return sbus_method_handler(conn, router, &meta, message); + case DBUS_MESSAGE_TYPE_METHOD_RETURN: + case DBUS_MESSAGE_TYPE_ERROR: + /* This will be processed by the caller. */ + return DBUS_HANDLER_RESULT_HANDLED; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Invalid message type: %d\n", meta.type); + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +DBusHandlerResult +sbus_connection_filter(DBusConnection *dbus_conn, + DBusMessage *message, + void *handler_data) +{ + struct sbus_connection *conn; + + conn = talloc_get_type(handler_data, struct sbus_connection); + + return sbus_router_filter(conn, conn->router, message); +} diff --git a/src/sbus/router/sbus_router_hash.c b/src/sbus/router/sbus_router_hash.c new file mode 100644 index 0000000..2d407b2 --- /dev/null +++ b/src/sbus/router/sbus_router_hash.c @@ -0,0 +1,547 @@ +/* + Authors: + Pavel Březina <pbrezina@redhat.com> + + Copyright (C) 2017 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <dhash.h> +#include <string.h> +#include <talloc.h> + +#include "util/util.h" +#include "util/dlinklist.h" +#include "sbus/sbus_opath.h" +#include "sbus/sbus_private.h" +#include "util/sss_ptr_hash.h" + +static struct sbus_interface * +sbus_interface_list_lookup(struct sbus_interface_list *list, + const char *name) +{ + struct sbus_interface_list *item; + + DLIST_FOR_EACH(item, list) { + if (strcmp(item->interface->name, name) == 0) { + return item->interface; + } + } + + return NULL; +} + +static errno_t +sbus_interface_list_copy(TALLOC_CTX *mem_ctx, + struct sbus_interface_list *list, + struct sbus_interface_list **_copy) +{ + TALLOC_CTX *list_ctx; + struct sbus_interface_list *list_copy; + struct sbus_interface_list *item_copy; + struct sbus_interface_list *item; + struct sbus_interface *iface; + errno_t ret; + + if (list == NULL) { + *_copy = NULL; + return EOK; + } + + /* Create a memory context that will be used as a parent for copies. */ + list_ctx = talloc_new(mem_ctx); + if (list_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + /* Start with an empty list. */ + list_copy = NULL; + DLIST_FOR_EACH(item, list) { + iface = sbus_interface_list_lookup(list_copy, item->interface->name); + if (iface != NULL) { + /* This interface already exist in the list. */ + continue; + } + + /* Create a copy of this item and insert it into the list. */ + item_copy = talloc_zero(list_ctx, struct sbus_interface_list); + if (item_copy == NULL) { + ret = ENOMEM; + goto done; + } + + item_copy->interface = item->interface; + DLIST_ADD(list_copy, item_copy); + } + + *_copy = list_copy; + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(list_ctx); + } + + return ret; +} + +hash_table_t * +sbus_router_paths_init(TALLOC_CTX *mem_ctx) +{ + return sss_ptr_hash_create(mem_ctx, NULL, NULL); +} + +errno_t +sbus_router_paths_add(hash_table_t *table, + const char *path, + struct sbus_interface *iface) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_interface_list *list; + struct sbus_interface_list *item; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + item = talloc_zero(tmp_ctx, struct sbus_interface_list); + if (item == NULL) { + ret = ENOMEM; + goto done; + } + + item->interface = sbus_interface_copy(item, iface); + if (item->interface == NULL) { + ret = ENOMEM; + goto done; + } + + /* First, check if the path already exist and just append the interface + * to the list if it does (but only if the interface does not exist). */ + list = sss_ptr_hash_lookup(table, path, struct sbus_interface_list); + if (list != NULL) { + if (sbus_interface_list_lookup(list, iface->name) != NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Trying to register the same interface" + " twice: iface=%s, opath=%s\n", iface->name, path); + ret = EEXIST; + goto done; + } + + DLIST_ADD_END(list, item, struct sbus_interface_list *); + ret = EOK; + goto done; + } + + /* Otherwise create new hash entry and new list. */ + list = item; + + ret = sss_ptr_hash_add(table, path, list, struct sbus_interface_list); + +done: + if (ret == EOK) { + talloc_steal(table, item); + } + + talloc_free(tmp_ctx); + + return ret; +} + +/** + * First @object_path is looked up in @table, if it is not found it steps up + * in the path hierarchy and try to lookup the parent node. This continues + * until the root is reached. + */ +struct sbus_interface * +sbus_router_paths_lookup(hash_table_t *table, + const char *path, + const char *iface_name) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_interface_list *list; + struct sbus_interface *iface; + const char *lookup_path; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return NULL; + } + + iface = NULL; + lookup_path = path; + while (lookup_path != NULL) { + list = sss_ptr_hash_lookup(table, lookup_path, + struct sbus_interface_list); + if (list != NULL) { + iface = sbus_interface_list_lookup(list, iface_name); + if (iface != NULL) { + goto done; + } + } + + /* We will not free lookup path since it is freed with tmp_ctx + * and the object paths are supposed to be small. */ + lookup_path = sbus_opath_subtree_parent(tmp_ctx, lookup_path); + } + +done: + talloc_free(tmp_ctx); + return iface; +} + +/** + * Acquire list of all interfaces that are supported on given object path. + */ +errno_t +sbus_router_paths_supported(TALLOC_CTX *mem_ctx, + hash_table_t *table, + const char *path, + struct sbus_interface_list **_list) +{ + TALLOC_CTX *tmp_ctx; + TALLOC_CTX *list_ctx; + struct sbus_interface_list *list; + struct sbus_interface_list *list_copy; + struct sbus_interface_list *list_output; + const char *lookup_path; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return ENOMEM; + } + + list_ctx = talloc_new(tmp_ctx); + if (list_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + /* Start with an empty list. */ + list_output = NULL; + lookup_path = path; + while (lookup_path != NULL) { + list = sss_ptr_hash_lookup(table, lookup_path, + struct sbus_interface_list); + if (list != NULL) { + ret = sbus_interface_list_copy(list_ctx, list, &list_copy); + if (ret != EOK) { + goto done; + } + + DLIST_CONCATENATE(list_output, list_copy, + struct sbus_interface_list *); + } + + /* We will not free lookup path since it is freed with tmp_ctx + * and the object paths are supposed to be small. */ + lookup_path = sbus_opath_subtree_parent(tmp_ctx, lookup_path); + } + + talloc_steal(mem_ctx, list_ctx); + *_list = list_output; + ret = EOK; + +done: + talloc_free(tmp_ctx); + return ret; +} + +const char ** +sbus_router_paths_nodes(TALLOC_CTX *mem_ctx, + hash_table_t *table) +{ + const char **paths = NULL; + hash_key_t *keys; + unsigned long count; + unsigned long i, j; + char *basepath; + errno_t ret; + int hret; + + hret = hash_keys(table, &count, &keys); + if (hret != HASH_SUCCESS) { + return NULL; + } + + paths = talloc_zero_array(mem_ctx, const char *, count + 2); + if (paths == NULL) { + ret = ENOMEM; + goto done; + } + + for (i = 0, j = 0; i < count; i++) { + /* Do not include subtree paths. The must have node factory. */ + basepath = keys[i].str; + if (sbus_opath_is_subtree(basepath)) { + basepath = sbus_opath_subtree_base(paths, basepath); + if (basepath == NULL) { + ret = ENOMEM; + goto done; + } + + if (sbus_router_paths_exist(table, basepath)) { + talloc_free(basepath); + continue; + } + } + + if (strcmp(basepath, "/") == 0) { + continue; + } + + /* All paths starts with / that is not part of the node name. */ + paths[j] = basepath + 1; + j++; + } + + ret = EOK; + +done: + talloc_free(keys); + + if (ret != EOK) { + talloc_zfree(paths); + } + + return paths; +} + +bool +sbus_router_paths_exist(hash_table_t *table, + const char *object_path) +{ + return sss_ptr_hash_has_key(table, object_path); +} + +static struct sbus_listener * +sbus_listener_list_lookup(struct sbus_listener_list *list, + struct sbus_listener *a) +{ + struct sbus_listener_list *item; + struct sbus_listener *b; + + /* We know that interface and signal name already match. We need to check + * handlers and object paths. */ + DLIST_FOR_EACH(item, list) { + b = item->listener; + + if (memcmp(&a->handler, &b->handler, sizeof(struct sbus_handler)) != 0) { + continue; + } + + if (a->object_path == NULL && b->object_path == NULL) { + return b; + } + + if (a->object_path == NULL && b->object_path != NULL) { + continue; + } + + if (a->object_path != NULL && b->object_path == NULL) { + continue; + } + + if (strcmp(a->object_path, b->object_path) != 0) { + continue; + } + + return b; + } + + return NULL; +} + +static void +sbus_router_listeners_delete_cb(hash_entry_t *item, + hash_destroy_enum deltype, + void *pvt) +{ + struct sbus_connection *conn; + char *signal_name; + char *interface; + char *rule; + errno_t ret; + + conn = talloc_get_type(pvt, struct sbus_connection); + if (conn->connection == NULL) { + return; + } + + if (conn->disconnecting) { + return; + } + + /* If we still have the D-Bus connection available, we try to unregister + * the previously registered listener when its removed from table. */ + + ret = sbus_router_signal_parse(NULL, item->key.str, + &interface, &signal_name); + if (ret != EOK) { + /* There is nothing we can do. */ + return; + } + + rule = sbus_router_signal_rule(NULL, interface, signal_name); + talloc_free(interface); + talloc_free(signal_name); + if (rule == NULL) { + /* There is nothing we can do. */ + return; + } + + dbus_bus_remove_match(conn->connection, rule, NULL); + + talloc_free(rule); +} + +hash_table_t * +sbus_router_listeners_init(TALLOC_CTX *mem_ctx, + struct sbus_connection *conn) +{ + return sss_ptr_hash_create(mem_ctx, sbus_router_listeners_delete_cb, conn); +} + +errno_t +sbus_router_listeners_add(hash_table_t *table, + const char *interface, + const char *signal_name, + struct sbus_listener *listener, + bool *_signal_known) +{ + TALLOC_CTX *tmp_ctx; + struct sbus_listener_list *list; + struct sbus_listener_list *item; + bool signal_known = false; + const char *key; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory!\n"); + return ENOMEM; + } + + key = talloc_asprintf(tmp_ctx, "%s.%s", interface, signal_name); + if (key == NULL) { + ret = ENOMEM; + goto done; + } + + item = talloc_zero(tmp_ctx, struct sbus_listener_list); + if (item == NULL) { + ret = ENOMEM; + goto done; + } + + item->listener = sbus_listener_copy(item, listener); + if (item->listener == NULL) { + ret = ENOMEM; + goto done; + } + + /* First, check if the listener already exist and just append it to the + * list if it does (but only if this listener doesn't already exist. */ + list = sss_ptr_hash_lookup(table, key, struct sbus_listener_list); + if (list != NULL) { + signal_known = true; + + if (sbus_listener_list_lookup(list, listener) != NULL) { + DEBUG(SSSDBG_MINOR_FAILURE, "Trying to register the same listener" + " twice: iface=%s, signal=%s, path=%s\n", + interface, signal_name, (listener->object_path == NULL ? + "<null>": listener->object_path)); + ret = EEXIST; + goto done; + } + + DLIST_ADD_END(list, item, struct sbus_listener_list *); + ret = EOK; + goto done; + } + + /* Otherwise create new hash entry and new list. */ + signal_known = false; + list = item; + + ret = sss_ptr_hash_add(table, key, list, struct sbus_listener_list); + +done: + if (ret == EOK) { + talloc_steal(table, item); + *_signal_known = signal_known; + } + + talloc_free(tmp_ctx); + + return ret; +} + +struct sbus_listener_list * +sbus_router_listeners_lookup(hash_table_t *table, + const char *interface, + const char *signal_name) +{ + struct sbus_listener_list *list; + char *key; + + key = talloc_asprintf(NULL, "%s.%s", interface, signal_name); + if (key == NULL) { + return NULL; + } + + list = sss_ptr_hash_lookup(table, key, struct sbus_listener_list); + talloc_free(key); + + return list; +} + +hash_table_t * +sbus_router_nodes_init(TALLOC_CTX *mem_ctx) +{ + return sss_ptr_hash_create(mem_ctx, NULL, NULL); +} + +errno_t +sbus_router_nodes_add(hash_table_t *table, + struct sbus_node *node) +{ + struct sbus_node *copy; + errno_t ret; + + copy = sbus_node_copy(table, node); + if (copy == NULL) { + return ENOMEM; + } + + ret = sss_ptr_hash_add(table, copy->path, copy, struct sbus_node); + if (ret != EOK) { + talloc_free(copy); + return ret; + } + + return EOK; +} + +struct sbus_node * +sbus_router_nodes_lookup(hash_table_t *table, + const char *path) +{ + return sss_ptr_hash_lookup(table, path, struct sbus_node); +} |