|  | // 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; | 
|  | 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_FS | 
|  | static 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 */ |