429 lines
12 KiB
C
429 lines
12 KiB
C
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
/* FS-Cache cache handling
|
||
|
*
|
||
|
* Copyright (C) 2021 Red Hat, Inc. All Rights Reserved.
|
||
|
* Written by David Howells (dhowells@redhat.com)
|
||
|
*/
|
||
|
|
||
|
#define FSCACHE_DEBUG_LEVEL CACHE
|
||
|
#include <linux/export.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include "internal.h"
|
||
|
|
||
|
static LIST_HEAD(fscache_caches);
|
||
|
DECLARE_RWSEM(fscache_addremove_sem);
|
||
|
EXPORT_SYMBOL(fscache_addremove_sem);
|
||
|
DECLARE_WAIT_QUEUE_HEAD(fscache_clearance_waiters);
|
||
|
EXPORT_SYMBOL(fscache_clearance_waiters);
|
||
|
|
||
|
static atomic_t fscache_cache_debug_id;
|
||
|
|
||
|
/*
|
||
|
* Allocate a cache cookie.
|
||
|
*/
|
||
|
static struct fscache_cache *fscache_alloc_cache(const char *name)
|
||
|
{
|
||
|
struct fscache_cache *cache;
|
||
|
|
||
|
cache = kzalloc(sizeof(*cache), GFP_KERNEL);
|
||
|
if (cache) {
|
||
|
if (name) {
|
||
|
cache->name = kstrdup(name, GFP_KERNEL);
|
||
|
if (!cache->name) {
|
||
|
kfree(cache);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
refcount_set(&cache->ref, 1);
|
||
|
INIT_LIST_HEAD(&cache->cache_link);
|
||
|
cache->debug_id = atomic_inc_return(&fscache_cache_debug_id);
|
||
|
}
|
||
|
return cache;
|
||
|
}
|
||
|
|
||
|
static bool fscache_get_cache_maybe(struct fscache_cache *cache,
|
||
|
enum fscache_cache_trace where)
|
||
|
{
|
||
|
bool success;
|
||
|
int ref;
|
||
|
|
||
|
success = __refcount_inc_not_zero(&cache->ref, &ref);
|
||
|
if (success)
|
||
|
trace_fscache_cache(cache->debug_id, ref + 1, where);
|
||
|
return success;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Look up a cache cookie.
|
||
|
*/
|
||
|
struct fscache_cache *fscache_lookup_cache(const char *name, bool is_cache)
|
||
|
{
|
||
|
struct fscache_cache *candidate, *cache, *unnamed = NULL;
|
||
|
|
||
|
/* firstly check for the existence of the cache under read lock */
|
||
|
down_read(&fscache_addremove_sem);
|
||
|
|
||
|
list_for_each_entry(cache, &fscache_caches, cache_link) {
|
||
|
if (cache->name && name && strcmp(cache->name, name) == 0 &&
|
||
|
fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
|
||
|
goto got_cache_r;
|
||
|
if (!cache->name && !name &&
|
||
|
fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
|
||
|
goto got_cache_r;
|
||
|
}
|
||
|
|
||
|
if (!name) {
|
||
|
list_for_each_entry(cache, &fscache_caches, cache_link) {
|
||
|
if (cache->name &&
|
||
|
fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
|
||
|
goto got_cache_r;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
up_read(&fscache_addremove_sem);
|
||
|
|
||
|
/* the cache does not exist - create a candidate */
|
||
|
candidate = fscache_alloc_cache(name);
|
||
|
if (!candidate)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
/* write lock, search again and add if still not present */
|
||
|
down_write(&fscache_addremove_sem);
|
||
|
|
||
|
list_for_each_entry(cache, &fscache_caches, cache_link) {
|
||
|
if (cache->name && name && strcmp(cache->name, name) == 0 &&
|
||
|
fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
|
||
|
goto got_cache_w;
|
||
|
if (!cache->name) {
|
||
|
unnamed = cache;
|
||
|
if (!name &&
|
||
|
fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
|
||
|
goto got_cache_w;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (unnamed && is_cache &&
|
||
|
fscache_get_cache_maybe(unnamed, fscache_cache_get_acquire))
|
||
|
goto use_unnamed_cache;
|
||
|
|
||
|
if (!name) {
|
||
|
list_for_each_entry(cache, &fscache_caches, cache_link) {
|
||
|
if (cache->name &&
|
||
|
fscache_get_cache_maybe(cache, fscache_cache_get_acquire))
|
||
|
goto got_cache_w;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
list_add_tail(&candidate->cache_link, &fscache_caches);
|
||
|
trace_fscache_cache(candidate->debug_id,
|
||
|
refcount_read(&candidate->ref),
|
||
|
fscache_cache_new_acquire);
|
||
|
up_write(&fscache_addremove_sem);
|
||
|
return candidate;
|
||
|
|
||
|
got_cache_r:
|
||
|
up_read(&fscache_addremove_sem);
|
||
|
return cache;
|
||
|
use_unnamed_cache:
|
||
|
cache = unnamed;
|
||
|
cache->name = candidate->name;
|
||
|
candidate->name = NULL;
|
||
|
got_cache_w:
|
||
|
up_write(&fscache_addremove_sem);
|
||
|
kfree(candidate->name);
|
||
|
kfree(candidate);
|
||
|
return cache;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fscache_acquire_cache - Acquire a cache-level cookie.
|
||
|
* @name: The name of the cache.
|
||
|
*
|
||
|
* Get a cookie to represent an actual cache. If a name is given and there is
|
||
|
* a nameless cache record available, this will acquire that and set its name,
|
||
|
* directing all the volumes using it to this cache.
|
||
|
*
|
||
|
* The cache will be switched over to the preparing state if not currently in
|
||
|
* use, otherwise -EBUSY will be returned.
|
||
|
*/
|
||
|
struct fscache_cache *fscache_acquire_cache(const char *name)
|
||
|
{
|
||
|
struct fscache_cache *cache;
|
||
|
|
||
|
ASSERT(name);
|
||
|
cache = fscache_lookup_cache(name, true);
|
||
|
if (IS_ERR(cache))
|
||
|
return cache;
|
||
|
|
||
|
if (!fscache_set_cache_state_maybe(cache,
|
||
|
FSCACHE_CACHE_IS_NOT_PRESENT,
|
||
|
FSCACHE_CACHE_IS_PREPARING)) {
|
||
|
pr_warn("Cache tag %s in use\n", name);
|
||
|
fscache_put_cache(cache, fscache_cache_put_cache);
|
||
|
return ERR_PTR(-EBUSY);
|
||
|
}
|
||
|
|
||
|
return cache;
|
||
|
}
|
||
|
EXPORT_SYMBOL(fscache_acquire_cache);
|
||
|
|
||
|
/**
|
||
|
* fscache_put_cache - Release a cache-level cookie.
|
||
|
* @cache: The cache cookie to be released
|
||
|
* @where: An indication of where the release happened
|
||
|
*
|
||
|
* Release the caller's reference on a cache-level cookie. The @where
|
||
|
* indication should give information about the circumstances in which the call
|
||
|
* occurs and will be logged through a tracepoint.
|
||
|
*/
|
||
|
void fscache_put_cache(struct fscache_cache *cache,
|
||
|
enum fscache_cache_trace where)
|
||
|
{
|
||
|
unsigned int debug_id = cache->debug_id;
|
||
|
bool zero;
|
||
|
int ref;
|
||
|
|
||
|
if (IS_ERR_OR_NULL(cache))
|
||
|
return;
|
||
|
|
||
|
zero = __refcount_dec_and_test(&cache->ref, &ref);
|
||
|
trace_fscache_cache(debug_id, ref - 1, where);
|
||
|
|
||
|
if (zero) {
|
||
|
down_write(&fscache_addremove_sem);
|
||
|
list_del_init(&cache->cache_link);
|
||
|
up_write(&fscache_addremove_sem);
|
||
|
kfree(cache->name);
|
||
|
kfree(cache);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fscache_relinquish_cache - Reset cache state and release cookie
|
||
|
* @cache: The cache cookie to be released
|
||
|
*
|
||
|
* Reset the state of a cache and release the caller's reference on a cache
|
||
|
* cookie.
|
||
|
*/
|
||
|
void fscache_relinquish_cache(struct fscache_cache *cache)
|
||
|
{
|
||
|
enum fscache_cache_trace where =
|
||
|
(cache->state == FSCACHE_CACHE_IS_PREPARING) ?
|
||
|
fscache_cache_put_prep_failed :
|
||
|
fscache_cache_put_relinquish;
|
||
|
|
||
|
cache->ops = NULL;
|
||
|
cache->cache_priv = NULL;
|
||
|
fscache_set_cache_state(cache, FSCACHE_CACHE_IS_NOT_PRESENT);
|
||
|
fscache_put_cache(cache, where);
|
||
|
}
|
||
|
EXPORT_SYMBOL(fscache_relinquish_cache);
|
||
|
|
||
|
/**
|
||
|
* fscache_add_cache - Declare a cache as being open for business
|
||
|
* @cache: The cache-level cookie representing the cache
|
||
|
* @ops: Table of cache operations to use
|
||
|
* @cache_priv: Private data for the cache record
|
||
|
*
|
||
|
* Add a cache to the system, making it available for netfs's to use.
|
||
|
*
|
||
|
* See Documentation/filesystems/caching/backend-api.rst for a complete
|
||
|
* description.
|
||
|
*/
|
||
|
int fscache_add_cache(struct fscache_cache *cache,
|
||
|
const struct fscache_cache_ops *ops,
|
||
|
void *cache_priv)
|
||
|
{
|
||
|
int n_accesses;
|
||
|
|
||
|
_enter("{%s,%s}", ops->name, cache->name);
|
||
|
|
||
|
BUG_ON(fscache_cache_state(cache) != FSCACHE_CACHE_IS_PREPARING);
|
||
|
|
||
|
/* Get a ref on the cache cookie and keep its n_accesses counter raised
|
||
|
* by 1 to prevent wakeups from transitioning it to 0 until we're
|
||
|
* withdrawing caching services from it.
|
||
|
*/
|
||
|
n_accesses = atomic_inc_return(&cache->n_accesses);
|
||
|
trace_fscache_access_cache(cache->debug_id, refcount_read(&cache->ref),
|
||
|
n_accesses, fscache_access_cache_pin);
|
||
|
|
||
|
down_write(&fscache_addremove_sem);
|
||
|
|
||
|
cache->ops = ops;
|
||
|
cache->cache_priv = cache_priv;
|
||
|
fscache_set_cache_state(cache, FSCACHE_CACHE_IS_ACTIVE);
|
||
|
|
||
|
up_write(&fscache_addremove_sem);
|
||
|
pr_notice("Cache \"%s\" added (type %s)\n", cache->name, ops->name);
|
||
|
_leave(" = 0 [%s]", cache->name);
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(fscache_add_cache);
|
||
|
|
||
|
/**
|
||
|
* fscache_begin_cache_access - Pin a cache so it can be accessed
|
||
|
* @cache: The cache-level cookie
|
||
|
* @why: An indication of the circumstances of the access for tracing
|
||
|
*
|
||
|
* Attempt to pin the cache to prevent it from going away whilst we're
|
||
|
* accessing it and returns true if successful. This works as follows:
|
||
|
*
|
||
|
* (1) If the cache tests as not live (state is not FSCACHE_CACHE_IS_ACTIVE),
|
||
|
* then we return false to indicate access was not permitted.
|
||
|
*
|
||
|
* (2) If the cache tests as live, then we increment the n_accesses count and
|
||
|
* then recheck the liveness, ending the access if it ceased to be live.
|
||
|
*
|
||
|
* (3) When we end the access, we decrement n_accesses and wake up the any
|
||
|
* waiters if it reaches 0.
|
||
|
*
|
||
|
* (4) Whilst the cache is caching, n_accesses is kept artificially
|
||
|
* incremented to prevent wakeups from happening.
|
||
|
*
|
||
|
* (5) When the cache is taken offline, the state is changed to prevent new
|
||
|
* accesses, n_accesses is decremented and we wait for n_accesses to
|
||
|
* become 0.
|
||
|
*/
|
||
|
bool fscache_begin_cache_access(struct fscache_cache *cache, enum fscache_access_trace why)
|
||
|
{
|
||
|
int n_accesses;
|
||
|
|
||
|
if (!fscache_cache_is_live(cache))
|
||
|
return false;
|
||
|
|
||
|
n_accesses = atomic_inc_return(&cache->n_accesses);
|
||
|
smp_mb__after_atomic(); /* Reread live flag after n_accesses */
|
||
|
trace_fscache_access_cache(cache->debug_id, refcount_read(&cache->ref),
|
||
|
n_accesses, why);
|
||
|
if (!fscache_cache_is_live(cache)) {
|
||
|
fscache_end_cache_access(cache, fscache_access_unlive);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fscache_end_cache_access - Unpin a cache at the end of an access.
|
||
|
* @cache: The cache-level cookie
|
||
|
* @why: An indication of the circumstances of the access for tracing
|
||
|
*
|
||
|
* Unpin a cache after we've accessed it. The @why indicator is merely
|
||
|
* provided for tracing purposes.
|
||
|
*/
|
||
|
void fscache_end_cache_access(struct fscache_cache *cache, enum fscache_access_trace why)
|
||
|
{
|
||
|
int n_accesses;
|
||
|
|
||
|
smp_mb__before_atomic();
|
||
|
n_accesses = atomic_dec_return(&cache->n_accesses);
|
||
|
trace_fscache_access_cache(cache->debug_id, refcount_read(&cache->ref),
|
||
|
n_accesses, why);
|
||
|
if (n_accesses == 0)
|
||
|
wake_up_var(&cache->n_accesses);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* fscache_io_error - Note a cache I/O error
|
||
|
* @cache: The record describing the cache
|
||
|
*
|
||
|
* Note that an I/O error occurred in a cache and that it should no longer be
|
||
|
* used for anything. This also reports the error into the kernel log.
|
||
|
*
|
||
|
* See Documentation/filesystems/caching/backend-api.rst for a complete
|
||
|
* description.
|
||
|
*/
|
||
|
void fscache_io_error(struct fscache_cache *cache)
|
||
|
{
|
||
|
if (fscache_set_cache_state_maybe(cache,
|
||
|
FSCACHE_CACHE_IS_ACTIVE,
|
||
|
FSCACHE_CACHE_GOT_IOERROR))
|
||
|
pr_err("Cache '%s' stopped due to I/O error\n",
|
||
|
cache->name);
|
||
|
}
|
||
|
EXPORT_SYMBOL(fscache_io_error);
|
||
|
|
||
|
/**
|
||
|
* fscache_withdraw_cache - Withdraw a cache from the active service
|
||
|
* @cache: The cache cookie
|
||
|
*
|
||
|
* Begin the process of withdrawing a cache from service. This stops new
|
||
|
* cache-level and volume-level accesses from taking place and waits for
|
||
|
* currently ongoing cache-level accesses to end.
|
||
|
*/
|
||
|
void fscache_withdraw_cache(struct fscache_cache *cache)
|
||
|
{
|
||
|
int n_accesses;
|
||
|
|
||
|
pr_notice("Withdrawing cache \"%s\" (%u objs)\n",
|
||
|
cache->name, atomic_read(&cache->object_count));
|
||
|
|
||
|
fscache_set_cache_state(cache, FSCACHE_CACHE_IS_WITHDRAWN);
|
||
|
|
||
|
/* Allow wakeups on dec-to-0 */
|
||
|
n_accesses = atomic_dec_return(&cache->n_accesses);
|
||
|
trace_fscache_access_cache(cache->debug_id, refcount_read(&cache->ref),
|
||
|
n_accesses, fscache_access_cache_unpin);
|
||
|
|
||
|
wait_var_event(&cache->n_accesses,
|
||
|
atomic_read(&cache->n_accesses) == 0);
|
||
|
}
|
||
|
EXPORT_SYMBOL(fscache_withdraw_cache);
|
||
|
|
||
|
#ifdef CONFIG_PROC_FS
|
||
|
static const char fscache_cache_states[NR__FSCACHE_CACHE_STATE] = "-PAEW";
|
||
|
|
||
|
/*
|
||
|
* Generate a list of caches in /proc/fs/fscache/caches
|
||
|
*/
|
||
|
static int fscache_caches_seq_show(struct seq_file *m, void *v)
|
||
|
{
|
||
|
struct fscache_cache *cache;
|
||
|
|
||
|
if (v == &fscache_caches) {
|
||
|
seq_puts(m,
|
||
|
"CACHE REF VOLS OBJS ACCES S NAME\n"
|
||
|
"======== ===== ===== ===== ===== = ===============\n"
|
||
|
);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
cache = list_entry(v, struct fscache_cache, cache_link);
|
||
|
seq_printf(m,
|
||
|
"%08x %5d %5d %5d %5d %c %s\n",
|
||
|
cache->debug_id,
|
||
|
refcount_read(&cache->ref),
|
||
|
atomic_read(&cache->n_volumes),
|
||
|
atomic_read(&cache->object_count),
|
||
|
atomic_read(&cache->n_accesses),
|
||
|
fscache_cache_states[cache->state],
|
||
|
cache->name ?: "-");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void *fscache_caches_seq_start(struct seq_file *m, loff_t *_pos)
|
||
|
__acquires(fscache_addremove_sem)
|
||
|
{
|
||
|
down_read(&fscache_addremove_sem);
|
||
|
return seq_list_start_head(&fscache_caches, *_pos);
|
||
|
}
|
||
|
|
||
|
static void *fscache_caches_seq_next(struct seq_file *m, void *v, loff_t *_pos)
|
||
|
{
|
||
|
return seq_list_next(v, &fscache_caches, _pos);
|
||
|
}
|
||
|
|
||
|
static void fscache_caches_seq_stop(struct seq_file *m, void *v)
|
||
|
__releases(fscache_addremove_sem)
|
||
|
{
|
||
|
up_read(&fscache_addremove_sem);
|
||
|
}
|
||
|
|
||
|
const struct seq_operations fscache_caches_seq_ops = {
|
||
|
.start = fscache_caches_seq_start,
|
||
|
.next = fscache_caches_seq_next,
|
||
|
.stop = fscache_caches_seq_stop,
|
||
|
.show = fscache_caches_seq_show,
|
||
|
};
|
||
|
#endif /* CONFIG_PROC_FS */
|