
From: David Howells <dhowells@redhat.com>

The attached patch splits the generic part of CacheFS out as FS-Cache - a
general cache manager.

FS-Cache now mediates between cache backends (such as CacheFS) and network
filesystems:

	+---------+
	|         |                        +-----------+
	|   NFS   |--+                     |           |
	|         |  |                 +-->|  CacheFS  |
	+---------+  |   +----------+  |   | /dev/hda5 |
	             |   |          |  |   +-----------+
	+---------+  +-->|          |  |
	|         |      |          |--+   +-------------+
	|   AFS   |----->| FS-Cache |      |             |
	|         |      |          |----->| Cache Files |
	+---------+  +-->|          |      | /var/cache  |
	             |   |          |--+   +-------------+
	+---------+  |   +----------+  |
	|         |  |                 |   +-------------+
	|  ISOFS  |--+                 |   |             |
	|         |                    +-->| ReiserCache |
	+---------+                        | /           |
	                                   +-------------+

This will allow cache backends other than CacheFS to be added to the system
without any need to change the netfs's that use FS-Cache.

Signed-Off-By: David Howells <dhowells@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/fs/Kconfig                    |   15 
 25-akpm/fs/Makefile                   |    1 
 25-akpm/fs/fscache/Makefile           |   13 
 25-akpm/fs/fscache/cookie.c           |  973 ++++++++++++++++++++++++++++++++++
 25-akpm/fs/fscache/fscache-int.h      |   81 ++
 25-akpm/fs/fscache/fsdef.c            |   90 +++
 25-akpm/fs/fscache/main.c             |  111 +++
 25-akpm/fs/fscache/page.c             |  231 ++++++++
 25-akpm/include/linux/fscache-cache.h |  205 +++++++
 25-akpm/include/linux/fscache.h       |  357 ++++++++++++
 10 files changed, 2075 insertions(+), 2 deletions(-)

diff -puN /dev/null fs/fscache/cookie.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/fs/fscache/cookie.c	2004-11-03 20:25:57.310037632 -0800
@@ -0,0 +1,973 @@
+/* cookie.c: general filesystem cache cookie management
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include "fscache-int.h"
+
+LIST_HEAD(fscache_netfs_list);
+LIST_HEAD(fscache_cache_list);
+DECLARE_RWSEM(fscache_addremove_sem);
+
+kmem_cache_t *fscache_cookie_jar;
+
+static void fscache_withdraw_node(struct fscache_cache *cache,
+				  struct fscache_node *node);
+
+/*****************************************************************************/
+/*
+ * register a network filesystem for caching
+ */
+int __fscache_register_netfs(struct fscache_netfs *netfs,
+			     struct fscache_index_def *primary_idef)
+{
+	struct fscache_netfs *ptr;
+	int ret;
+
+	_enter("{%s}", netfs->name);
+
+	INIT_LIST_HEAD(&netfs->link);
+
+	/* allocate a cookie for the primary index */
+	netfs->primary_index =
+		kmem_cache_alloc(fscache_cookie_jar, SLAB_KERNEL);
+
+	if (!netfs->primary_index) {
+		_leave(" = -ENOMEM");
+		return -ENOMEM;
+	}
+
+	/* initialise the primary index cookie */
+	memset(netfs->primary_index, 0, sizeof(*netfs->primary_index));
+
+	atomic_set(&netfs->primary_index->usage, 1);
+	atomic_set(&netfs->primary_index->children, 0);
+
+	netfs->primary_index->idef		= primary_idef;
+	netfs->primary_index->iparent		= &fscache_fsdef_index;
+	netfs->primary_index->netfs		= netfs;
+	netfs->primary_index->netfs_data	= netfs;
+
+	atomic_inc(&netfs->primary_index->iparent->usage);
+	atomic_inc(&netfs->primary_index->iparent->children);
+
+	rwlock_init(&netfs->primary_index->lock);
+	init_rwsem(&netfs->primary_index->sem);
+	INIT_LIST_HEAD(&netfs->primary_index->search_results);
+	INIT_LIST_HEAD(&netfs->primary_index->backing_nodes);
+
+	/* check the netfs type is not already present */
+	down_write(&fscache_addremove_sem);
+
+	ret = -EEXIST;
+	list_for_each_entry(ptr, &fscache_netfs_list, link) {
+		if (strcmp(ptr->name, netfs->name) == 0)
+			goto already_registered;
+	}
+
+	list_add(&netfs->link, &fscache_netfs_list);
+	ret = 0;
+
+	printk("FS-Cache: netfs '%s' registered for caching\n", netfs->name);
+
+ already_registered:
+	up_write(&fscache_addremove_sem);
+
+	if (ret < 0) {
+		netfs->primary_index->iparent = NULL;
+		__fscache_cookie_put(netfs->primary_index);
+		netfs->primary_index = NULL;
+	}
+
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end __fscache_register_netfs() */
+
+EXPORT_SYMBOL(__fscache_register_netfs);
+
+/*****************************************************************************/
+/*
+ * unregister a network filesystem from the cache
+ * - all cookies must have been released first
+ */
+void __fscache_unregister_netfs(struct fscache_netfs *netfs)
+{
+	_enter("{%s.%u}", netfs->name, netfs->version);
+
+	down_write(&fscache_addremove_sem);
+
+	list_del(&netfs->link);
+	fscache_relinquish_cookie(netfs->primary_index, 0);
+
+	up_write(&fscache_addremove_sem);
+
+	printk("FS-Cache: netfs '%s' unregistered from caching\n", netfs->name);
+
+	_leave("");
+
+} /* end __fscache_unregister_netfs() */
+
+EXPORT_SYMBOL(__fscache_unregister_netfs);
+
+/*****************************************************************************/
+/*
+ * initialise a cache record
+ */
+void fscache_init_cache(struct fscache_cache *cache,
+			struct fscache_cache_ops *ops,
+			unsigned fsdef_ino,
+			const char *idfmt,
+			...)
+{
+	va_list va;
+
+	memset(cache, 0, sizeof(*cache));
+
+	cache->ops = ops;
+
+	va_start(va, idfmt);
+	vsnprintf(cache->identifier, sizeof(cache->identifier), idfmt, va);
+	va_end(va);
+
+	INIT_LIST_HEAD(&cache->link);
+	INIT_LIST_HEAD(&cache->node_list);
+	spin_lock_init(&cache->node_list_lock);
+
+	INIT_LIST_HEAD(&cache->fsdef_srch.link);
+	cache->fsdef_srch.cache = cache;
+	cache->fsdef_srch.ino = fsdef_ino;
+
+} /* end fscache_init_cache() */
+
+EXPORT_SYMBOL(fscache_init_cache);
+
+/*****************************************************************************/
+/*
+ * declare a mounted cache as being open for business
+ */
+void fscache_add_cache(struct fscache_cache *cache)
+{
+	struct fscache_node *ifsdef;
+
+	BUG_ON(!cache->ops);
+
+	_enter("{%s.%s}", cache->ops->name, cache->identifier);
+
+	/* prepare an active-node record for the FSDEF index of this cache */
+	ifsdef = cache->ops->lookup_node(cache, cache->fsdef_srch.ino);
+	BUG_ON(IS_ERR(ifsdef));	/* there shouldn't be an error as FSDEF is the
+				 * root dir of the FS and so should already be
+				 * in core */
+
+	if (!cache->ops->grab_node(ifsdef))
+		BUG();
+
+	ifsdef->cookie = &fscache_fsdef_index;
+
+	down_write(&fscache_addremove_sem);
+
+	/* add the cache to the list */
+	list_add(&cache->link, &fscache_cache_list);
+
+	/* add the cache's netfs definition index node to the cache's
+	 * list */
+	spin_lock(&cache->node_list_lock);
+	list_add_tail(&ifsdef->cache_link, &cache->node_list);
+	spin_unlock(&cache->node_list_lock);
+
+	/* add the cache's netfs definition index node to the top level index
+	 * cookie as a known backing node */
+	down_write(&fscache_fsdef_index.sem);
+
+	list_add_tail(&cache->fsdef_srch.link,
+		      &fscache_fsdef_index.search_results);
+	list_add_tail(&ifsdef->cookie_link,
+		      &fscache_fsdef_index.backing_nodes);
+
+	atomic_inc(&fscache_fsdef_index.usage);
+
+	/* done */
+	up_write(&fscache_fsdef_index.sem);
+	up_write(&fscache_addremove_sem);
+	_leave("");
+
+} /* end fscache_add_cache() */
+
+EXPORT_SYMBOL(fscache_add_cache);
+
+/*****************************************************************************/
+/*
+ * withdraw an unmounted cache from the active service
+ */
+void fscache_withdraw_cache(struct fscache_cache *cache)
+{
+	struct fscache_node *node;
+
+	_enter("");
+
+	/* make the cache unavailable for cookie acquisition */
+	set_bit(FSCACHE_CACHE_WITHDRAWN, &cache->flags);
+
+	down_write(&fscache_addremove_sem);
+	list_del_init(&cache->link);
+	up_write(&fscache_addremove_sem);
+
+	/* mark all nodes as being withdrawn */
+	spin_lock(&cache->node_list_lock);
+	list_for_each_entry(node, &cache->node_list, cache_link) {
+		set_bit(FSCACHE_NODE_WITHDRAWN, &node->flags);
+	}
+	spin_unlock(&cache->node_list_lock);
+
+	/* make sure all pages pinned by operations on behalf of the netfs are
+	 * written to disc */
+	cache->ops->sync(cache);
+
+	/* dissociate all the netfs pages backed by this cache from the block
+	 * mappings in the cache */
+	cache->ops->dissociate_pages(cache);
+
+	/* we now have to destroy all the active nodes pertaining to this
+	 * cache */
+	spin_lock(&cache->node_list_lock);
+
+	while (!list_empty(&cache->node_list)) {
+		node = list_entry(cache->node_list.next, struct fscache_node,
+				  cache_link);
+		list_del(&node->cache_link);
+		spin_unlock(&cache->node_list_lock);
+
+		/* we've extracted an active node from the tree - now dispose
+		 * of it */
+		fscache_withdraw_node(cache, node);
+		cache->ops->put_node(node);
+
+		spin_lock(&cache->node_list_lock);
+	}
+
+	spin_unlock(&cache->node_list_lock);
+
+	_leave("");
+
+} /* end fscache_withdraw_cache() */
+
+EXPORT_SYMBOL(fscache_withdraw_cache);
+
+/*****************************************************************************/
+/*
+ * withdraw a node from active service
+ * - need break the links to a cached object cookie
+ * - called under two situations:
+ *   (1) recycler decides to reclaim an in-use node
+ *   (2) a cache is unmounted
+ * - have to take care as the cookie can be being relinquished by the netfs
+ *   simultaneously
+ * - the active node is pinned by the caller holding a refcount on it
+ */
+static void fscache_withdraw_node(struct fscache_cache *cache,
+				  struct fscache_node *node)
+{
+	struct fscache_search_result *srch;
+	struct fscache_cookie *cookie, *xcookie = NULL;
+
+	_enter("");
+
+	/* first of all we have to break the links between the node and the
+	 * cookie
+	 * - we have to hold both semaphores BUT we have to get the cookie sem
+	 *   FIRST
+	 */
+	cache->ops->lock_node(node);
+
+	cookie = node->cookie;
+	if (cookie) {
+		/* pin the cookie so that is doesn't escape */
+		atomic_inc(&cookie->usage);
+
+		/* re-order the locks to avoid deadlock */
+		cache->ops->unlock_node(node);
+		down_write(&cookie->sem);
+		cache->ops->lock_node(node);
+
+		/* erase references from the node to the cookie */
+		list_del_init(&node->cookie_link);
+
+		xcookie = node->cookie;
+		node->cookie = NULL;
+
+		/* delete the search result record for this node from the
+		 * cookie's list */
+		list_for_each_entry(srch, &cookie->search_results, link) {
+			if (srch->cache == cache)
+				goto found_record;
+		}
+		BUG();
+
+	found_record:
+		list_del_init(&srch->link);
+
+		if (srch != &cache->fsdef_srch) {
+			dbgfree(srch);
+			kfree(srch);
+		}
+
+		up_write(&cookie->sem);
+	}
+
+	cache->ops->unlock_node(node);
+
+	/* we've broken the links between cookie and node */
+	if (xcookie) {
+		fscache_cookie_put(xcookie);
+		cache->ops->put_node(node);
+	}
+
+	/* unpin the cookie */
+	if (cookie)
+		fscache_cookie_put(cookie);
+
+	_leave("");
+
+} /* end fscache_withdraw_node() */
+
+/*****************************************************************************/
+/*
+ * search for representation of an object in its parent cache
+ * - the cookie semaphore must be locked by the caller
+ * - returns -ENODATA if the object or one of its ancestors doesn't exist
+ */
+static int fscache_search_for_object(struct fscache_cookie *cookie,
+				     struct fscache_cache *cache)
+{
+	struct fscache_search_result *srch;
+	struct fscache_cookie *iparent;
+	struct fscache_node *ipnode, *node;
+	int ret;
+
+	iparent = cookie->iparent;
+	if (!iparent) {
+		/* FSDEF entries don't have a parent */
+		_enter("{.fsdef},%s.%s",
+		       cache->ops->name, cache->identifier);
+		BUG_ON(list_empty(&cookie->backing_nodes));
+		BUG_ON(list_empty(&cookie->search_results));
+		_leave(" = 0 [.fsdef]");
+		return 0;
+	}
+
+	_enter("{%s/%s},%s.%s",
+	       iparent->idef->name,
+	       cookie->idef ? (char *) cookie->idef->name : "<file>",
+	       cache->ops->name, cache->identifier);
+
+	/* see if there's a search result for this object already */
+	read_lock(&cookie->lock);
+
+	list_for_each_entry(srch, &cookie->search_results, link) {
+		_debug("check entry %p x %p [ino %u]",
+		       cookie, cache, srch->ino);
+
+		if (srch->cache == cache) {
+			read_unlock(&cookie->lock);
+			_debug("found entry");
+
+			if (srch->ino) {
+				_leave(" = 0 [found ino %u]", srch->ino);
+				return 0;
+			}
+
+			/* entry is negative */
+			_leave(" = -ENODATA");
+			return -ENODATA;
+		}
+	}
+
+	read_unlock(&cookie->lock);
+
+	/* allocate an initially negative entry for this object */
+	_debug("alloc entry %p x %p", cookie, cache);
+
+	srch = kmalloc(sizeof(*srch), GFP_KERNEL);
+	if (!srch) {
+		_leave(" = -ENOMEM");
+		return -ENOMEM;
+	}
+
+	srch->cache	= cache;
+	srch->ino	= 0;
+	INIT_LIST_HEAD(&srch->link);
+
+ 	/* we need see if there's an entry for this cache in this object's
+	 * parent index, so the first thing to do is to see if the parent index
+	 * is represented on disc
+	 */
+	down_read(&iparent->sem);
+
+	ret = fscache_search_for_object(iparent, cache);
+	if (ret < 0) {
+		if (ret != -ENODATA)
+			goto error;
+
+		/* set a negative entry */
+		list_add_tail(&srch->link, &cookie->search_results);
+		goto done;
+	}
+
+	/* find the parent's backing node */
+	read_lock(&iparent->lock);
+	list_for_each_entry(ipnode, &iparent->backing_nodes, cookie_link) {
+		if (ipnode->cache == cache)
+			goto found_parent_entry;
+	}
+
+	BUG();
+
+ found_parent_entry:
+	read_unlock(&iparent->lock);
+	_debug("found_parent_entry");
+
+	/* search the parent index for a reference compatible with this
+	 * object */
+	ret = cache->ops->index_search(ipnode, cookie, srch);
+	switch (ret) {
+	default:
+		goto error;
+
+	case 0:
+		/* found - allocate a node */
+		node = cache->ops->lookup_node(cache, srch->ino);
+		if (IS_ERR(node)) {
+			ret = PTR_ERR(node);
+			goto error;
+		}
+
+		cache->ops->lock_node(node);
+
+		/* a node should only ever be attached to one cookie */
+		BUG_ON(!list_empty(&node->cookie_link));
+
+		/* attach the node to the cache's node list */
+		if (list_empty(&node->cache_link)) {
+			if (!cache->ops->grab_node(node))
+				goto igrab_failed_upput;
+
+			spin_lock(&cache->node_list_lock);
+			list_add_tail(&node->cache_link, &cache->node_list);
+			spin_unlock(&cache->node_list_lock);
+		}
+
+		/* attach the node to the cookie */
+		node->cookie = cookie;
+		atomic_inc(&cookie->usage);
+
+		write_lock(&iparent->lock);
+		list_add_tail(&srch->link, &cookie->search_results);
+		list_add_tail(&node->cookie_link, &cookie->backing_nodes);
+		write_unlock(&iparent->lock);
+
+		cache->ops->unlock_node(node);
+		break;
+
+	case -ENOENT:
+		/* we can at least set a valid negative entry */
+		list_add_tail(&srch->link, &cookie->search_results);
+		ret = -ENODATA;
+		break;
+	}
+
+ done:
+	up_read(&iparent->sem);
+	_leave(" = %d", ret);
+	return ret;
+
+ igrab_failed_upput:
+	cache->ops->unlock_node(node);
+	cache->ops->put_node(node);
+	ret = -ENOENT;
+ error:
+	up_read(&iparent->sem);
+	dbgfree(srch);
+	kfree(srch);
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end fscache_search_for_object() */
+
+/*****************************************************************************/
+/*
+ * instantiate the object in the specified cache
+ * - the cookie must be write-locked by the caller
+ * - search must have been performed first (so lists of search results are
+ *   filled out)
+ * - all parent index objects are instantiated if necessary
+ */
+static int fscache_instantiate_object(struct fscache_cookie *cookie,
+				      struct fscache_cache *cache)
+{
+	struct fscache_search_result *srch;
+	struct fscache_cookie *iparent;
+	struct fscache_node *ipnode, *node;
+	int ret;
+
+	iparent = cookie->iparent;
+	if (!iparent)
+		return 0; /* FSDEF entries don't have a parent */
+
+	_enter("{%s/%s},",
+	       iparent->idef->name,
+	       cookie->idef ? (char *) cookie->idef->name : "<file>");
+
+	/* find the search result for this object */
+	list_for_each_entry(srch, &cookie->search_results, link) {
+		if (srch->cache == cache)
+			goto found_search_result;
+	}
+
+	BUG();
+
+ found_search_result:
+	if (srch->ino) {
+		/* it was instantiated already */
+		_leave(" = 0 [found ino %u]", srch->ino);
+		return 0;
+	}
+
+	/* we need to insert an entry for this cache in the object's parent
+	 * index, so the first thing to do is make sure that the parent index
+	 * is represented on disc
+	 */
+	down_write(&iparent->sem);
+
+	ret = fscache_instantiate_object(iparent, cache);
+	if (ret < 0)
+		goto error;
+
+	/* the parent index's node should now be available */
+	list_for_each_entry(ipnode, &iparent->backing_nodes, cookie_link) {
+		if (ipnode->cache == cache)
+			goto found_parent_node;
+	}
+
+	BUG();
+
+ found_parent_node:
+	_debug("found_parent_node: node=%p", ipnode);
+
+	BUG_ON(ipnode->cookie != iparent);
+
+	/* allocate an entry within the parent index node */
+	ret = cache->ops->index_add(ipnode, cookie, srch);
+	if (ret < 0)
+		goto error;
+
+	/* we're going to need an in-memory reflection of the node too */
+	node = cache->ops->lookup_node(cache, srch->ino);
+	if (IS_ERR(node)) {
+		ret = PTR_ERR(node);
+		goto error_x; /* uh-oh... our search record is now wrong */
+	}
+
+	/* keep track of it */
+	cache->ops->lock_node(node);
+
+	BUG_ON(!list_empty(&node->cookie_link));
+
+	/* attach to the cache's node list */
+	if (list_empty(&node->cache_link)) {
+		if (!cache->ops->grab_node(node))
+			goto error_xi;
+
+		spin_lock(&cache->node_list_lock);
+		list_add_tail(&node->cache_link, &cache->node_list);
+		spin_unlock(&cache->node_list_lock);
+	}
+
+	/* attach to the cookie's search result list */
+	node->cookie = cookie;
+	atomic_inc(&cookie->usage);
+	list_add_tail(&node->cookie_link, &cookie->backing_nodes);
+
+	/* done */
+	cache->ops->unlock_node(node);
+	up_write(&iparent->sem);
+	_leave(" = 0 [new]");
+	return 0;
+
+	/* if we get an error after having instantiated a node on disc, just
+	 * discard the search record so we find it next time */
+ error_xi:
+	cache->ops->unlock_node(node);
+	cache->ops->put_node(node);
+	ret = -ENOENT;
+ error_x:
+	list_del(&srch->link);
+	dbgfree(srch);
+	kfree(srch);
+	srch = NULL;
+ error:
+	up_write(&iparent->sem);
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end fscache_instantiate_object() */
+
+/*****************************************************************************/
+/*
+ * select a cache on which to store a file
+ * - the cache addremove semaphore must be at least read-locked by the caller
+ */
+static struct fscache_cache *fscache_select_cache_for_file(void)
+{
+	struct fscache_cache *cache;
+
+	_enter("");
+
+	/* TODO: make more intelligent than just choosing the first cache */
+	cache = NULL;
+	if (!list_empty(&fscache_cache_list))
+		cache = list_entry(fscache_cache_list.next,
+				   struct fscache_cache,
+				   link);
+
+	_leave(" = %p", cache);
+	return cache;
+
+} /* end fscache_select_cache_for_file() */
+
+/*****************************************************************************/
+/*
+ * request a cookie to represent a data file or an index
+ * - iparent specifies the parent index to pin in memory
+ *   - the top level index cookie for each netfs is stored in the fscache_netfs
+ *     struct upon registration
+ * - idef is NULL for a data file
+ * - idef points to the definition for an index
+ * - the netfs_data will be passed to the functions pointed to in *idef
+ * - all attached caches will be searched to see if they contain this object
+ * - index objects aren't stored on disc until there's a dependent file that
+ *   needs storing
+ * - file objects are stored in a selected cache immediately, and all the
+ *   indexes forming the path to it are instantiated if necessary
+ * - we never let on to the netfs about errors
+ *   - we may set a negative cookie pointer, but that's okay
+ */
+struct fscache_cookie *__fscache_acquire_cookie(struct fscache_cookie *iparent,
+						struct fscache_index_def *idef,
+						void *netfs_data)
+{
+	struct fscache_cookie *cookie;
+	struct fscache_cache *cache;
+	int ret = 0;
+
+	_enter("{%s},{%s},%p",
+	       iparent ? (char *) iparent->idef->name : "<no-parent>",
+	       idef ? (char *) idef->name : "<file>",
+	       netfs_data);
+
+	/* if there's no parent cookie, then we don't create one here either */
+	if (iparent == FSCACHE_NEGATIVE_COOKIE) {
+		_leave(" [no parent]");
+		return FSCACHE_NEGATIVE_COOKIE;
+	}
+
+	/* if it's going to be an index then validate the index data */
+	if (idef) {
+		size_t dsize;
+		int loop;
+
+		if (!idef->name[0]) {
+			printk("FS-Cache: %s.%s.%p: nameless index\n",
+			       iparent->netfs->name,
+			       iparent->idef->name,
+			       idef);
+			return FSCACHE_NEGATIVE_COOKIE;
+		}
+
+		dsize = idef->data_size;
+
+		for (loop = 0; loop < 4; loop++) {
+			if (idef->keys[loop].type >=
+			    FSCACHE_INDEX_KEYS__LAST) {
+				printk("FS-Cache: %s.%s.%s:"
+				       " index type %u unsupported\n",
+				       iparent->netfs->name,
+				       iparent->idef->name,
+				       idef->name,
+				       idef->keys[loop].type);
+				return FSCACHE_NEGATIVE_COOKIE;
+			}
+
+			dsize += idef->keys[loop].len;
+		}
+
+		if (dsize > 400) {
+			printk("FS-Cache: %s.%s.%s:"
+			       " index entry size exceeds maximum %u>400\n",
+			       iparent->netfs->name,
+			       iparent->idef->name,
+			       idef->name,
+			       dsize);
+			return FSCACHE_NEGATIVE_COOKIE;
+		}
+	}
+
+	/* allocate and initialise a cookie */
+	cookie = kmem_cache_alloc(fscache_cookie_jar, SLAB_KERNEL);
+	if (!cookie) {
+		_leave(" [ENOMEM]");
+		return FSCACHE_NEGATIVE_COOKIE;
+	}
+
+	atomic_set(&cookie->usage, 1);
+	atomic_set(&cookie->children, 0);
+
+	atomic_inc(&iparent->usage);
+	atomic_inc(&iparent->children);
+
+	cookie->idef		= idef;
+	cookie->iparent		= iparent;
+	cookie->netfs		= iparent->netfs;
+	cookie->netfs_data	= netfs_data;
+
+	/* now we need to see whether the backing objects for this cookie yet
+	 * exist, if not there'll be nothing to search */
+	down_read(&fscache_addremove_sem);
+
+	if (list_empty(&fscache_cache_list)) {
+		up_read(&fscache_addremove_sem);
+		_leave(" [no caches]");
+		return cookie;
+	}
+
+	down_write(&cookie->sem);
+
+	/* search every cache we know about to see if the object is already
+	 * present */
+	list_for_each_entry(cache, &fscache_cache_list, link) {
+		ret = fscache_search_for_object(cookie, cache);
+		switch (ret) {
+		case 0:
+			if (!cookie->idef)
+				break;	/* only want the first file entry */
+		case -ENODATA:
+			ret = 0;
+			continue;
+		default:
+			goto error;
+		}
+	}
+
+	/* if the object is a cookie then we need do nothing more here - we
+	 * create indexes on disc when we need them as an index may exist in
+	 * multiple caches */
+	if (cookie->idef)
+		goto done;
+
+	/* the object is a file - we need to select a cache in which to store
+	 * it */
+	ret = -ENOMEDIUM;
+	cache = fscache_select_cache_for_file();
+	if (!cache)
+		goto error; /* couldn't decide on a cache */
+
+	/* create a file index entry on disc, along with all the indexes
+	 * required to find it again later */
+	ret = fscache_instantiate_object(cookie, cache);
+	if (ret == 0)
+		goto done;
+
+ error:
+	printk("FS-Cache: error from cache fs: %d\n", ret);
+	if (cookie) {
+		__fscache_cookie_put(cookie);
+		cookie = FSCACHE_NEGATIVE_COOKIE;
+		atomic_dec(&iparent->children);
+	}
+
+ done:
+	up_write(&cookie->sem);
+	up_read(&fscache_addremove_sem);
+	_leave(" = %p", cookie);
+	return cookie;
+
+} /* end __fscache_acquire_cookie() */
+
+EXPORT_SYMBOL(__fscache_acquire_cookie);
+
+/*****************************************************************************/
+/*
+ * release a cookie back to the cache
+ * - the object will be marked as recyclable on disc if retire is true
+ * - all dependents of this cookie must have already been unregistered
+ *   (indexes/files/pages)
+ */
+void __fscache_relinquish_cookie(struct fscache_cookie *cookie, int retire)
+{
+	struct fscache_cache *cache;
+	struct fscache_node *node;
+
+	_enter("%p{%s},%d",
+	       cookie,
+	       cookie && cookie->idef ? (char *) cookie->idef->name : "<file>",
+	       retire);
+
+	if (cookie == FSCACHE_NEGATIVE_COOKIE) {
+		_leave(" [no cookie]");
+		return;
+	}
+
+	if (atomic_read(&cookie->children) != 0) {
+		printk("FS-Cache: cookie still has children\n");
+		BUG();
+	}
+
+	/* detach pointers back to netfs */
+	down_write(&cookie->sem);
+
+	cookie->netfs_data	= NULL;
+	cookie->idef		= NULL;
+
+	read_lock(&cookie->lock);
+
+	/* queue retired objects for recycling */
+	if (retire) {
+		list_for_each_entry(node,
+				    &cookie->backing_nodes,
+				    cookie_link) {
+			set_bit(FSCACHE_NODE_RECYCLING, &node->flags);
+		}
+	}
+
+	/* break links with all the active nodes */
+	while (!list_empty(&cookie->backing_nodes)) {
+		node = list_entry(cookie->backing_nodes.next,
+				   struct fscache_node,
+				   cookie_link);
+
+		/* detach each cache node from the object cookie */
+		set_bit(FSCACHE_NODE_RELEASING, &node->flags);
+
+		list_del_init(&node->cookie_link);
+		read_unlock(&cookie->lock);
+
+		cache = node->cache;
+		cache->ops->lock_node(node);
+		node->cookie = NULL;
+		cache->ops->unlock_node(node);
+
+		if (atomic_dec_and_test(&cookie->usage))
+			/* the cookie refcount shouldn't be reduced to 0 yet */
+			BUG();
+
+		cache->ops->put_node(node);
+
+		read_lock(&cookie->lock);
+	}
+
+	read_unlock(&cookie->lock);
+	up_write(&cookie->sem);
+
+	if (cookie->iparent)
+		atomic_dec(&cookie->iparent->children);
+
+	/* finally dispose of the cookie */
+	fscache_cookie_put(cookie);
+
+	_leave("");
+
+} /* end __fscache_relinquish_cookie() */
+
+EXPORT_SYMBOL(__fscache_relinquish_cookie);
+
+/*****************************************************************************/
+/*
+ * update the index entries backing a cookie
+ */
+void __fscache_update_cookie(struct fscache_cookie *cookie)
+{
+	struct fscache_node *ixnode, *node;
+
+	_enter("{%s}",
+	       cookie &&
+	       cookie->idef ? (char *) cookie->idef->name : "<file>");
+
+	if (cookie == FSCACHE_NEGATIVE_COOKIE) {
+		_leave(" [no cookie]");
+		return;
+	}
+
+	down_write(&cookie->sem);
+	down_write(&cookie->iparent->sem);
+
+	/* update the index entry on disc in each cache backing this cookie */
+	list_for_each_entry(node, &cookie->backing_nodes, cookie_link) {
+		ixnode = fscache_find_parent_node(node);
+		node->cache->ops->index_update(ixnode, node);
+	}
+
+	up_write(&cookie->iparent->sem);
+	up_write(&cookie->sem);
+	_leave("");
+
+} /* end __fscache_update_cookie() */
+
+EXPORT_SYMBOL(__fscache_update_cookie);
+
+/*****************************************************************************/
+/*
+ * destroy a cookie
+ */
+void __fscache_cookie_put(struct fscache_cookie *cookie)
+{
+	struct fscache_search_result *srch;
+
+	_enter("%p", cookie);
+
+	if (cookie->iparent)
+		fscache_cookie_put(cookie->iparent);
+
+	/* dispose of any cached search results */
+	while (!list_empty(&cookie->search_results)) {
+		srch = list_entry(cookie->search_results.next,
+				  struct fscache_search_result,
+				  link);
+
+		list_del(&srch->link);
+		kfree(srch);
+	}
+
+	BUG_ON(!list_empty(&cookie->search_results));
+	BUG_ON(!list_empty(&cookie->backing_nodes));
+	kmem_cache_free(fscache_cookie_jar, cookie);
+
+	_leave("");
+
+} /* end __fscache_cookie_put() */
+
+/*****************************************************************************/
+/*
+ * initialise an cookie jar slab element prior to any use
+ */
+void fscache_cookie_init_once(void *_cookie, kmem_cache_t *cachep,
+			      unsigned long flags)
+{
+	struct fscache_cookie *cookie = _cookie;
+
+	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+	    SLAB_CTOR_CONSTRUCTOR) {
+		memset(cookie, 0, sizeof(*cookie));
+		rwlock_init(&cookie->lock);
+		init_rwsem(&cookie->sem);
+		INIT_LIST_HEAD(&cookie->search_results);
+		INIT_LIST_HEAD(&cookie->backing_nodes);
+	}
+
+} /* end fscache_cookie_init_once() */
diff -puN /dev/null fs/fscache/fscache-int.h
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/fs/fscache/fscache-int.h	2004-11-03 20:25:57.310037632 -0800
@@ -0,0 +1,81 @@
+/* fscache-int.h: internal definitions
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _FSCACHE_INT_H
+#define _FSCACHE_INT_H
+
+#include <linux/fscache-cache.h>
+#include <linux/timer.h>
+#include <linux/bio.h>
+
+extern kmem_cache_t *fscache_cookie_jar;
+
+extern struct fscache_cookie fscache_fsdef_index;
+
+extern void fscache_cookie_init_once(void *_cookie, kmem_cache_t *cachep, unsigned long flags);
+
+extern void __fscache_cookie_put(struct fscache_cookie *cookie);
+
+static inline void fscache_cookie_put(struct fscache_cookie *cookie)
+{
+	BUG_ON(atomic_read(&cookie->usage) <= 0);
+
+	if (atomic_dec_and_test(&cookie->usage))
+		__fscache_cookie_put(cookie);
+
+}
+
+/*****************************************************************************/
+/*
+ * debug tracing
+ */
+#define dbgprintk(FMT,...) \
+	printk("[%-6.6s] "FMT"\n",current->comm ,##__VA_ARGS__)
+#define _dbprintk(FMT,...) do { } while(0)
+
+#define kenter(FMT,...)	dbgprintk("==> %s("FMT")",__FUNCTION__ ,##__VA_ARGS__)
+#define kleave(FMT,...)	dbgprintk("<== %s()"FMT"",__FUNCTION__ ,##__VA_ARGS__)
+#define kdebug(FMT,...)	dbgprintk(FMT ,##__VA_ARGS__)
+
+#define kjournal(FMT,...) _dbprintk(FMT ,##__VA_ARGS__)
+
+#define dbgfree(ADDR)  _dbprintk("%p:%d: FREEING %p",__FILE__,__LINE__,ADDR)
+
+#define dbgpgalloc(PAGE)						\
+do {									\
+	_dbprintk("PGALLOC %s:%d: %p {%lx,%lu}\n",			\
+		  __FILE__,__LINE__,					\
+		  (PAGE),(PAGE)->mapping->host->i_ino,(PAGE)->index	\
+		  );							\
+} while(0)
+
+#define dbgpgfree(PAGE)						\
+do {								\
+	if ((PAGE))						\
+		_dbprintk("PGFREE %s:%d: %p {%lx,%lu}\n",	\
+			  __FILE__,__LINE__,			\
+			  (PAGE),				\
+			  (PAGE)->mapping->host->i_ino,		\
+			  (PAGE)->index				\
+			  );					\
+} while(0)
+
+#ifdef __KDEBUG
+#define _enter(FMT,...)	kenter(FMT,##__VA_ARGS__)
+#define _leave(FMT,...)	kleave(FMT,##__VA_ARGS__)
+#define _debug(FMT,...)	kdebug(FMT,##__VA_ARGS__)
+#else
+#define _enter(FMT,...)	do { } while(0)
+#define _leave(FMT,...)	do { } while(0)
+#define _debug(FMT,...)	do { } while(0)
+#endif
+
+#endif /* _FSCACHE_INT_H */
diff -puN /dev/null fs/fscache/fsdef.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/fs/fscache/fsdef.c	2004-11-03 20:25:57.311037480 -0800
@@ -0,0 +1,90 @@
+/* fsdef.c: filesystem index definition
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include "fscache-int.h"
+
+static fscache_match_val_t fscache_fsdef_index_match(void *target,
+						     const void *entry);
+
+static void fscache_fsdef_index_update(void *source, void *entry);
+
+static struct fscache_index_def fscache_fsdef_index_def = {
+	.name		= ".fsdef",
+	.data_size	= sizeof(struct fscache_fsdef_index_entry),
+	.keys		= {
+		{ .type = FSCACHE_INDEX_KEYS_ASCIIZ,	.len = 24 },
+	},
+	.match		= fscache_fsdef_index_match,
+	.update		= fscache_fsdef_index_update
+};
+
+struct fscache_cookie fscache_fsdef_index = {
+	.usage		= ATOMIC_INIT(1),
+	.idef		= &fscache_fsdef_index_def,
+	.lock		= RW_LOCK_UNLOCKED,
+	.sem		= __RWSEM_INITIALIZER(fscache_fsdef_index.sem),
+	.search_results	= LIST_HEAD_INIT(fscache_fsdef_index.search_results),
+	.backing_nodes	= LIST_HEAD_INIT(fscache_fsdef_index.backing_nodes),
+};
+
+EXPORT_SYMBOL(fscache_fsdef_index);
+
+/*****************************************************************************/
+/*
+ * see if the netfs definition matches
+ */
+static fscache_match_val_t fscache_fsdef_index_match(void *target,
+						     const void *entry)
+{
+	const struct fscache_fsdef_index_entry *fsdef = entry;
+	struct fscache_netfs *netfs = target;
+
+	_enter("%p,%p", target, entry);
+
+	/* name and version must both match with what's on disc */
+	_debug("{%s.%u},{%s.%u}",
+	       netfs->name, netfs->version, fsdef->name, fsdef->version);
+
+	if (strncmp(netfs->name, fsdef->name, sizeof(fsdef->name)) != 0) {
+		_leave(" = FAILED");
+		return FSCACHE_MATCH_FAILED;
+	}
+
+	if (netfs->version == fsdef->version) {
+		_leave(" = SUCCESS");
+		return FSCACHE_MATCH_SUCCESS;
+	}
+
+	/* an entry of the same name but different version is scheduled for
+	 * deletion */
+	_leave(" = SUCCESS_DELETE");
+	return FSCACHE_MATCH_SUCCESS_DELETE;
+
+} /* end fscache_fsdef_index_match() */
+
+/*****************************************************************************/
+/*
+ * update the netfs definition to be stored on disc
+ */
+static void fscache_fsdef_index_update(void *source, void *entry)
+{
+	struct fscache_fsdef_index_entry *fsdef = entry;
+	struct fscache_netfs *netfs = source;
+
+	_enter("{%s.%u},", netfs->name, netfs->version);
+
+	/* install the netfs name and version in the top-level index entry */
+	strncpy(fsdef->name, netfs->name, sizeof(fsdef->name));
+
+	fsdef->version = netfs->version;
+
+} /* end fscache_fsdef_index_update() */
diff -puN /dev/null fs/fscache/main.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/fs/fscache/main.c	2004-11-03 20:25:57.312037328 -0800
@@ -0,0 +1,111 @@
+/* main.c: general filesystem caching manager
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <linux/slab.h>
+#include "fscache-int.h"
+
+int fscache_debug = 0;
+
+static int fscache_init(void);
+static void fscache_exit(void);
+
+fs_initcall(fscache_init);
+module_exit(fscache_exit);
+
+MODULE_DESCRIPTION("FS Cache Manager");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+/*****************************************************************************/
+/*
+ * initialise the fs caching module
+ */
+static int fscache_init(void)
+{
+	fscache_cookie_jar =
+		kmem_cache_create("fscache_cookie_jar",
+				  sizeof(struct fscache_cookie),
+				  0,
+				  SLAB_HWCACHE_ALIGN,
+				  fscache_cookie_init_once,
+				  NULL);
+
+	if (!fscache_cookie_jar) {
+		printk(KERN_NOTICE
+		       "FS-Cache: Failed to allocate a cookie jar\n");
+		return -ENOMEM;
+	}
+
+	printk(KERN_INFO "fscache: general fs caching registered\n");
+	return 0;
+
+} /* end fscache_init() */
+
+/*****************************************************************************/
+/*
+ * clean up on module removal
+ */
+static void __exit fscache_exit(void)
+{
+	printk(KERN_INFO "FS-Cache: general fs caching unregistering\n");
+
+	kmem_cache_destroy(fscache_cookie_jar);
+
+} /* end fscache_exit() */
+
+/*****************************************************************************/
+/*
+ * clear the dead space between task_struct and kernel stack
+ * - called by supplying -finstrument-functions to gcc
+ */
+#if 0
+void __cyg_profile_func_enter (void *this_fn, void *call_site)
+__attribute__((no_instrument_function));
+
+void __cyg_profile_func_enter (void *this_fn, void *call_site)
+{
+       asm volatile("  movl    %%esp,%%edi     \n"
+                    "  andl    %0,%%edi        \n"
+                    "  addl    %1,%%edi        \n"
+                    "  movl    %%esp,%%ecx     \n"
+                    "  subl    %%edi,%%ecx     \n"
+                    "  shrl    $2,%%ecx        \n"
+                    "  movl    $0xedededed,%%eax     \n"
+                    "  rep stosl               \n"
+                    :
+                    : "i"(~(THREAD_SIZE-1)), "i"(sizeof(struct thread_info))
+                    : "eax", "ecx", "edi", "memory", "cc"
+                    );
+}
+
+void __cyg_profile_func_exit(void *this_fn, void *call_site)
+__attribute__((no_instrument_function));
+
+void __cyg_profile_func_exit(void *this_fn, void *call_site)
+{
+       asm volatile("  movl    %%esp,%%edi     \n"
+                    "  andl    %0,%%edi        \n"
+                    "  addl    %1,%%edi        \n"
+                    "  movl    %%esp,%%ecx     \n"
+                    "  subl    %%edi,%%ecx     \n"
+                    "  shrl    $2,%%ecx        \n"
+                    "  movl    $0xdadadada,%%eax     \n"
+                    "  rep stosl               \n"
+                    :
+                    : "i"(~(THREAD_SIZE-1)), "i"(sizeof(struct thread_info))
+                    : "eax", "ecx", "edi", "memory", "cc"
+                    );
+}
+#endif
diff -puN /dev/null fs/fscache/Makefile
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/fs/fscache/Makefile	2004-11-03 20:25:57.312037328 -0800
@@ -0,0 +1,13 @@
+#
+# Makefile for general filesystem caching code
+#
+
+#CFLAGS += -finstrument-functions
+
+fscache-objs := \
+	cookie.o \
+	fsdef.o \
+	main.o \
+	page.o
+
+obj-$(CONFIG_FSCACHE) := fscache.o
diff -puN /dev/null fs/fscache/page.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/fs/fscache/page.c	2004-11-03 20:25:57.313037176 -0800
@@ -0,0 +1,231 @@
+/* page.c: general filesystem cache cookie management
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/fscache-cache.h>
+#include <linux/buffer_head.h>
+#include "fscache-int.h"
+
+/*****************************************************************************/
+/*
+ * read a page from the cache or allocate a block in which to store it
+ * - we return:
+ *   -ENOMEM	- out of memory, nothing done
+ *   -ENOBUFS	- no backing node available in which to cache the block
+ *   -ENODATA	- no data available in the backing node for this block
+ *   0		- dispatched a read - it'll call end_io_func() when finished
+ */
+int __fscache_read_or_alloc_page(struct fscache_cookie *cookie,
+				 struct page *page,
+				 fscache_rw_complete_t end_io_func,
+				 void *end_io_data,
+				 unsigned long gfp)
+{
+	struct fscache_node *node;
+	struct fscache_page *pageio;
+	int ret;
+
+	_enter("%p,{%lu},", cookie, page->index);
+
+	if (cookie == FSCACHE_NEGATIVE_COOKIE) {
+		_leave(" -ENOBUFS [no cookie]");
+		return -ENOBUFS;
+	}
+
+	if (list_empty(&cookie->backing_nodes)) {
+		_leave(" -ENOBUFS [no backing nodes]");
+		return -ENOBUFS;
+	}
+
+	BUG_ON(cookie->idef); /* not supposed to use this for indexes */
+
+	/* get the cache-cookie for this page */
+	pageio = cookie->netfs->ops->get_page_token(page);
+	if (IS_ERR(pageio)) {
+		_leave(" = %ld", PTR_ERR(pageio));
+		return PTR_ERR(pageio);
+	}
+
+	/* prevent the file from being uncached whilst we access it */
+	down_read(&cookie->sem);
+
+	ret = -ENOBUFS;
+	if (!list_empty(&cookie->backing_nodes)) {
+		/* get and pin the backing node */
+		node = list_entry(cookie->backing_nodes.next,
+				  struct fscache_node,
+				  cookie_link);
+
+		if (node->cache->ops->grab_node(node)) {
+			/* ask the cache to honour the operation */
+			ret = node->cache->ops->read_or_alloc_page(node,
+								   page,
+								   pageio,
+								   end_io_func,
+								   end_io_data,
+								   gfp);
+
+			node->cache->ops->put_node(node);
+		}
+
+	}
+	up_read(&cookie->sem);
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end __fscache_read_or_alloc_page() */
+
+EXPORT_SYMBOL(__fscache_read_or_alloc_page);
+
+/*****************************************************************************/
+/*
+ * request a page be stored in the cache
+ * - returns:
+ *   -ENOMEM	- out of memory, nothing done
+ *   -ENOBUFS	- no backing node available in which to cache the page
+ *   0		- dispatched a write - it'll call end_io_func() when finished
+ */
+int __fscache_write_page(struct fscache_cookie *cookie,
+			 struct page *page,
+			 fscache_rw_complete_t end_io_func,
+			 void *end_io_data,
+			 unsigned long gfp)
+{
+	struct fscache_page *pageio;
+	struct fscache_node *node;
+	int ret;
+
+	_enter("%p,{%lu},", cookie, page->index);
+
+	if (cookie == FSCACHE_NEGATIVE_COOKIE) {
+		_leave(" -ENOBUFS [no cookie]");
+		return -ENOBUFS; /* no actual cookie */
+	}
+
+	BUG_ON(cookie->idef); /* not supposed to use this for indexes */
+
+	/* get the cache-cookie for this page */
+	pageio = cookie->netfs->ops->get_page_token(page);
+	if (IS_ERR(pageio)) {
+		_leave(" = %ld", PTR_ERR(pageio));
+		return PTR_ERR(pageio);
+	}
+
+	/* prevent the file from been uncached whilst we deal with it */
+	down_read(&cookie->sem);
+
+	ret = -ENOBUFS;
+	if (!list_empty(&cookie->backing_nodes) && pageio->mapped_block) {
+		node = list_entry(cookie->backing_nodes.next,
+				  struct fscache_node,
+				  cookie_link);
+
+		/* ask the cache to honour the operation */
+		ret = node->cache->ops->write_page(node,
+						   page,
+						   pageio,
+						   end_io_func,
+						   end_io_data,
+						   gfp);
+	}
+
+	up_read(&cookie->sem);
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end __fscache_write_page() */
+
+EXPORT_SYMBOL(__fscache_write_page);
+
+/*****************************************************************************/
+/*
+ * remove a page from the cache
+ * - if the block backing the page still has a vjentry then the block will be
+ *   recycled
+ */
+void __fscache_uncache_page(struct fscache_cookie *cookie, struct page *page)
+{
+	struct fscache_page *pageio;
+	struct fscache_node *node;
+
+	_enter(",{%lu}", page->index);
+
+	if (cookie == FSCACHE_NEGATIVE_COOKIE) {
+		_leave(" [no cookie]");
+		return;
+	}
+
+	BUG_ON(cookie->idef); /* not supposed to use this for indexes */
+
+	/* get the cache-cookie for this page */
+	pageio = cookie->netfs->ops->get_page_token(page);
+	if (IS_ERR(pageio)) {
+		_leave(" [get_page_cookie() = %ld]", PTR_ERR(pageio));
+		return;
+	}
+
+	if (list_empty(&cookie->backing_nodes)) {
+		BUG_ON(pageio->mapped_block);
+		_leave(" [no backing]");
+		return;
+	}
+
+	if (!pageio->mapped_block) {
+		_leave(" [no mapping]");
+		return;
+	}
+
+	/* ask the cache to honour the operation */
+	down_read(&cookie->sem);
+
+	if (!list_empty(&cookie->backing_nodes) && pageio->mapped_block) {
+		node = list_entry(cookie->backing_nodes.next,
+				  struct fscache_node,
+				  cookie_link);
+
+		node->cache->ops->uncache_page(node, pageio);
+	}
+
+	up_read(&cookie->sem);
+
+	_leave("");
+	return;
+
+} /* end __fscache_uncache_page() */
+
+EXPORT_SYMBOL(__fscache_uncache_page);
+
+/*****************************************************************************/
+/*
+ * get a page caching token from for a page, allocating it and attaching it to
+ * the page's private pointer if it doesn't exist
+ */
+struct fscache_page * __fscache_page_get_private(struct page *page,
+						 unsigned gfp_flags)
+{
+	struct fscache_page *pageio = (struct fscache_page *) page->private;
+
+	if (!pageio) {
+		pageio = kmalloc(sizeof(*pageio), gfp_flags);
+		if (!pageio)
+			return ERR_PTR(-ENOMEM);
+
+		memset(pageio, 0, sizeof(*pageio));
+		rwlock_init(&pageio->lock);
+
+		page->private = (unsigned long) pageio;
+		SetPagePrivate(page);
+	}
+
+	return pageio;
+} /* end __fscache_page_get_private() */
+
+EXPORT_SYMBOL(__fscache_page_get_private);
diff -puN fs/Kconfig~split-general-cache-manager-from-cachefs fs/Kconfig
--- 25/fs/Kconfig~split-general-cache-manager-from-cachefs	2004-11-03 20:25:57.300039152 -0800
+++ 25-akpm/fs/Kconfig	2004-11-03 20:25:57.315036872 -0800
@@ -494,10 +494,21 @@ config AUTOFS4_FS
 
 menu "Caches"
 
-config CACHEFS
-	tristate "Filesystem caching support"
+config FSCACHE
+	tristate "General filesystem cache manager"
 	depends on EXPERIMENTAL
 	help
+	  This option enables a generic filesystem caching manager that can be
+	  used by various network and other filesystems to cache data
+	  locally. Diffent sorts of caches can be plugged in, depending on the
+	  resources available.
+
+	  See Documentation/filesystems/fscache.txt for more information.
+
+config CACHEFS
+	tristate "Filesystem caching filesystem"
+	depends on FSCACHE
+	help
 	  This filesystem acts as a cache for other filesystems - primarily
 	  networking filesystems - rather than thus allowing fast local disc to
 	  enhance the speed of slower devices.
diff -puN fs/Makefile~split-general-cache-manager-from-cachefs fs/Makefile
--- 25/fs/Makefile~split-general-cache-manager-from-cachefs	2004-11-03 20:25:57.302038848 -0800
+++ 25-akpm/fs/Makefile	2004-11-03 20:25:57.316036720 -0800
@@ -46,6 +46,7 @@ obj-y				+= devpts/
 obj-$(CONFIG_PROFILING)		+= dcookies.o
  
 # Do not add any filesystems before this line
+obj-$(CONFIG_FSCACHE)		+= fscache/
 obj-$(CONFIG_REISERFS_FS)	+= reiserfs/
 obj-$(CONFIG_EXT3_FS)		+= ext3/ # Before ext2 so root fs can be ext3
 obj-$(CONFIG_JBD)		+= jbd/
diff -puN /dev/null include/linux/fscache-cache.h
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/include/linux/fscache-cache.h	2004-11-03 20:25:57.317036568 -0800
@@ -0,0 +1,205 @@
+/* fscache-cache.h: general filesystem caching backing cache interface
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_FSCACHE_CACHE_H
+#define _LINUX_FSCACHE_CACHE_H
+
+#include <linux/fscache.h>
+
+struct fscache_cache;
+struct fscache_cache_ops;
+struct fscache_node;
+struct fscache_search_result;
+
+struct fscache_search_result {
+	struct list_head		link;		/* link in search_results */
+	struct fscache_cache		*cache;		/* cache searched */
+	unsigned			ino;		/* node ID (or 0 if negative) */
+};
+
+struct fscache_cache {
+	struct fscache_cache_ops	*ops;
+	struct list_head		link;		/* link in list of caches */
+	size_t				max_index_size;	/* maximum size of index data */
+	unsigned long			flags;
+#define FSCACHE_CACHE_WITHDRAWN		0		/* T if cache has been withdrawn */
+
+	char				identifier[32];	/* cache label */
+
+	/* node management */
+	struct list_head		node_list;	/* list of data/index nodes */
+	spinlock_t			node_list_lock;
+	struct fscache_search_result	fsdef_srch;	/* search result for the fsdef index */
+};
+
+extern void fscache_init_cache(struct fscache_cache *cache,
+			       struct fscache_cache_ops *ops,
+			       unsigned fsdef_ino,
+			       const char *idfmt,
+			       ...) __attribute__ ((format (printf,4,5)));
+
+extern void fscache_add_cache(struct fscache_cache *cache);
+extern void fscache_withdraw_cache(struct fscache_cache *cache);
+
+/* see if a cache has been withdrawn */
+static inline int fscache_is_cache_withdrawn(struct fscache_cache *cache)
+{
+	return test_bit(FSCACHE_CACHE_WITHDRAWN, &cache->flags);
+}
+
+/*****************************************************************************/
+/*
+ * cache operations
+ */
+struct fscache_cache_ops {
+	/* name of cache provider */
+	const char *name;
+
+	/* look up the nominated node for this cache */
+	struct fscache_node *(*lookup_node)(struct fscache_cache *cache, unsigned ino);
+
+	/* increment the usage count on this inode (may fail if unmounting) */
+	struct fscache_node *(*grab_node)(struct fscache_node *node);
+
+	/* lock a semaphore on a node */
+	void (*lock_node)(struct fscache_node *node);
+
+	/* unlock a semaphore on a node */
+	void (*unlock_node)(struct fscache_node *node);
+
+	/* dispose of a reference to a node */
+	void (*put_node)(struct fscache_node *node);
+
+	/* search an index for an inode to back a cookie
+	 * - the "inode number" should be set in result->ino
+	 */
+	int (*index_search)(struct fscache_node *node, struct fscache_cookie *cookie,
+			    struct fscache_search_result *result);
+
+	/* create a new file or inode, with an entry in the named index
+	 * - the "inode number" should be set in result->ino
+	 */
+	int (*index_add)(struct fscache_node *node, struct fscache_cookie *cookie,
+			 struct fscache_search_result *result);
+
+	/* update the index entry for a node
+	 * - the netfs's update operation should be called
+	 */
+	int (*index_update)(struct fscache_node *ixnode,
+			    struct fscache_node *node);
+
+	/* sync a cache */
+	void (*sync)(struct fscache_cache *cache);
+
+	/* dissociate a cache from all the pages it was backing */
+	void (*dissociate_pages)(struct fscache_cache *cache);
+
+	/* request a backing block for a page be read or allocated in the
+	 * cache */
+	int (*read_or_alloc_page)(struct fscache_node *node,
+				  struct page *page,
+				  struct fscache_page *pageio,
+				  fscache_rw_complete_t end_io_func,
+				  void *end_io_data,
+				  unsigned long gfp);
+
+	/* write a page to its backing block in the cache */
+	int (*write_page)(struct fscache_node *node,
+			  struct page *page,
+			  struct fscache_page *pageio,
+			  fscache_rw_complete_t end_io_func,
+			  void *end_io_data,
+			  unsigned long gfp);
+
+	/* detach a backing block from a page */
+	void (*uncache_page)(struct fscache_node *node,
+			     struct fscache_page *pageio);
+};
+
+/*****************************************************************************/
+/*
+ * data file or index object cookie
+ * - a file will only appear in one cache
+ * - a request to cache a file may or may not be honoured, subject to
+ *   constraints such as disc space
+ * - indexes files are created on disc just-in-time
+ */
+struct fscache_cookie {
+	atomic_t			usage;		/* number of users of this cookie */
+	atomic_t			children;	/* number of children of this cookie */
+	rwlock_t			lock;		/* list access lock */
+	struct rw_semaphore		sem;		/* list creation vs scan lock */
+	struct list_head		search_results;	/* results of searching iparent */
+	struct list_head		backing_nodes;	/* node(s) backing this file/index */
+	struct fscache_index_def	*idef;		/* index definition */
+	struct fscache_cookie		*iparent;	/* index holding this entry */
+	struct fscache_netfs		*netfs;		/* owner network fs definition */
+	void				*netfs_data;	/* back pointer to netfs */
+};
+
+extern struct fscache_cookie fscache_fsdef_index;
+
+/*****************************************************************************/
+/*
+ * on-disc cache file or index handle
+ */
+struct fscache_node {
+	unsigned long			flags;
+#define FSCACHE_NODE_ISINDEX		0	/* T if inode is index file (F if file) */
+#define FSCACHE_NODE_RELEASING		1	/* T if inode is being released */
+#define FSCACHE_NODE_RECYCLING		2	/* T if inode is being retired */
+#define FSCACHE_NODE_WITHDRAWN		3	/* T if inode has been withdrawn */
+
+	struct list_head		cache_link;	/* link in cache->node_list */
+	struct list_head		cookie_link;	/* link in cookie->backing_nodes */
+	struct fscache_cache		*cache;		/* cache that supplied this node */
+	struct fscache_cookie		*cookie;	/* netfs's file/index object */
+};
+
+static inline
+void fscache_node_init(struct fscache_node *node)
+{
+	node->flags = 0;
+	INIT_LIST_HEAD(&node->cache_link);
+	INIT_LIST_HEAD(&node->cookie_link);
+	node->cache = NULL;
+	node->cookie = NULL;
+}
+
+/* find the parent index node for a node */
+static inline
+struct fscache_node *fscache_find_parent_node(struct fscache_node *node)
+{
+	struct fscache_cookie *cookie = node->cookie;
+	struct fscache_cache *cache = node->cache;
+	struct fscache_node *parent;
+
+	list_for_each_entry(parent,
+			    &cookie->iparent->backing_nodes,
+			    cookie_link
+			    ) {
+		if (parent->cache == cache)
+			return parent;
+	}
+
+	return NULL;
+}
+
+/*****************************************************************************/
+/*
+ * definition of the contents of an FSDEF index entry
+ */
+struct fscache_fsdef_index_entry {
+	uint8_t		name[24];	/* name of netfs */
+	uint32_t	version;	/* version of layout */
+};
+
+#endif /* _LINUX_FSCACHE_CACHE_H */
diff -puN /dev/null include/linux/fscache.h
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/include/linux/fscache.h	2004-11-03 20:25:57.319036264 -0800
@@ -0,0 +1,357 @@
+/* fscache.h: general filesystem caching interface
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_FSCACHE_H
+#define _LINUX_FSCACHE_H
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/list.h>
+#include <linux/pagemap.h>
+
+#ifdef CONFIG_FSCACHE_MODULE
+#define CONFIG_FSCACHE
+#endif
+
+struct fscache_cookie;
+struct fscache_netfs;
+struct fscache_netfs_operations;
+struct fscache_page;
+
+#define FSCACHE_NEGATIVE_COOKIE		NULL
+
+typedef void (*fscache_rw_complete_t)(void *cookie_data,
+				      struct page *page,
+				      void *data,
+				      int error);
+
+/* result of index entry comparison */
+typedef enum {
+	/* no match */
+	FSCACHE_MATCH_FAILED,
+
+	/* successful match */
+	FSCACHE_MATCH_SUCCESS,
+
+	/* successful match, entry requires update */
+	FSCACHE_MATCH_SUCCESS_UPDATE,
+
+	/* successful match, entry requires deletion */
+	FSCACHE_MATCH_SUCCESS_DELETE,
+} fscache_match_val_t;
+
+/*****************************************************************************/
+/*
+ * fscache index definition
+ * - each index file contains a number of fixed size entries
+ *   - they don't have to fit exactly into a page, but if they don't, the gap
+ *     at the end of the page will not be used
+ */
+struct fscache_index_def
+{
+	/* name of index */
+	uint8_t			name[8];
+
+	/* size of data to be stored in index */
+	uint16_t		data_size;
+
+	/* key description (for displaying in cache mountpoint) */
+	struct {
+		uint8_t		type;
+		uint16_t	len;
+	} keys[4];
+
+#define FSCACHE_INDEX_KEYS_NOTUSED	0
+#define FSCACHE_INDEX_KEYS_BIN		1
+#define FSCACHE_INDEX_KEYS_BIN_SZ1	2
+#define FSCACHE_INDEX_KEYS_BIN_SZ2	3
+#define FSCACHE_INDEX_KEYS_BIN_SZ4	4
+#define FSCACHE_INDEX_KEYS_ASCIIZ	5
+#define FSCACHE_INDEX_KEYS_IPV4ADDR	6
+#define FSCACHE_INDEX_KEYS_IPV6ADDR	7
+#define FSCACHE_INDEX_KEYS__LAST	FSCACHE_INDEX_KEYS_IPV6ADDR
+
+	/* see if entry matches the specified key
+	 * - the netfs data from the cookie being used as the target is
+	 *   presented
+	 * - entries that aren't in use will not be presented for matching
+	 */
+	fscache_match_val_t (*match)(void *target_netfs_data,
+				     const void *entry);
+
+	/* update entry from key
+	 * - the netfs data from the cookie being used as the source is
+	 *   presented
+	 */
+	void (*update)(void *source_netfs_data, void *entry);
+};
+
+/* pattern used to fill dead space in an index entry */
+#define FSCACHE_INDEX_DEADFILL_PATTERN 0x79
+
+#ifdef CONFIG_FSCACHE
+extern struct fscache_cookie *__fscache_acquire_cookie(struct fscache_cookie *iparent,
+						       struct fscache_index_def *idef,
+						       void *netfs_data);
+
+extern void __fscache_relinquish_cookie(struct fscache_cookie *cookie,
+					int retire);
+
+extern void __fscache_update_cookie(struct fscache_cookie *cookie);
+#endif
+
+static inline
+struct fscache_cookie *fscache_acquire_cookie(struct fscache_cookie *iparent,
+					      struct fscache_index_def *idef,
+					      void *netfs_data)
+{
+#ifdef CONFIG_FSCACHE
+	if (iparent != FSCACHE_NEGATIVE_COOKIE)
+		return __fscache_acquire_cookie(iparent, idef, netfs_data);
+#endif
+	return FSCACHE_NEGATIVE_COOKIE;
+}
+
+static inline
+void fscache_relinquish_cookie(struct fscache_cookie *cookie,
+			       int retire)
+{
+#ifdef CONFIG_FSCACHE
+	if (cookie != FSCACHE_NEGATIVE_COOKIE)
+		__fscache_relinquish_cookie(cookie, retire);
+#endif
+}
+
+static inline
+void fscache_update_cookie(struct fscache_cookie *cookie)
+{
+#ifdef CONFIG_FSCACHE
+	if (cookie != FSCACHE_NEGATIVE_COOKIE)
+		__fscache_update_cookie(cookie);
+#endif
+}
+
+/*****************************************************************************/
+/*
+ * fscache cached network filesystem type
+ * - name, version and ops must be filled in before registration
+ * - all other fields will be set during registration
+ */
+struct fscache_netfs
+{
+	const char			*name;		/* filesystem name */
+	unsigned			version;	/* indexing version */
+	struct fscache_cookie		*primary_index;
+	struct fscache_netfs_operations	*ops;
+	struct list_head		link;		/* internal link */
+};
+
+struct fscache_netfs_operations
+{
+	/* get page-to-block mapping token for a page
+	 * - one should be allocated if it doesn't exist
+	 * - returning -ENODATA will cause this page to be ignored
+	 * - typically, the struct will be attached to page->private
+	 */
+	struct fscache_page *(*get_page_token)(struct page *page);
+};
+
+#ifdef CONFIG_FSCACHE
+extern int __fscache_register_netfs(struct fscache_netfs *netfs,
+				    struct fscache_index_def *primary_idef);
+extern void __fscache_unregister_netfs(struct fscache_netfs *netfs);
+#endif
+
+static inline
+int fscache_register_netfs(struct fscache_netfs *netfs,
+			   struct fscache_index_def *primary_idef)
+{
+#ifdef CONFIG_FSCACHE
+	return __fscache_register_netfs(netfs, primary_idef);
+#else
+	return 0;
+#endif
+}
+
+static inline
+void fscache_unregister_netfs(struct fscache_netfs *netfs)
+{
+#ifdef CONFIG_FSCACHE
+	__fscache_unregister_netfs(netfs);
+#endif
+}
+
+/*****************************************************************************/
+/*
+ * page mapping cookie
+ * - stores the mapping of a page to a block in the cache (may also be null)
+ * - note that the mapping may be removed without notice if a cache is removed
+ */
+struct fscache_page
+{
+	void			*mapped_block;	/* block mirroring this page */
+	rwlock_t		lock;
+
+	unsigned long		flags;
+#define FSCACHE_PAGE_BOUNDARY	0	/* next block has a different
+					 * indirection chain */
+#define FSCACHE_PAGE_NEW	1	/* this is a newly allocated block */
+};
+
+/*
+ * read a page from the cache or allocate a block in which to store it
+ * - if the cookie is not backed by a file:
+ *   - -ENOBUFS will be returned and nothing more will be done
+ * - else if the page is backed by a block in the cache:
+ *   - a read will be started which will call end_io_func on completion
+ *   - the wb-journal will be searched for an entry pertaining to this block
+ *     - if an entry is found:
+ *       - 1 will be returned [not yet supported]
+ *       else
+ *       - 0 will be returned
+ * - else if the page is unbacked:
+ *   - a block will be allocated and attached
+ *   - the validity journal will be marked to note the block does not yet
+ *     contain valid data
+ *   - -ENODATA will be returned
+ */
+#ifdef CONFIG_FSCACHE
+extern int __fscache_read_or_alloc_page(struct fscache_cookie *cookie,
+					struct page *page,
+					fscache_rw_complete_t end_io_func,
+					void *end_io_data,
+					unsigned long gfp);
+#endif
+
+static inline
+int fscache_read_or_alloc_page(struct fscache_cookie *cookie,
+			       struct page *page,
+			       fscache_rw_complete_t end_io_func,
+			       void *end_io_data,
+			       unsigned long gfp)
+{
+#ifdef CONFIG_FSCACHE
+	if (cookie != FSCACHE_NEGATIVE_COOKIE)
+		return __fscache_read_or_alloc_page(cookie, page, end_io_func,
+						    end_io_data, gfp);
+#endif
+	return -ENOBUFS;
+}
+
+/*
+ * request a page be stored in the cache
+ * - this request may be ignored if no cache block is currently attached, in
+ *   which case it:
+ *   - returns -ENOBUFS
+ * - if a cache block was already allocated:
+ *   - the page cookie will be updated to reflect the block selected
+ *   - a BIO will be dispatched to write the page (end_io_func will be called
+ *     from the completion function)
+ *     - end_io_func can be NULL, in which case a default function will just
+ *       clear the writeback bit on the page
+ *   - any associated validity journal entry will be cleared
+ *   - returns 0
+ */
+#ifdef CONFIG_FSCACHE
+extern int __fscache_write_page(struct fscache_cookie *cookie,
+				struct page *page,
+				fscache_rw_complete_t end_io_func,
+				void *end_io_data,
+				unsigned long gfp);
+#endif
+
+static inline
+int fscache_write_page(struct fscache_cookie *cookie,
+		       struct page *page,
+		       fscache_rw_complete_t end_io_func,
+		       void *end_io_data,
+		       unsigned long gfp)
+{
+#ifdef CONFIG_FSCACHE
+	if (cookie != FSCACHE_NEGATIVE_COOKIE)
+		return __fscache_write_page(cookie, page, end_io_func,
+					    end_io_data, gfp);
+#endif
+	return -ENOBUFS;
+}
+
+/*
+ * indicate that caching is no longer required on a page
+ * - note: cannot cancel any outstanding BIOs between this page and the cache
+ */
+#ifdef CONFIG_FSCACHE
+extern void __fscache_uncache_page(struct fscache_cookie *cookie,
+				   struct page *page);
+#endif
+
+static inline
+void fscache_uncache_page(struct fscache_cookie *cookie,
+			  struct page *page)
+{
+#ifdef CONFIG_FSCACHE
+	__fscache_uncache_page(cookie, page);
+#endif
+}
+
+/*
+ * keep track of pages changed locally but not yet committed
+ */
+#if 0 /* TODO */
+extern void fscache_writeback_prepare(struct fscache_cookie *cookie,
+				      struct page *page,
+				      unsigned short from,
+				      unsigned short to);
+
+extern void fscache_writeback_committed(struct fscache_cookie *cookie,
+					struct page *page,
+					unsigned short from,
+					unsigned short to);
+
+extern void fscache_writeback_aborted(struct fscache_cookie *cookie,
+				      struct page *page,
+				      unsigned short from,
+				      unsigned short to);
+#endif
+
+/*
+ * convenience routines for mapping page->private directly to a struct
+ * fscache_page
+ */
+static inline
+struct fscache_page *__fscache_page_grab_private(struct page *page)
+{
+	return (struct fscache_page *) (PagePrivate(page) ? page->private : 0);
+}
+
+#define fscache_page_grab_private(X)		\
+({						\
+	BUG_ON(!PagePrivate(X));		\
+	__fscache_page_grab_private(X);		\
+})
+
+
+#ifdef CONFIG_FSCACHE
+extern struct fscache_page *__fscache_page_get_private(struct page *page,
+						       unsigned gfp);
+#endif
+
+static inline
+struct fscache_page *fscache_page_get_private(struct page *page,
+					      unsigned gfp)
+{
+#ifdef CONFIG_FSCACHE
+	return __fscache_page_get_private(page, gfp);
+#else
+	return ERR_PTR(-EIO);
+#endif
+}
+
+#endif /* _LINUX_FSCACHE_H */
_
