123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- // 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 ([email protected])
- */
- #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);
- static void fscache_create_volume_work(struct work_struct *work);
- 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);
- }
- /*
- * Pin the cache behind a volume so that we can access it.
- */
- static void __fscache_begin_volume_access(struct fscache_volume *volume,
- struct fscache_cookie *cookie,
- enum fscache_access_trace why)
- {
- int n_accesses;
- n_accesses = atomic_inc_return(&volume->n_accesses);
- smp_mb__after_atomic();
- trace_fscache_access_volume(volume->debug_id, cookie ? cookie->debug_id : 0,
- refcount_read(&volume->ref),
- n_accesses, why);
- }
- /**
- * fscache_begin_volume_access - Pin a cache so a volume can be accessed
- * @volume: The volume cookie
- * @cookie: A datafile cookie for a tracing reference (or NULL)
- * @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 a volume 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 volume's n_accesses
- * count and then recheck the cache liveness, ending the access if it
- * ceased to be live.
- *
- * (3) When we end the access, we decrement the volume's n_accesses and wake
- * up the any waiters if it reaches 0.
- *
- * (4) Whilst the cache is caching, the volume's 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, the volume's n_accesses is decremented and we wait for it to
- * become 0.
- *
- * The datafile @cookie and the @why indicator are merely provided for tracing
- * purposes.
- */
- bool fscache_begin_volume_access(struct fscache_volume *volume,
- struct fscache_cookie *cookie,
- enum fscache_access_trace why)
- {
- if (!fscache_cache_is_live(volume->cache))
- return false;
- __fscache_begin_volume_access(volume, cookie, why);
- if (!fscache_cache_is_live(volume->cache)) {
- fscache_end_volume_access(volume, cookie, fscache_access_unlive);
- return false;
- }
- return true;
- }
- /**
- * fscache_end_volume_access - Unpin a cache at the end of an access.
- * @volume: The volume cookie
- * @cookie: A datafile cookie for a tracing reference (or NULL)
- * @why: An indication of the circumstances of the access for tracing
- *
- * Unpin a cache volume after we've accessed it. The datafile @cookie and the
- * @why indicator are merely provided for tracing purposes.
- */
- void fscache_end_volume_access(struct fscache_volume *volume,
- struct fscache_cookie *cookie,
- enum fscache_access_trace why)
- {
- int n_accesses;
- smp_mb__before_atomic();
- n_accesses = atomic_dec_return(&volume->n_accesses);
- trace_fscache_access_volume(volume->debug_id, cookie ? cookie->debug_id : 0,
- refcount_read(&volume->ref),
- n_accesses, why);
- if (n_accesses == 0)
- wake_up_var(&volume->n_accesses);
- }
- EXPORT_SYMBOL(fscache_end_volume_access);
- 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_on_bit_timeout(&candidate->flags, FSCACHE_VOLUME_ACQUIRE_PENDING,
- TASK_UNINTERRUPTIBLE, 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_on_bit(&candidate->flags, FSCACHE_VOLUME_ACQUIRE_PENDING,
- TASK_UNINTERRUPTIBLE);
- }
- }
- /*
- * 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 (fscache_is_acquire_pending(candidate))
- 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;
- u8 *key;
- klen = strlen(volume_key);
- if (klen > NAME_MAX)
- return NULL;
- if (!coherency_data)
- coherency_len = 0;
- cache = fscache_lookup_cache(cache_name, false);
- if (IS_ERR(cache))
- return NULL;
- volume = kzalloc(struct_size(volume, coherency, coherency_len),
- GFP_KERNEL);
- if (!volume)
- goto err_cache;
- volume->cache = cache;
- volume->coherency_len = coherency_len;
- if (coherency_data)
- memcpy(volume->coherency, coherency_data, coherency_len);
- INIT_LIST_HEAD(&volume->proc_link);
- INIT_WORK(&volume->work, fscache_create_volume_work);
- 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.
- */
- 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;
- }
- /*
- * Create a volume's representation on disk. Have a volume ref and a cache
- * access we have to release.
- */
- static void fscache_create_volume_work(struct work_struct *work)
- {
- const struct fscache_cache_ops *ops;
- struct fscache_volume *volume =
- container_of(work, struct fscache_volume, work);
- fscache_see_volume(volume, fscache_volume_see_create_work);
- ops = volume->cache->ops;
- if (ops->acquire_volume)
- ops->acquire_volume(volume);
- fscache_end_cache_access(volume->cache,
- fscache_access_acquire_volume_end);
- clear_and_wake_up_bit(FSCACHE_VOLUME_CREATING, &volume->flags);
- fscache_put_volume(volume, fscache_volume_put_create_work);
- }
- /*
- * Dispatch a worker thread to create a volume's representation on disk.
- */
- void fscache_create_volume(struct fscache_volume *volume, bool wait)
- {
- if (test_and_set_bit(FSCACHE_VOLUME_CREATING, &volume->flags))
- goto maybe_wait;
- if (volume->cache_priv)
- goto no_wait; /* We raced */
- if (!fscache_begin_cache_access(volume->cache,
- fscache_access_acquire_volume))
- goto no_wait;
- fscache_get_volume(volume, fscache_volume_get_create_work);
- if (!schedule_work(&volume->work))
- fscache_put_volume(volume, fscache_volume_put_create_work);
- maybe_wait:
- if (wait) {
- fscache_see_volume(volume, fscache_volume_wait_create_work);
- wait_on_bit(&volume->flags, FSCACHE_VOLUME_CREATING,
- TASK_UNINTERRUPTIBLE);
- }
- return;
- no_wait:
- clear_bit_unlock(FSCACHE_VOLUME_CREATING, &volume->flags);
- wake_up_bit(&volume->flags, FSCACHE_VOLUME_CREATING);
- }
- /*
- * 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);
- }
- fscache_create_volume(volume, false);
- 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_and_wake_up_bit(FSCACHE_VOLUME_ACQUIRE_PENDING,
- &cursor->flags);
- 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) {
- __fscache_begin_volume_access(volume, NULL,
- fscache_access_relinquish_volume);
- if (volume->cache_priv)
- cache->ops->free_volume(volume);
- fscache_end_volume_access(volume, NULL,
- fscache_access_relinquish_volume_end);
- }
- 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);
- } else if (coherency_data) {
- memcpy(volume->coherency, coherency_data, volume->coherency_len);
- }
- fscache_put_volume(volume, fscache_volume_put_relinquish);
- }
- EXPORT_SYMBOL(__fscache_relinquish_volume);
- /**
- * fscache_withdraw_volume - Withdraw a volume from being cached
- * @volume: Volume cookie
- *
- * Withdraw a cache volume from service, waiting for all accesses to complete
- * before returning.
- */
- void fscache_withdraw_volume(struct fscache_volume *volume)
- {
- int n_accesses;
- _debug("withdraw V=%x", volume->debug_id);
- /* Allow wakeups on dec-to-0 */
- n_accesses = atomic_dec_return(&volume->n_accesses);
- trace_fscache_access_volume(volume->debug_id, 0,
- refcount_read(&volume->ref),
- n_accesses, fscache_access_cache_unpin);
- wait_var_event(&volume->n_accesses,
- atomic_read(&volume->n_accesses) == 0);
- }
- EXPORT_SYMBOL(fscache_withdraw_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 */
|