linux/fs/fscache/volume.c
David Howells 62ab633523 fscache: Implement volume registration
Add functions to the fscache API to allow volumes to be acquired and
relinquished by the network filesystem.  A volume is an index of data
storage cache objects.  A volume is represented by a volume cookie in the
API.  A filesystem would typically create a volume for a superblock and
then create per-inode cookies within it.

To request a volume, the filesystem calls:

	struct fscache_volume *
	fscache_acquire_volume(const char *volume_key,
			       const char *cache_name,
			       const void *coherency_data,
			       size_t coherency_len)

The volume_key is a printable string used to match the volume in the cache.
It should not contain any '/' characters.  For AFS, for example, this would
be "afs,<cellname>,<volume_id>", e.g. "afs,example.com,523001".

The cache_name can be NULL, but if not it should be a string indicating the
name of the cache to use if there's more than one available.

The coherency data, if given, is an arbitrarily-sized blob that's attached
to the volume and is compared when the volume is looked up.  If it doesn't
match, the old volume is judged to be out of date and it and everything
within it is discarded.

Acquiring a volume twice concurrently is disallowed, though the function
will wait if an old volume cookie is being relinquishing.


When a network filesystem has finished with a volume, it should return the
volume cookie by calling:

	void
	fscache_relinquish_volume(struct fscache_volume *volume,
				  const void *coherency_data,
				  bool invalidate)

If invalidate is true, the entire volume will be discarded; if false, the
volume will be synced and the coherency data will be updated.

Changes
=======
ver #4:
 - Removed an extraneous param from kdoc on fscache_relinquish_volume()[3].

ver #3:
 - fscache_hash()'s size parameter is now in bytes.  Use __le32 as the unit
   to round up to.
 - When comparing cookies, simply see if the attributes are the same rather
   than subtracting them to produce a strcmp-style return[2].
 - Make the coherency data an arbitrary blob rather than a u64, but don't
   store it for the moment.

ver #2:
 - Fix error check[1].
 - Make a fscache_acquire_volume() return errors, including EBUSY if a
   conflicting volume cookie already exists.  No error is printed now -
   that's left to the netfs.

Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
Link: https://lore.kernel.org/r/20211203095608.GC2480@kili/ [1]
Link: https://lore.kernel.org/r/CAHk-=whtkzB446+hX0zdLsdcUJsJ=8_-0S1mE_R+YurThfUbLA@mail.gmail.com/ [2]
Link: https://lore.kernel.org/r/20211220224646.30e8205c@canb.auug.org.au/ [3]
Link: https://lore.kernel.org/r/163819588944.215744.1629085755564865996.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/163906890630.143852.13972180614535611154.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967086836.1823006.8191672796841981763.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021495816.640689.4403156093668590217.stgit@warthog.procyon.org.uk/ # v4
2022-01-07 09:22:19 +00:00

341 lines
9.0 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/* Volume-level cache cookie handling.
*
* Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#define FSCACHE_DEBUG_LEVEL COOKIE
#include <linux/export.h>
#include <linux/slab.h>
#include "internal.h"
#define fscache_volume_hash_shift 10
static struct hlist_bl_head fscache_volume_hash[1 << fscache_volume_hash_shift];
static atomic_t fscache_volume_debug_id;
static LIST_HEAD(fscache_volumes);
struct fscache_volume *fscache_get_volume(struct fscache_volume *volume,
enum fscache_volume_trace where)
{
int ref;
__refcount_inc(&volume->ref, &ref);
trace_fscache_volume(volume->debug_id, ref + 1, where);
return volume;
}
static void fscache_see_volume(struct fscache_volume *volume,
enum fscache_volume_trace where)
{
int ref = refcount_read(&volume->ref);
trace_fscache_volume(volume->debug_id, ref, where);
}
static bool fscache_volume_same(const struct fscache_volume *a,
const struct fscache_volume *b)
{
size_t klen;
if (a->key_hash != b->key_hash ||
a->cache != b->cache ||
a->key[0] != b->key[0])
return false;
klen = round_up(a->key[0] + 1, sizeof(__le32));
return memcmp(a->key, b->key, klen) == 0;
}
static bool fscache_is_acquire_pending(struct fscache_volume *volume)
{
return test_bit(FSCACHE_VOLUME_ACQUIRE_PENDING, &volume->flags);
}
static void fscache_wait_on_volume_collision(struct fscache_volume *candidate,
unsigned int collidee_debug_id)
{
wait_var_event_timeout(&candidate->flags,
fscache_is_acquire_pending(candidate), 20 * HZ);
if (!fscache_is_acquire_pending(candidate)) {
pr_notice("Potential volume collision new=%08x old=%08x",
candidate->debug_id, collidee_debug_id);
fscache_stat(&fscache_n_volumes_collision);
wait_var_event(&candidate->flags, fscache_is_acquire_pending(candidate));
}
}
/*
* Attempt to insert the new volume into the hash. If there's a collision, we
* wait for the old volume to complete if it's being relinquished and an error
* otherwise.
*/
static bool fscache_hash_volume(struct fscache_volume *candidate)
{
struct fscache_volume *cursor;
struct hlist_bl_head *h;
struct hlist_bl_node *p;
unsigned int bucket, collidee_debug_id = 0;
bucket = candidate->key_hash & (ARRAY_SIZE(fscache_volume_hash) - 1);
h = &fscache_volume_hash[bucket];
hlist_bl_lock(h);
hlist_bl_for_each_entry(cursor, p, h, hash_link) {
if (fscache_volume_same(candidate, cursor)) {
if (!test_bit(FSCACHE_VOLUME_RELINQUISHED, &cursor->flags))
goto collision;
fscache_see_volume(cursor, fscache_volume_get_hash_collision);
set_bit(FSCACHE_VOLUME_COLLIDED_WITH, &cursor->flags);
set_bit(FSCACHE_VOLUME_ACQUIRE_PENDING, &candidate->flags);
collidee_debug_id = cursor->debug_id;
break;
}
}
hlist_bl_add_head(&candidate->hash_link, h);
hlist_bl_unlock(h);
if (test_bit(FSCACHE_VOLUME_ACQUIRE_PENDING, &candidate->flags))
fscache_wait_on_volume_collision(candidate, collidee_debug_id);
return true;
collision:
fscache_see_volume(cursor, fscache_volume_collision);
hlist_bl_unlock(h);
return false;
}
/*
* Allocate and initialise a volume representation cookie.
*/
static struct fscache_volume *fscache_alloc_volume(const char *volume_key,
const char *cache_name,
const void *coherency_data,
size_t coherency_len)
{
struct fscache_volume *volume;
struct fscache_cache *cache;
size_t klen, hlen;
char *key;
cache = fscache_lookup_cache(cache_name, false);
if (IS_ERR(cache))
return NULL;
volume = kzalloc(sizeof(*volume), GFP_KERNEL);
if (!volume)
goto err_cache;
volume->cache = cache;
INIT_LIST_HEAD(&volume->proc_link);
INIT_WORK(&volume->work, NULL /* PLACEHOLDER */);
refcount_set(&volume->ref, 1);
spin_lock_init(&volume->lock);
/* Stick the length on the front of the key and pad it out to make
* hashing easier.
*/
klen = strlen(volume_key);
hlen = round_up(1 + klen + 1, sizeof(__le32));
key = kzalloc(hlen, GFP_KERNEL);
if (!key)
goto err_vol;
key[0] = klen;
memcpy(key + 1, volume_key, klen);
volume->key = key;
volume->key_hash = fscache_hash(0, key, hlen);
volume->debug_id = atomic_inc_return(&fscache_volume_debug_id);
down_write(&fscache_addremove_sem);
atomic_inc(&cache->n_volumes);
list_add_tail(&volume->proc_link, &fscache_volumes);
fscache_see_volume(volume, fscache_volume_new_acquire);
fscache_stat(&fscache_n_volumes);
up_write(&fscache_addremove_sem);
_leave(" = v=%x", volume->debug_id);
return volume;
err_vol:
kfree(volume);
err_cache:
fscache_put_cache(cache, fscache_cache_put_alloc_volume);
fscache_stat(&fscache_n_volumes_nomem);
return NULL;
}
/*
* Acquire a volume representation cookie and link it to a (proposed) cache.
*/
struct fscache_volume *__fscache_acquire_volume(const char *volume_key,
const char *cache_name,
const void *coherency_data,
size_t coherency_len)
{
struct fscache_volume *volume;
volume = fscache_alloc_volume(volume_key, cache_name,
coherency_data, coherency_len);
if (!volume)
return ERR_PTR(-ENOMEM);
if (!fscache_hash_volume(volume)) {
fscache_put_volume(volume, fscache_volume_put_hash_collision);
return ERR_PTR(-EBUSY);
}
// PLACEHOLDER: Create the volume if we have a cache available
return volume;
}
EXPORT_SYMBOL(__fscache_acquire_volume);
static void fscache_wake_pending_volume(struct fscache_volume *volume,
struct hlist_bl_head *h)
{
struct fscache_volume *cursor;
struct hlist_bl_node *p;
hlist_bl_for_each_entry(cursor, p, h, hash_link) {
if (fscache_volume_same(cursor, volume)) {
fscache_see_volume(cursor, fscache_volume_see_hash_wake);
clear_bit(FSCACHE_VOLUME_ACQUIRE_PENDING, &cursor->flags);
wake_up_bit(&cursor->flags, FSCACHE_VOLUME_ACQUIRE_PENDING);
return;
}
}
}
/*
* Remove a volume cookie from the hash table.
*/
static void fscache_unhash_volume(struct fscache_volume *volume)
{
struct hlist_bl_head *h;
unsigned int bucket;
bucket = volume->key_hash & (ARRAY_SIZE(fscache_volume_hash) - 1);
h = &fscache_volume_hash[bucket];
hlist_bl_lock(h);
hlist_bl_del(&volume->hash_link);
if (test_bit(FSCACHE_VOLUME_COLLIDED_WITH, &volume->flags))
fscache_wake_pending_volume(volume, h);
hlist_bl_unlock(h);
}
/*
* Drop a cache's volume attachments.
*/
static void fscache_free_volume(struct fscache_volume *volume)
{
struct fscache_cache *cache = volume->cache;
if (volume->cache_priv) {
// PLACEHOLDER: Detach any attached cache
}
down_write(&fscache_addremove_sem);
list_del_init(&volume->proc_link);
atomic_dec(&volume->cache->n_volumes);
up_write(&fscache_addremove_sem);
if (!hlist_bl_unhashed(&volume->hash_link))
fscache_unhash_volume(volume);
trace_fscache_volume(volume->debug_id, 0, fscache_volume_free);
kfree(volume->key);
kfree(volume);
fscache_stat_d(&fscache_n_volumes);
fscache_put_cache(cache, fscache_cache_put_volume);
}
/*
* Drop a reference to a volume cookie.
*/
void fscache_put_volume(struct fscache_volume *volume,
enum fscache_volume_trace where)
{
if (volume) {
unsigned int debug_id = volume->debug_id;
bool zero;
int ref;
zero = __refcount_dec_and_test(&volume->ref, &ref);
trace_fscache_volume(debug_id, ref - 1, where);
if (zero)
fscache_free_volume(volume);
}
}
/*
* Relinquish a volume representation cookie.
*/
void __fscache_relinquish_volume(struct fscache_volume *volume,
const void *coherency_data,
bool invalidate)
{
if (WARN_ON(test_and_set_bit(FSCACHE_VOLUME_RELINQUISHED, &volume->flags)))
return;
if (invalidate)
set_bit(FSCACHE_VOLUME_INVALIDATE, &volume->flags);
fscache_put_volume(volume, fscache_volume_put_relinquish);
}
EXPORT_SYMBOL(__fscache_relinquish_volume);
#ifdef CONFIG_PROC_FS
/*
* Generate a list of volumes in /proc/fs/fscache/volumes
*/
static int fscache_volumes_seq_show(struct seq_file *m, void *v)
{
struct fscache_volume *volume;
if (v == &fscache_volumes) {
seq_puts(m,
"VOLUME REF nCOOK ACC FL CACHE KEY\n"
"======== ===== ===== === == =============== ================\n");
return 0;
}
volume = list_entry(v, struct fscache_volume, proc_link);
seq_printf(m,
"%08x %5d %5d %3d %02lx %-15.15s %s\n",
volume->debug_id,
refcount_read(&volume->ref),
atomic_read(&volume->n_cookies),
atomic_read(&volume->n_accesses),
volume->flags,
volume->cache->name ?: "-",
volume->key + 1);
return 0;
}
static void *fscache_volumes_seq_start(struct seq_file *m, loff_t *_pos)
__acquires(&fscache_addremove_sem)
{
down_read(&fscache_addremove_sem);
return seq_list_start_head(&fscache_volumes, *_pos);
}
static void *fscache_volumes_seq_next(struct seq_file *m, void *v, loff_t *_pos)
{
return seq_list_next(v, &fscache_volumes, _pos);
}
static void fscache_volumes_seq_stop(struct seq_file *m, void *v)
__releases(&fscache_addremove_sem)
{
up_read(&fscache_addremove_sem);
}
const struct seq_operations fscache_volumes_seq_ops = {
.start = fscache_volumes_seq_start,
.next = fscache_volumes_seq_next,
.stop = fscache_volumes_seq_stop,
.show = fscache_volumes_seq_show,
};
#endif /* CONFIG_PROC_FS */