Skip to content

Commit

Permalink
fscache: Maintain a catalogue of allocated cookies
Browse files Browse the repository at this point in the history
Maintain a catalogue of allocated cookies so that cookie collisions can be
handled properly.  For the moment, this just involves printing a warning
and returning a NULL cookie to the caller of fscache_acquire_cookie(), but
in future it might make sense to wait for the old cookie to finish being
cleaned up.

This requires the cookie key to be stored attached to the cookie so that we
still have the key available if the netfs relinquishes the cookie.  This is
done by an earlier patch.

The catalogue also renders redundant fscache_netfs_list (used for checking
for duplicates), so that can be removed.

Signed-off-by: David Howells <[email protected]>
Acked-by: Anna Schumaker <[email protected]>
Tested-by: Steve Dickson <[email protected]>
  • Loading branch information
dhowells committed Apr 6, 2018
1 parent ee1235a commit ec0328e
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 119 deletions.
294 changes: 238 additions & 56 deletions fs/fscache/cookie.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,54 @@ struct kmem_cache *fscache_cookie_jar;

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,
struct fscache_cookie *cookie);
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
*/
Expand All @@ -41,6 +82,170 @@ void fscache_cookie_init_once(void *_cookie)
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
Expand All @@ -65,7 +270,7 @@ struct fscache_cookie *__fscache_acquire_cookie(
loff_t object_size,
bool enable)
{
struct fscache_cookie *cookie;
struct fscache_cookie *candidate, *cookie;

BUG_ON(!def);

Expand Down Expand Up @@ -95,53 +300,24 @@ struct fscache_cookie *__fscache_acquire_cookie(
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:
Expand Down Expand Up @@ -178,16 +354,10 @@ struct fscache_cookie *__fscache_acquire_cookie(
}

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);

Expand Down Expand Up @@ -678,6 +848,22 @@ void __fscache_relinquish_cookie(struct fscache_cookie *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.
*/
Expand All @@ -698,12 +884,8 @@ void fscache_cookie_put(struct fscache_cookie *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;
Expand Down
7 changes: 7 additions & 0 deletions fs/fscache/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,14 @@ extern struct fscache_cache *fscache_select_cache_for_object(
*/
extern struct kmem_cache *fscache_cookie_jar;

extern void fscache_free_cookie(struct fscache_cookie *);
extern void fscache_cookie_init_once(void *);
extern struct fscache_cookie *fscache_alloc_cookie(struct fscache_cookie *,
const struct fscache_cookie_def *,
const void *, size_t,
const void *, size_t,
void *, loff_t);
extern struct fscache_cookie *fscache_hash_cookie(struct fscache_cookie *);
extern void fscache_cookie_put(struct fscache_cookie *,
enum fscache_cookie_trace);

Expand Down
Loading

0 comments on commit ec0328e

Please sign in to comment.