429 lines · 12.1 KB · C
RawDownload
// 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_obj(*cache);	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;	bool zero;	int ref; 	if (IS_ERR_OR_NULL(cache))		return; 	debug_id = cache->debug_id;	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_FSstatic const char fscache_cache_states[NR__FSCACHE_CACHE_STATE] __nonstring = "-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 */