From 17287e82bb882736bbb15e2423d14d007bb32624 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pavel=20B=C5=99ezina?= <pbrezina@redhat.com>
Date: Thu, 11 Dec 2014 22:23:32 +0100
Subject: [PATCH 04/13] sbus: add sbus_opath_hash

Add a hash table to store registered object paths and their
interfaces. It also provides function to add new entry and
lookup an interface in this table.

When an entry or the table itself is destroyed, registered
object path is unregistered through delete callback.

Preparation for:
https://fedorahosted.org/sssd/ticket/2339
---
 src/sbus/sssd_dbus_interface.c | 241 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 241 insertions(+)

diff --git a/src/sbus/sssd_dbus_interface.c b/src/sbus/sssd_dbus_interface.c
index cd41c4f29f700d010836a45c73092a0eb16ec9f2..e1275377b4afaf299c20adeff64896b46e9b53cf 100644
--- a/src/sbus/sssd_dbus_interface.c
+++ b/src/sbus/sssd_dbus_interface.c
@@ -20,6 +20,7 @@
 
 #include <talloc.h>
 #include <dbus/dbus.h>
+#include <dhash.h>
 
 #include "util/util.h"
 #include "sbus/sssd_dbus.h"
@@ -28,6 +29,26 @@
 DBusObjectPathVTable dbus_object_path_vtable =
     { NULL, sbus_message_handler, NULL, NULL, NULL, NULL };
 
+struct sbus_interface_list {
+    struct sbus_interface_list *prev, *next;
+    struct sbus_interface *interface;
+};
+
+static struct sbus_interface *
+sbus_iface_list_lookup(struct sbus_interface_list *list,
+                       const char *iface)
+{
+    struct sbus_interface_list *item;
+
+    DLIST_FOR_EACH(item, list) {
+        if (strcmp(item->interface->vtable->meta->name, iface) == 0) {
+            return item->interface;
+        }
+    }
+
+    return NULL;
+}
+
 static bool path_in_interface_list(struct sbus_interface_p *list,
                                    const char *path)
 {
@@ -121,6 +142,42 @@ static char *sbus_opath_get_base_path(TALLOC_CTX *mem_ctx,
     return tree_path;
 }
 
+static char *sbus_opath_march_up(TALLOC_CTX *mem_ctx,
+                                 const char *path)
+{
+    char *subtree;
+    char *slash;
+
+    /* first remove /~* from the end, stop when we have reached the root i.e.
+     * subtree == "/" */
+    subtree = sbus_opath_get_base_path(mem_ctx, path);
+    if (subtree == NULL || subtree[1] == '\0') {
+        return NULL;
+    }
+
+    /* Find the first separator and replace the part with asterisk. */
+    slash = strrchr(subtree, '/');
+    if (slash == NULL) {
+        /* we cannot continue up */
+        talloc_free(subtree);
+        return NULL;
+    }
+
+    if (*(slash + 1) == '\0') {
+        /* this object path is invalid since it cannot end with slash */
+        DEBUG(SSSDBG_CRIT_FAILURE, "Invalid object path '%s'?\n", path);
+        talloc_free(subtree);
+        return NULL;
+    }
+
+    /* because object path cannot end with / there is enough space for
+     * asterisk and terminating zero */
+    *(slash + 1) = '*';
+    *(slash + 2) = '\0';
+
+    return subtree;
+}
+
 bool sbus_iface_handles_path(struct sbus_interface_p *intf_p,
                                     const char *path)
 {
@@ -131,6 +188,190 @@ bool sbus_iface_handles_path(struct sbus_interface_p *intf_p,
     return strcmp(path, intf_p->intf->path) == 0;
 }
 
+static void
+sbus_opath_hash_delete_cb(hash_entry_t *item,
+                          hash_destroy_enum deltype,
+                          void *pvt)
+{
+    struct sbus_connection *conn;
+    char *path;
+
+    conn = talloc_get_type(pvt, struct sbus_connection);
+    path = sbus_opath_get_base_path(NULL, item->key.str);
+
+    dbus_connection_unregister_object_path(conn->dbus.conn, path);
+}
+
+static errno_t
+sbus_opath_hash_init(TALLOC_CTX *mem_ctx,
+                     struct sbus_connection *conn,
+                     hash_table_t **_table)
+{
+    return sss_hash_create_ex(mem_ctx, 10, _table, 0, 0, 0, 0,
+                              sbus_opath_hash_delete_cb, conn);
+}
+
+static errno_t
+sbus_opath_hash_add_iface(hash_table_t *table,
+                          const char *object_path,
+                          struct sbus_interface *iface,
+                          bool *_path_known)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct sbus_interface_list *list = NULL;
+    struct sbus_interface_list *item = NULL;
+    const char *iface_name = iface->vtable->meta->name;
+    hash_key_t key;
+    hash_value_t value;
+    errno_t ret;
+    int hret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "Registering interface %s with path %s\n",
+          iface_name, object_path);
+
+    /* create new list item */
+
+    item = talloc_zero(tmp_ctx, struct sbus_interface_list);
+    if (item == NULL) {
+        return ENOMEM;
+    }
+
+    item->interface = iface;
+
+    /* first lookup existing list in hash table */
+
+    key.type = HASH_KEY_STRING;
+    key.str = talloc_strdup(tmp_ctx, object_path);
+    if (key.str == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    hret = hash_lookup(table, &key, &value);
+    if (hret == HASH_SUCCESS) {
+        /* This object path has already some interface registered. We will
+         * check for existence of the interface currently being added and
+         * add it if missing. */
+
+        *_path_known = true;
+
+        list = talloc_get_type(value.ptr, struct sbus_interface_list);
+        if (sbus_iface_list_lookup(list, iface_name) != NULL) {
+            DEBUG(SSSDBG_MINOR_FAILURE, "Trying to register the same interface"
+                  " twice: iface=%s, opath=%s\n", iface_name, object_path);
+            ret = EEXIST;
+            goto done;
+        }
+
+        DLIST_ADD_END(list, item, struct sbus_interface_list *);
+        ret = EOK;
+        goto done;
+    } else if (hret != HASH_ERROR_KEY_NOT_FOUND) {
+        ret = EIO;
+        goto done;
+    }
+
+    /* otherwise create new hash entry */
+
+    *_path_known = false;
+    DLIST_ADD(list, item);
+
+    value.type = HASH_VALUE_PTR;
+    value.ptr = list;
+
+    hret = hash_enter(table, &key, &value);
+    if (hret != HASH_SUCCESS) {
+        ret = EIO;
+        goto done;
+    }
+
+    talloc_steal(table, key.str);
+    ret = EOK;
+
+done:
+    if (ret == EOK) {
+        talloc_steal(item, iface);
+        talloc_steal(table, item);
+    } else {
+        talloc_free(item);
+    }
+
+    return ret;
+}
+
+static bool
+sbus_opath_hash_has_path(hash_table_t *table,
+                         const char *object_path)
+{
+    hash_key_t key;
+
+    key.type = HASH_KEY_STRING;
+    key.str = discard_const(object_path);
+
+    return hash_has_key(table, &key);
+}
+
+/**
+ * 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.
+ */
+static struct sbus_interface *
+sbus_opath_hash_lookup_iface(hash_table_t *table,
+                             const char *object_path,
+                             const char *iface_name)
+{
+    TALLOC_CTX *tmp_ctx = NULL;
+    struct sbus_interface_list *list = NULL;
+    struct sbus_interface *iface = NULL;
+    char *lookup_path = NULL;
+    hash_key_t key;
+    hash_value_t value;
+    int hret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return NULL;
+    }
+
+    lookup_path = talloc_strdup(tmp_ctx, object_path);
+    if (lookup_path == NULL) {
+        goto done;
+    }
+
+    while (lookup_path != NULL) {
+        key.type = HASH_KEY_STRING;
+        key.str = lookup_path;
+
+        hret = hash_lookup(table, &key, &value);
+        if (hret == HASH_SUCCESS) {
+            list = talloc_get_type(value.ptr, struct sbus_interface_list);
+            iface = sbus_iface_list_lookup(list, iface_name);
+            if (iface != NULL) {
+                goto done;
+            }
+        } else if (hret != HASH_ERROR_KEY_NOT_FOUND) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "Unable to search hash table: hret=%d\n", hret);
+            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_march_up(tmp_ctx, lookup_path);
+    }
+
+done:
+    talloc_free(tmp_ctx);
+    return iface;
+}
+
 static struct sbus_interface *
 sbus_new_interface(TALLOC_CTX *mem_ctx,
                    const char *object_path,
-- 
1.7.11.7

