All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/4] Split general cache manager from CacheFS
@ 2004-10-06 16:21 David Howells
  0 siblings, 0 replies; only message in thread
From: David Howells @ 2004-10-06 16:21 UTC (permalink / raw)
  To: akpm; +Cc: linux-cachefs, linux-kernel


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>
---

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

diff -uNrp linux-2.6.9-rc3-mm2/fs/Kconfig linux-2.6.9-rc3-mm2-fscache/fs/Kconfig
--- linux-2.6.9-rc3-mm2/fs/Kconfig	2004-10-05 10:38:31.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/Kconfig	2004-10-05 11:22:28.000000000 +0100
@@ -485,10 +485,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 -uNrp linux-2.6.9-rc3-mm2/fs/Makefile linux-2.6.9-rc3-mm2-fscache/fs/Makefile
--- linux-2.6.9-rc3-mm2/fs/Makefile	2004-10-05 10:38:31.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/Makefile	2004-10-05 11:22:28.000000000 +0100
@@ -45,6 +45,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_REISER4_FS)	+= reiser4/
 obj-$(CONFIG_EXT3_FS)		+= ext3/ # Before ext2 so root fs can be ext3
diff -uNrp linux-2.6.9-rc3-mm2/include/linux/fscache-cache.h linux-2.6.9-rc3-mm2-fscache/include/linux/fscache-cache.h
--- linux-2.6.9-rc3-mm2/include/linux/fscache-cache.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/include/linux/fscache-cache.h	2004-10-06 13:15:38.992932649 +0100
@@ -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 -uNrp linux-2.6.9-rc3-mm2/include/linux/fscache.h linux-2.6.9-rc3-mm2-fscache/include/linux/fscache.h
--- linux-2.6.9-rc3-mm2/include/linux/fscache.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/include/linux/fscache.h	2004-10-06 13:15:52.692786627 +0100
@@ -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 */
diff -uNrp linux-2.6.9-rc3-mm2/fs/fscache/cookie.c linux-2.6.9-rc3-mm2-fscache/fs/fscache/cookie.c
--- linux-2.6.9-rc3-mm2/fs/fscache/cookie.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/fscache/cookie.c	2004-10-06 16:36:15.449547547 +0100
@@ -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 -uNrp linux-2.6.9-rc3-mm2/fs/fscache/fscache-int.h linux-2.6.9-rc3-mm2-fscache/fs/fscache/fscache-int.h
--- linux-2.6.9-rc3-mm2/fs/fscache/fscache-int.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/fscache/fscache-int.h	2004-10-05 11:22:28.000000000 +0100
@@ -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 -uNrp linux-2.6.9-rc3-mm2/fs/fscache/fsdef.c linux-2.6.9-rc3-mm2-fscache/fs/fscache/fsdef.c
--- linux-2.6.9-rc3-mm2/fs/fscache/fsdef.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/fscache/fsdef.c	2004-10-06 12:15:31.000000000 +0100
@@ -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 -uNrp linux-2.6.9-rc3-mm2/fs/fscache/main.c linux-2.6.9-rc3-mm2-fscache/fs/fscache/main.c
--- linux-2.6.9-rc3-mm2/fs/fscache/main.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/fscache/main.c	2004-10-06 13:32:12.000000000 +0100
@@ -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 -uNrp linux-2.6.9-rc3-mm2/fs/fscache/Makefile linux-2.6.9-rc3-mm2-fscache/fs/fscache/Makefile
--- linux-2.6.9-rc3-mm2/fs/fscache/Makefile	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/fscache/Makefile	2004-10-05 11:22:28.000000000 +0100
@@ -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 -uNrp linux-2.6.9-rc3-mm2/fs/fscache/page.c linux-2.6.9-rc3-mm2-fscache/fs/fscache/page.c
--- linux-2.6.9-rc3-mm2/fs/fscache/page.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.9-rc3-mm2-fscache/fs/fscache/page.c	2004-10-05 11:22:28.000000000 +0100
@@ -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);

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2004-10-06 16:26 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2004-10-06 16:21 [PATCH 1/4] Split general cache manager from CacheFS David Howells

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.