static atomic_t fscache_object_debug_id = ATOMIC_INIT(0);
 
+#define fscache_cookie_hash_shift 15
+static struct hlist_bl_head fscache_cookie_hash[1 << fscache_cookie_hash_shift];
+
 static int fscache_acquire_non_index_cookie(struct fscache_cookie *cookie,
                                            loff_t object_size);
 static int fscache_alloc_object(struct fscache_cache *cache,
 static int fscache_attach_object(struct fscache_cookie *cookie,
                                 struct fscache_object *object);
 
+static void fscache_print_cookie(struct fscache_cookie *cookie, char prefix)
+{
+       struct hlist_node *object;
+       const u8 *k;
+       unsigned loop;
+
+       pr_err("%c-cookie c=%p [p=%p fl=%lx nc=%u na=%u]\n",
+              prefix, cookie, cookie->parent, cookie->flags,
+              atomic_read(&cookie->n_children),
+              atomic_read(&cookie->n_active));
+       pr_err("%c-cookie d=%p n=%p\n",
+              prefix, cookie->def, cookie->netfs_data);
+
+       object = READ_ONCE(cookie->backing_objects.first);
+       if (object)
+               pr_err("%c-cookie o=%p\n",
+                      prefix, hlist_entry(object, struct fscache_object, cookie_link));
+
+       pr_err("%c-key=[%u] '", prefix, cookie->key_len);
+       k = (cookie->key_len <= sizeof(cookie->inline_key)) ?
+               cookie->inline_key : cookie->key;
+       for (loop = 0; loop < cookie->key_len; loop++)
+               pr_cont("%02x", k[loop]);
+       pr_cont("'\n");
+}
+
+void fscache_free_cookie(struct fscache_cookie *cookie)
+{
+       if (cookie) {
+               BUG_ON(!hlist_empty(&cookie->backing_objects));
+               if (cookie->aux_len > sizeof(cookie->inline_aux))
+                       kfree(cookie->aux);
+               if (cookie->key_len > sizeof(cookie->inline_key))
+                       kfree(cookie->key);
+               kmem_cache_free(fscache_cookie_jar, cookie);
+       }
+}
+
 /*
  * initialise an cookie jar slab element prior to any use
  */
        INIT_HLIST_HEAD(&cookie->backing_objects);
 }
 
+/*
+ * Set the index key in a cookie.  The cookie struct has space for a 12-byte
+ * key plus length and hash, but if that's not big enough, it's instead a
+ * pointer to a buffer containing 3 bytes of hash, 1 byte of length and then
+ * the key data.
+ */
+static int fscache_set_key(struct fscache_cookie *cookie,
+                          const void *index_key, size_t index_key_len)
+{
+       unsigned long long h;
+       u32 *buf;
+       int i;
+
+       cookie->key_len = index_key_len;
+
+       if (index_key_len > sizeof(cookie->inline_key)) {
+               buf = kzalloc(index_key_len, GFP_KERNEL);
+               if (!buf)
+                       return -ENOMEM;
+               cookie->key = buf;
+       } else {
+               buf = (u32 *)cookie->inline_key;
+               buf[0] = 0;
+               buf[1] = 0;
+               buf[2] = 0;
+       }
+
+       memcpy(buf, index_key, index_key_len);
+
+       /* Calculate a hash and combine this with the length in the first word
+        * or first half word
+        */
+       h = (unsigned long)cookie->parent;
+       h += index_key_len + cookie->type;
+       for (i = 0; i < (index_key_len + sizeof(u32) - 1) / sizeof(u32); i++)
+               h += buf[i];
+
+       cookie->key_hash = h ^ (h >> 32);
+       return 0;
+}
+
+static long fscache_compare_cookie(const struct fscache_cookie *a,
+                                  const struct fscache_cookie *b)
+{
+       const void *ka, *kb;
+
+       if (a->key_hash != b->key_hash)
+               return (long)a->key_hash - (long)b->key_hash;
+       if (a->parent != b->parent)
+               return (long)a->parent - (long)b->parent;
+       if (a->key_len != b->key_len)
+               return (long)a->key_len - (long)b->key_len;
+       if (a->type != b->type)
+               return (long)a->type - (long)b->type;
+
+       if (a->key_len <= sizeof(a->inline_key)) {
+               ka = &a->inline_key;
+               kb = &b->inline_key;
+       } else {
+               ka = a->key;
+               kb = b->key;
+       }
+       return memcmp(ka, kb, a->key_len);
+}
+
+/*
+ * Allocate a cookie.
+ */
+struct fscache_cookie *fscache_alloc_cookie(
+       struct fscache_cookie *parent,
+       const struct fscache_cookie_def *def,
+       const void *index_key, size_t index_key_len,
+       const void *aux_data, size_t aux_data_len,
+       void *netfs_data,
+       loff_t object_size)
+{
+       struct fscache_cookie *cookie;
+
+       /* allocate and initialise a cookie */
+       cookie = kmem_cache_alloc(fscache_cookie_jar, GFP_KERNEL);
+       if (!cookie)
+               return NULL;
+
+       cookie->key_len = index_key_len;
+       cookie->aux_len = aux_data_len;
+
+       if (fscache_set_key(cookie, index_key, index_key_len) < 0)
+               goto nomem;
+
+       if (cookie->aux_len <= sizeof(cookie->inline_aux)) {
+               memcpy(cookie->inline_aux, aux_data, cookie->aux_len);
+       } else {
+               cookie->aux = kmemdup(aux_data, cookie->aux_len, GFP_KERNEL);
+               if (!cookie->aux)
+                       goto nomem;
+       }
+
+       atomic_set(&cookie->usage, 1);
+       atomic_set(&cookie->n_children, 0);
+
+       /* We keep the active count elevated until relinquishment to prevent an
+        * attempt to wake up every time the object operations queue quiesces.
+        */
+       atomic_set(&cookie->n_active, 1);
+
+       cookie->def             = def;
+       cookie->parent          = parent;
+       cookie->netfs_data      = netfs_data;
+       cookie->flags           = (1 << FSCACHE_COOKIE_NO_DATA_YET);
+       cookie->type            = def->type;
+
+       /* radix tree insertion won't use the preallocation pool unless it's
+        * told it may not wait */
+       INIT_RADIX_TREE(&cookie->stores, GFP_NOFS & ~__GFP_DIRECT_RECLAIM);
+       return cookie;
+
+nomem:
+       fscache_free_cookie(cookie);
+       return NULL;
+}
+
+/*
+ * Attempt to insert the new cookie into the hash.  If there's a collision, we
+ * return the old cookie if it's not in use and an error otherwise.
+ */
+struct fscache_cookie *fscache_hash_cookie(struct fscache_cookie *candidate)
+{
+       struct fscache_cookie *cursor;
+       struct hlist_bl_head *h;
+       struct hlist_bl_node *p;
+       unsigned int bucket;
+
+       bucket = candidate->key_hash & (ARRAY_SIZE(fscache_cookie_hash) - 1);
+       h = &fscache_cookie_hash[bucket];
+
+       hlist_bl_lock(h);
+       hlist_bl_for_each_entry(cursor, p, h, hash_link) {
+               if (fscache_compare_cookie(candidate, cursor) == 0)
+                       goto collision;
+       }
+
+       __set_bit(FSCACHE_COOKIE_ACQUIRED, &candidate->flags);
+       fscache_cookie_get(candidate->parent, fscache_cookie_get_acquire_parent);
+       atomic_inc(&candidate->parent->n_children);
+       hlist_bl_add_head(&candidate->hash_link, h);
+       hlist_bl_unlock(h);
+       return candidate;
+
+collision:
+       if (test_and_set_bit(FSCACHE_COOKIE_ACQUIRED, &cursor->flags)) {
+               trace_fscache_cookie(cursor, fscache_cookie_collision,
+                                    atomic_read(&cursor->usage));
+               pr_err("Duplicate cookie detected\n");
+               fscache_print_cookie(cursor, 'O');
+               fscache_print_cookie(candidate, 'N');
+               hlist_bl_unlock(h);
+               return NULL;
+       }
+
+       fscache_cookie_get(cursor, fscache_cookie_get_reacquire);
+       hlist_bl_unlock(h);
+       return cursor;
+}
+
 /*
  * request a cookie to represent an object (index, datafile, xattr, etc)
  * - parent specifies the parent object
        loff_t object_size,
        bool enable)
 {
-       struct fscache_cookie *cookie;
+       struct fscache_cookie *candidate, *cookie;
 
        BUG_ON(!def);
 
        BUG_ON(def->type == FSCACHE_COOKIE_TYPE_INDEX &&
               parent->type != FSCACHE_COOKIE_TYPE_INDEX);
 
-       /* allocate and initialise a cookie */
-       cookie = kmem_cache_alloc(fscache_cookie_jar, GFP_KERNEL);
-       if (!cookie) {
+       candidate = fscache_alloc_cookie(parent, def,
+                                        index_key, index_key_len,
+                                        aux_data, aux_data_len,
+                                        netfs_data, object_size);
+       if (!candidate) {
                fscache_stat(&fscache_n_acquires_oom);
                _leave(" [ENOMEM]");
                return NULL;
        }
 
-       cookie->key_len         = index_key_len;
-       cookie->aux_len         = aux_data_len;
-
-       if (cookie->key_len <= sizeof(cookie->inline_key)) {
-               memcpy(cookie->inline_key, index_key, cookie->key_len);
-       } else {
-               cookie->key = kmemdup(index_key, cookie->key_len, GFP_KERNEL);
-               if (!cookie->key)
-                       goto nomem;
-       }
-
-       if (cookie->aux_len <= sizeof(cookie->inline_aux)) {
-               memcpy(cookie->inline_aux, aux_data, cookie->aux_len);
-       } else {
-               cookie->aux = kmemdup(aux_data, cookie->aux_len, GFP_KERNEL);
-               if (!cookie->aux)
-                       goto nomem;
+       cookie = fscache_hash_cookie(candidate);
+       if (!cookie) {
+               trace_fscache_cookie(candidate, fscache_cookie_discard, 1);
+               goto out;
        }
 
-       atomic_set(&cookie->usage, 1);
-       atomic_set(&cookie->n_children, 0);
-
-       /* We keep the active count elevated until relinquishment to prevent an
-        * attempt to wake up every time the object operations queue quiesces.
-        */
-       atomic_set(&cookie->n_active, 1);
-
-       fscache_cookie_get(parent, fscache_cookie_get_acquire_parent);
-       atomic_inc(&parent->n_children);
-
-       cookie->def             = def;
-       cookie->parent          = parent;
-       cookie->netfs_data      = netfs_data;
-       cookie->flags           = (1 << FSCACHE_COOKIE_NO_DATA_YET);
-       cookie->type            = def->type;
-       
-       /* radix tree insertion won't use the preallocation pool unless it's
-        * told it may not wait */
-       INIT_RADIX_TREE(&cookie->stores, GFP_NOFS & ~__GFP_DIRECT_RECLAIM);
+       if (cookie == candidate)
+               candidate = NULL;
 
        switch (cookie->type) {
        case FSCACHE_COOKIE_TYPE_INDEX:
        }
 
        fscache_stat(&fscache_n_acquires_ok);
-       _leave(" = %p", cookie);
-       return cookie;
 
-nomem:
-       if (cookie->aux_len > sizeof(cookie->inline_aux))
-               kfree(cookie->aux);
-       if (cookie->key_len > sizeof(cookie->inline_key))
-               kfree(cookie->key);
-       kmem_cache_free(fscache_cookie_jar, cookie);
-       return NULL;
+out:
+       fscache_free_cookie(candidate);
+       return cookie;
 }
 EXPORT_SYMBOL(__fscache_acquire_cookie);
 
 }
 EXPORT_SYMBOL(__fscache_relinquish_cookie);
 
+/*
+ * Remove a cookie from the hash table.
+ */
+static void fscache_unhash_cookie(struct fscache_cookie *cookie)
+{
+       struct hlist_bl_head *h;
+       unsigned int bucket;
+
+       bucket = cookie->key_hash & (ARRAY_SIZE(fscache_cookie_hash) - 1);
+       h = &fscache_cookie_hash[bucket];
+
+       hlist_bl_lock(h);
+       hlist_bl_del(&cookie->hash_link);
+       hlist_bl_unlock(h);
+}
+
 /*
  * Drop a reference to a cookie.
  */
                BUG_ON(usage < 0);
 
                parent = cookie->parent;
-               BUG_ON(!hlist_empty(&cookie->backing_objects));
-               if (cookie->aux_len > sizeof(cookie->inline_aux))
-                       kfree(cookie->aux);
-               if (cookie->key_len > sizeof(cookie->inline_key))
-                       kfree(cookie->key);
-               kmem_cache_free(fscache_cookie_jar, cookie);
+               fscache_unhash_cookie(cookie);
+               fscache_free_cookie(cookie);
 
                cookie = parent;
                where = fscache_cookie_put_parent;
 
 #include <linux/slab.h>
 #include "internal.h"
 
-static LIST_HEAD(fscache_netfs_list);
-
 /*
  * register a network filesystem for caching
  */
 int __fscache_register_netfs(struct fscache_netfs *netfs)
 {
-       struct fscache_netfs *ptr;
-       struct fscache_cookie *cookie;
-       int ret;
+       struct fscache_cookie *candidate, *cookie;
 
        _enter("{%s}", netfs->name);
 
-       INIT_LIST_HEAD(&netfs->link);
-
        /* allocate a cookie for the primary index */
-       cookie = kmem_cache_zalloc(fscache_cookie_jar, GFP_KERNEL);
-
-       if (!cookie) {
+       candidate = fscache_alloc_cookie(&fscache_fsdef_index,
+                                        &fscache_fsdef_netfs_def,
+                                        netfs->name, strlen(netfs->name),
+                                        &netfs->version, sizeof(netfs->version),
+                                        netfs, 0);
+       if (!candidate) {
                _leave(" = -ENOMEM");
                return -ENOMEM;
        }
 
-       cookie->key_len = strlen(netfs->name);
-       if (cookie->key_len <= sizeof(cookie->inline_key)) {
-               memcpy(cookie->inline_key, netfs->name, strlen(netfs->name));
-       } else {
-               ret = -ENOMEM;
-               cookie->key = kmemdup(netfs->name, cookie->key_len, GFP_KERNEL);
-               if (!cookie->key)
-                       goto nomem;
-       }
-
-       cookie->aux_len = sizeof(netfs->version);
-       memcpy(cookie->inline_aux, &netfs->version, cookie->aux_len);
-
-       /* initialise the primary index cookie */
-       atomic_set(&cookie->usage, 1);
-       atomic_set(&cookie->n_children, 0);
-       atomic_set(&cookie->n_active, 1);
-
-       cookie->def             = &fscache_fsdef_netfs_def;
-       cookie->parent          = &fscache_fsdef_index;
-       cookie->netfs_data      = netfs;
-       cookie->flags           = 1 << FSCACHE_COOKIE_ENABLED;
-       cookie->type            = FSCACHE_COOKIE_TYPE_INDEX;
-
-       spin_lock_init(&cookie->lock);
-       spin_lock_init(&cookie->stores_lock);
-       INIT_HLIST_HEAD(&cookie->backing_objects);
+       candidate->flags = 1 << FSCACHE_COOKIE_ENABLED;
 
        /* check the netfs type is not already present */
-       down_write(&fscache_addremove_sem);
-
-       ret = -EEXIST;
-       list_for_each_entry(ptr, &fscache_netfs_list, link) {
-               if (strcmp(ptr->name, netfs->name) == 0)
-                       goto already_registered;
+       cookie = fscache_hash_cookie(candidate);
+       if (!cookie)
+               goto already_registered;
+       if (cookie != candidate) {
+               trace_fscache_cookie(candidate, fscache_cookie_discard, 1);
+               fscache_free_cookie(candidate);
        }
 
        fscache_cookie_get(cookie->parent, fscache_cookie_get_register_netfs);
        atomic_inc(&cookie->parent->n_children);
 
        netfs->primary_index = cookie;
-       list_add(&netfs->link, &fscache_netfs_list);
-       ret = 0;
 
        pr_notice("Netfs '%s' registered for caching\n", netfs->name);
        trace_fscache_netfs(netfs);
+       _leave(" = 0");
+       return 0;
 
 already_registered:
-       up_write(&fscache_addremove_sem);
-
-nomem:
-       if (ret < 0)
-               kmem_cache_free(fscache_cookie_jar, cookie);
-
-       _leave(" = %d", ret);
-       return ret;
+       fscache_cookie_put(candidate, fscache_cookie_put_dup_netfs);
+       _leave(" = -EEXIST");
+       return -EEXIST;
 }
 EXPORT_SYMBOL(__fscache_register_netfs);
 
 {
        _enter("{%s.%u}", netfs->name, netfs->version);
 
-       down_write(&fscache_addremove_sem);
-
-       list_del(&netfs->link);
        fscache_relinquish_cookie(netfs->primary_index, NULL, false);
-
-       up_write(&fscache_addremove_sem);
-
-       pr_notice("Netfs '%s' unregistered from caching\n",
-                 netfs->name);
+       pr_notice("Netfs '%s' unregistered from caching\n", netfs->name);
 
        _leave("");
 }