summaryrefslogtreecommitdiffstats
path: root/src/sbus/router
diff options
context:
space:
mode:
Diffstat (limited to 'src/sbus/router')
-rw-r--r--src/sbus/router/sbus_router.c387
-rw-r--r--src/sbus/router/sbus_router_handler.c324
-rw-r--r--src/sbus/router/sbus_router_hash.c547
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);
+}