Commit 9549332d authored by David Howells's avatar David Howells
Browse files

fscache: Implement cache registration



Implement a register of caches and provide functions to manage it.

Two functions are provided for the cache backend to use:

 (1) Acquire a cache cookie:

	struct fscache_cache *fscache_acquire_cache(const char *name)

     This gets the cache cookie for a cache of the specified name and moves
     it to the preparation state.  If a nameless cache cookie exists, that
     will be given this name and used.

 (2) Relinquish a cache cookie:

	void fscache_relinquish_cache(struct fscache_cache *cache);

     This relinquishes a cache cookie, cleans it and makes it available if
     it's still referenced by a network filesystem.

Note that network filesystems don't deal with cache cookies directly, but
rather go straight to the volume registration.

Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
cc: linux-cachefs@redhat.com
Link: https://lore.kernel.org/r/163819587157.215744.13523139317322503286.stgit@warthog.procyon.org.uk/ # v1
Link: https://lore.kernel.org/r/163906889665.143852.10378009165231294456.stgit@warthog.procyon.org.uk/ # v2
Link: https://lore.kernel.org/r/163967085081.1823006.2218944206363626210.stgit@warthog.procyon.org.uk/ # v3
Link: https://lore.kernel.org/r/164021494847.640689.10109692261640524343.stgit@warthog.procyon.org.uk/ # v4
parent e8a07c9d
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -4,6 +4,7 @@
#
#


fscache-y := \
fscache-y := \
	cache.o \
	main.o
	main.o


fscache-$(CONFIG_PROC_FS) += proc.o
fscache-$(CONFIG_PROC_FS) += proc.o

fs/fscache/cache.c

0 → 100644
+274 −0
Original line number Original line Diff line number Diff line
// 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);

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->cache_priv = NULL;
	smp_store_release(&cache->state, FSCACHE_CACHE_IS_NOT_PRESENT);
	fscache_put_cache(cache, where);
}
EXPORT_SYMBOL(fscache_relinquish_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 */
+33 −0
Original line number Original line Diff line number Diff line
@@ -17,6 +17,39 @@
#include <linux/sched.h>
#include <linux/sched.h>
#include <linux/seq_file.h>
#include <linux/seq_file.h>


/*
 * cache.c
 */
#ifdef CONFIG_PROC_FS
extern const struct seq_operations fscache_caches_seq_ops;
#endif
struct fscache_cache *fscache_lookup_cache(const char *name, bool is_cache);
void fscache_put_cache(struct fscache_cache *cache, enum fscache_cache_trace where);

static inline enum fscache_cache_state fscache_cache_state(const struct fscache_cache *cache)
{
	return smp_load_acquire(&cache->state);
}

static inline bool fscache_cache_is_live(const struct fscache_cache *cache)
{
	return fscache_cache_state(cache) == FSCACHE_CACHE_IS_ACTIVE;
}

static inline void fscache_set_cache_state(struct fscache_cache *cache,
					   enum fscache_cache_state new_state)
{
	smp_store_release(&cache->state, new_state);

}

static inline bool fscache_set_cache_state_maybe(struct fscache_cache *cache,
						 enum fscache_cache_state old_state,
						 enum fscache_cache_state new_state)
{
	return try_cmpxchg_release(&cache->state, &old_state, new_state);
}

/*
/*
 * main.c
 * main.c
 */
 */
+4 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,10 @@ int __init fscache_proc_init(void)
	if (!proc_mkdir("fs/fscache", NULL))
	if (!proc_mkdir("fs/fscache", NULL))
		goto error_dir;
		goto error_dir;


	if (!proc_create_seq("fs/fscache/caches", S_IFREG | 0444, NULL,
			     &fscache_caches_seq_ops))
		goto error;

#ifdef CONFIG_FSCACHE_STATS
#ifdef CONFIG_FSCACHE_STATS
	if (!proc_create_single("fs/fscache/stats", S_IFREG | 0444, NULL,
	if (!proc_create_single("fs/fscache/stats", S_IFREG | 0444, NULL,
				fscache_stats_show))
				fscache_stats_show))
+34 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,40 @@


#include <linux/fscache.h>
#include <linux/fscache.h>


enum fscache_cache_trace;
enum fscache_access_trace;

enum fscache_cache_state {
	FSCACHE_CACHE_IS_NOT_PRESENT,	/* No cache is present for this name */
	FSCACHE_CACHE_IS_PREPARING,	/* A cache is preparing to come live */
	FSCACHE_CACHE_IS_ACTIVE,	/* Attached cache is active and can be used */
	FSCACHE_CACHE_GOT_IOERROR,	/* Attached cache stopped on I/O error */
	FSCACHE_CACHE_IS_WITHDRAWN,	/* Attached cache is being withdrawn */
#define NR__FSCACHE_CACHE_STATE (FSCACHE_CACHE_IS_WITHDRAWN + 1)
};

/*
 * Cache cookie.
 */
struct fscache_cache {
	struct list_head	cache_link;	/* Link in cache list */
	void			*cache_priv;	/* Private cache data (or NULL) */
	refcount_t		ref;
	atomic_t		n_volumes;	/* Number of active volumes; */
	atomic_t		n_accesses;	/* Number of in-progress accesses on the cache */
	atomic_t		object_count;	/* no. of live objects in this cache */
	unsigned int		debug_id;
	enum fscache_cache_state state;
	char			*name;
};

extern struct workqueue_struct *fscache_wq;
extern struct workqueue_struct *fscache_wq;


/*
 * out-of-line cache backend functions
 */
extern struct rw_semaphore fscache_addremove_sem;
extern struct fscache_cache *fscache_acquire_cache(const char *name);
extern void fscache_relinquish_cache(struct fscache_cache *cache);

#endif /* _LINUX_FSCACHE_CACHE_H */
#endif /* _LINUX_FSCACHE_CACHE_H */
Loading