summary refs log tree commit diff
path: root/fs/afs
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /fs/afs
downloadlinux-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
Diffstat (limited to 'fs/afs')
-rw-r--r--fs/afs/Makefile28
-rw-r--r--fs/afs/cache.h27
-rw-r--r--fs/afs/callback.c168
-rw-r--r--fs/afs/cell.c569
-rw-r--r--fs/afs/cell.h78
-rw-r--r--fs/afs/cmservice.c652
-rw-r--r--fs/afs/cmservice.h29
-rw-r--r--fs/afs/dir.c666
-rw-r--r--fs/afs/errors.h34
-rw-r--r--fs/afs/file.c305
-rw-r--r--fs/afs/fsclient.c837
-rw-r--r--fs/afs/fsclient.h54
-rw-r--r--fs/afs/inode.c287
-rw-r--r--fs/afs/internal.h140
-rw-r--r--fs/afs/kafsasyncd.c257
-rw-r--r--fs/afs/kafsasyncd.h52
-rw-r--r--fs/afs/kafstimod.c204
-rw-r--r--fs/afs/kafstimod.h49
-rw-r--r--fs/afs/main.c286
-rw-r--r--fs/afs/misc.c39
-rw-r--r--fs/afs/mntpt.c287
-rw-r--r--fs/afs/mount.h23
-rw-r--r--fs/afs/proc.c857
-rw-r--r--fs/afs/server.c502
-rw-r--r--fs/afs/server.h102
-rw-r--r--fs/afs/super.c441
-rw-r--r--fs/afs/super.h43
-rw-r--r--fs/afs/transport.h21
-rw-r--r--fs/afs/types.h125
-rw-r--r--fs/afs/vlclient.c695
-rw-r--r--fs/afs/vlclient.h93
-rw-r--r--fs/afs/vlocation.c954
-rw-r--r--fs/afs/vnode.c395
-rw-r--r--fs/afs/vnode.h94
-rw-r--r--fs/afs/volume.c520
-rw-r--r--fs/afs/volume.h142
36 files changed, 10055 insertions, 0 deletions
diff --git a/fs/afs/Makefile b/fs/afs/Makefile
new file mode 100644
index 000000000000..4029c9da4b86
--- /dev/null
+++ b/fs/afs/Makefile
@@ -0,0 +1,28 @@
+#
+# Makefile for Red Hat Linux AFS client.
+#
+
+#CFLAGS += -finstrument-functions
+
+kafs-objs := \
+	callback.o \
+	cell.o \
+	cmservice.o \
+	dir.o \
+	file.o \
+	fsclient.o \
+	inode.o \
+	kafsasyncd.o \
+	kafstimod.o \
+	main.o \
+	misc.o \
+	mntpt.o \
+	proc.o \
+	server.o \
+	super.o \
+	vlclient.o \
+	vlocation.o \
+	vnode.o \
+	volume.o
+
+obj-$(CONFIG_AFS_FS)  := kafs.o
diff --git a/fs/afs/cache.h b/fs/afs/cache.h
new file mode 100644
index 000000000000..9eb7722b34d5
--- /dev/null
+++ b/fs/afs/cache.h
@@ -0,0 +1,27 @@
+/* cache.h: AFS local cache management interface
+ *
+ * Copyright (C) 2002 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_AFS_CACHE_H
+#define _LINUX_AFS_CACHE_H
+
+#undef AFS_CACHING_SUPPORT
+
+#include <linux/mm.h>
+#ifdef AFS_CACHING_SUPPORT
+#include <linux/cachefs.h>
+#endif
+#include "types.h"
+
+#ifdef __KERNEL__
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_AFS_CACHE_H */
diff --git a/fs/afs/callback.c b/fs/afs/callback.c
new file mode 100644
index 000000000000..2fd62f89ae01
--- /dev/null
+++ b/fs/afs/callback.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2002 Red Hat, Inc. All rights reserved.
+ *
+ * This software may be freely redistributed under the terms of the
+ * GNU General Public License.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: David Woodhouse <dwmw2@cambridge.redhat.com>
+ *          David Howells <dhowells@redhat.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include "server.h"
+#include "vnode.h"
+#include "internal.h"
+
+/*****************************************************************************/
+/*
+ * allow the fileserver to request callback state (re-)initialisation
+ */
+int SRXAFSCM_InitCallBackState(struct afs_server *server)
+{
+	struct list_head callbacks;
+
+	_enter("%p", server);
+
+	INIT_LIST_HEAD(&callbacks);
+
+	/* transfer the callback list from the server to a temp holding area */
+	spin_lock(&server->cb_lock);
+
+	list_add(&callbacks, &server->cb_promises);
+	list_del_init(&server->cb_promises);
+
+	/* munch our way through the list, grabbing the inode, dropping all the
+	 * locks and regetting them in the right order
+	 */
+	while (!list_empty(&callbacks)) {
+		struct afs_vnode *vnode;
+		struct inode *inode;
+
+		vnode = list_entry(callbacks.next, struct afs_vnode, cb_link);
+		list_del_init(&vnode->cb_link);
+
+		/* try and grab the inode - may fail */
+		inode = igrab(AFS_VNODE_TO_I(vnode));
+		if (inode) {
+			int release = 0;
+
+			spin_unlock(&server->cb_lock);
+			spin_lock(&vnode->lock);
+
+			if (vnode->cb_server == server) {
+				vnode->cb_server = NULL;
+				afs_kafstimod_del_timer(&vnode->cb_timeout);
+				spin_lock(&afs_cb_hash_lock);
+				list_del_init(&vnode->cb_hash_link);
+				spin_unlock(&afs_cb_hash_lock);
+				release = 1;
+			}
+
+			spin_unlock(&vnode->lock);
+
+			iput(inode);
+			afs_put_server(server);
+
+			spin_lock(&server->cb_lock);
+		}
+	}
+
+	spin_unlock(&server->cb_lock);
+
+	_leave(" = 0");
+	return 0;
+} /* end SRXAFSCM_InitCallBackState() */
+
+/*****************************************************************************/
+/*
+ * allow the fileserver to break callback promises
+ */
+int SRXAFSCM_CallBack(struct afs_server *server, size_t count,
+		      struct afs_callback callbacks[])
+{
+	_enter("%p,%u,", server, count);
+
+	for (; count > 0; callbacks++, count--) {
+		struct afs_vnode *vnode = NULL;
+		struct inode *inode = NULL;
+		int valid = 0;
+
+		_debug("- Fid { vl=%08x n=%u u=%u }  CB { v=%u x=%u t=%u }",
+		       callbacks->fid.vid,
+		       callbacks->fid.vnode,
+		       callbacks->fid.unique,
+		       callbacks->version,
+		       callbacks->expiry,
+		       callbacks->type
+		       );
+
+		/* find the inode for this fid */
+		spin_lock(&afs_cb_hash_lock);
+
+		list_for_each_entry(vnode,
+				    &afs_cb_hash(server, &callbacks->fid),
+				    cb_hash_link) {
+			if (memcmp(&vnode->fid, &callbacks->fid,
+				   sizeof(struct afs_fid)) != 0)
+				continue;
+
+			/* right vnode, but is it same server? */
+			if (vnode->cb_server != server)
+				break; /* no */
+
+			/* try and nail the inode down */
+			inode = igrab(AFS_VNODE_TO_I(vnode));
+			break;
+		}
+
+		spin_unlock(&afs_cb_hash_lock);
+
+		if (inode) {
+			/* we've found the record for this vnode */
+			spin_lock(&vnode->lock);
+			if (vnode->cb_server == server) {
+				/* the callback _is_ on the calling server */
+				vnode->cb_server = NULL;
+				valid = 1;
+
+				afs_kafstimod_del_timer(&vnode->cb_timeout);
+				vnode->flags |= AFS_VNODE_CHANGED;
+
+				spin_lock(&server->cb_lock);
+				list_del_init(&vnode->cb_link);
+				spin_unlock(&server->cb_lock);
+
+				spin_lock(&afs_cb_hash_lock);
+				list_del_init(&vnode->cb_hash_link);
+				spin_unlock(&afs_cb_hash_lock);
+			}
+			spin_unlock(&vnode->lock);
+
+			if (valid) {
+				invalidate_remote_inode(inode);
+				afs_put_server(server);
+			}
+			iput(inode);
+		}
+	}
+
+	_leave(" = 0");
+	return 0;
+} /* end SRXAFSCM_CallBack() */
+
+/*****************************************************************************/
+/*
+ * allow the fileserver to see if the cache manager is still alive
+ */
+int SRXAFSCM_Probe(struct afs_server *server)
+{
+	_debug("SRXAFSCM_Probe(%p)\n", server);
+	return 0;
+} /* end SRXAFSCM_Probe() */
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
new file mode 100644
index 000000000000..009a9ae88d61
--- /dev/null
+++ b/fs/afs/cell.c
@@ -0,0 +1,569 @@
+/* cell.c: AFS cell and server record management
+ *
+ * Copyright (C) 2002 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/sched.h>
+#include <linux/slab.h>
+#include <rxrpc/peer.h>
+#include <rxrpc/connection.h>
+#include "volume.h"
+#include "cell.h"
+#include "server.h"
+#include "transport.h"
+#include "vlclient.h"
+#include "kafstimod.h"
+#include "super.h"
+#include "internal.h"
+
+DECLARE_RWSEM(afs_proc_cells_sem);
+LIST_HEAD(afs_proc_cells);
+
+static struct list_head afs_cells = LIST_HEAD_INIT(afs_cells);
+static DEFINE_RWLOCK(afs_cells_lock);
+static DECLARE_RWSEM(afs_cells_sem); /* add/remove serialisation */
+static struct afs_cell *afs_cell_root;
+
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_cell_cache_match(void *target,
+						const void *entry);
+static void afs_cell_cache_update(void *source, void *entry);
+
+struct cachefs_index_def afs_cache_cell_index_def = {
+	.name			= "cell_ix",
+	.data_size		= sizeof(struct afs_cache_cell),
+	.keys[0]		= { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
+	.match			= afs_cell_cache_match,
+	.update			= afs_cell_cache_update,
+};
+#endif
+
+/*****************************************************************************/
+/*
+ * create a cell record
+ * - "name" is the name of the cell
+ * - "vllist" is a colon separated list of IP addresses in "a.b.c.d" format
+ */
+int afs_cell_create(const char *name, char *vllist, struct afs_cell **_cell)
+{
+	struct afs_cell *cell;
+	char *next;
+	int ret;
+
+	_enter("%s", name);
+
+	BUG_ON(!name); /* TODO: want to look up "this cell" in the cache */
+
+	/* allocate and initialise a cell record */
+	cell = kmalloc(sizeof(struct afs_cell) + strlen(name) + 1, GFP_KERNEL);
+	if (!cell) {
+		_leave(" = -ENOMEM");
+		return -ENOMEM;
+	}
+
+	down_write(&afs_cells_sem);
+
+	memset(cell, 0, sizeof(struct afs_cell));
+	atomic_set(&cell->usage, 0);
+
+	INIT_LIST_HEAD(&cell->link);
+
+	rwlock_init(&cell->sv_lock);
+	INIT_LIST_HEAD(&cell->sv_list);
+	INIT_LIST_HEAD(&cell->sv_graveyard);
+	spin_lock_init(&cell->sv_gylock);
+
+	init_rwsem(&cell->vl_sem);
+	INIT_LIST_HEAD(&cell->vl_list);
+	INIT_LIST_HEAD(&cell->vl_graveyard);
+	spin_lock_init(&cell->vl_gylock);
+
+	strcpy(cell->name,name);
+
+	/* fill in the VL server list from the rest of the string */
+	ret = -EINVAL;
+	do {
+		unsigned a, b, c, d;
+
+		next = strchr(vllist, ':');
+		if (next)
+			*next++ = 0;
+
+		if (sscanf(vllist, "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
+			goto badaddr;
+
+		if (a > 255 || b > 255 || c > 255 || d > 255)
+			goto badaddr;
+
+		cell->vl_addrs[cell->vl_naddrs++].s_addr =
+			htonl((a << 24) | (b << 16) | (c << 8) | d);
+
+		if (cell->vl_naddrs >= AFS_CELL_MAX_ADDRS)
+			break;
+
+	} while(vllist = next, vllist);
+
+	/* add a proc dir for this cell */
+	ret = afs_proc_cell_setup(cell);
+	if (ret < 0)
+		goto error;
+
+#ifdef AFS_CACHING_SUPPORT
+	/* put it up for caching */
+	cachefs_acquire_cookie(afs_cache_netfs.primary_index,
+			       &afs_vlocation_cache_index_def,
+			       cell,
+			       &cell->cache);
+#endif
+
+	/* add to the cell lists */
+	write_lock(&afs_cells_lock);
+	list_add_tail(&cell->link, &afs_cells);
+	write_unlock(&afs_cells_lock);
+
+	down_write(&afs_proc_cells_sem);
+	list_add_tail(&cell->proc_link, &afs_proc_cells);
+	up_write(&afs_proc_cells_sem);
+
+	*_cell = cell;
+	up_write(&afs_cells_sem);
+
+	_leave(" = 0 (%p)", cell);
+	return 0;
+
+ badaddr:
+	printk(KERN_ERR "kAFS: bad VL server IP address: '%s'\n", vllist);
+ error:
+	up_write(&afs_cells_sem);
+	kfree(cell);
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_cell_create() */
+
+/*****************************************************************************/
+/*
+ * initialise the cell database from module parameters
+ */
+int afs_cell_init(char *rootcell)
+{
+	struct afs_cell *old_root, *new_root;
+	char *cp;
+	int ret;
+
+	_enter("");
+
+	if (!rootcell) {
+		/* module is loaded with no parameters, or built statically.
+		 * - in the future we might initialize cell DB here.
+		 */
+		_leave(" = 0 (but no root)");
+		return 0;
+	}
+
+	cp = strchr(rootcell, ':');
+	if (!cp) {
+		printk(KERN_ERR "kAFS: no VL server IP addresses specified\n");
+		_leave(" = %d (no colon)", -EINVAL);
+		return -EINVAL;
+	}
+
+	/* allocate a cell record for the root cell */
+	*cp++ = 0;
+	ret = afs_cell_create(rootcell, cp, &new_root);
+	if (ret < 0) {
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+	/* as afs_put_cell() takes locks by itself, we have to do
+	 * a little gymnastics to be race-free.
+	 */
+	afs_get_cell(new_root);
+
+	write_lock(&afs_cells_lock);
+	while (afs_cell_root) {
+		old_root = afs_cell_root;
+		afs_cell_root = NULL;
+		write_unlock(&afs_cells_lock);
+		afs_put_cell(old_root);
+		write_lock(&afs_cells_lock);
+	}
+	afs_cell_root = new_root;
+	write_unlock(&afs_cells_lock);
+
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_cell_init() */
+
+/*****************************************************************************/
+/*
+ * lookup a cell record
+ */
+int afs_cell_lookup(const char *name, unsigned namesz, struct afs_cell **_cell)
+{
+	struct afs_cell *cell;
+	int ret;
+
+	_enter("\"%*.*s\",", namesz, namesz, name ? name : "");
+
+	*_cell = NULL;
+
+	if (name) {
+		/* if the cell was named, look for it in the cell record list */
+		ret = -ENOENT;
+		cell = NULL;
+		read_lock(&afs_cells_lock);
+
+		list_for_each_entry(cell, &afs_cells, link) {
+			if (strncmp(cell->name, name, namesz) == 0) {
+				afs_get_cell(cell);
+				goto found;
+			}
+		}
+		cell = NULL;
+	found:
+
+		read_unlock(&afs_cells_lock);
+
+		if (cell)
+			ret = 0;
+	}
+	else {
+		read_lock(&afs_cells_lock);
+
+		cell = afs_cell_root;
+		if (!cell) {
+			/* this should not happen unless user tries to mount
+			 * when root cell is not set. Return an impossibly
+			 * bizzare errno to alert the user. Things like
+			 * ENOENT might be "more appropriate" but they happen
+			 * for other reasons.
+			 */
+			ret = -EDESTADDRREQ;
+		}
+		else {
+			afs_get_cell(cell);
+			ret = 0;
+		}
+
+		read_unlock(&afs_cells_lock);
+	}
+
+	*_cell = cell;
+	_leave(" = %d (%p)", ret, cell);
+	return ret;
+
+} /* end afs_cell_lookup() */
+
+/*****************************************************************************/
+/*
+ * try and get a cell record
+ */
+struct afs_cell *afs_get_cell_maybe(struct afs_cell **_cell)
+{
+	struct afs_cell *cell;
+
+	write_lock(&afs_cells_lock);
+
+	cell = *_cell;
+	if (cell && !list_empty(&cell->link))
+		afs_get_cell(cell);
+	else
+		cell = NULL;
+
+	write_unlock(&afs_cells_lock);
+
+	return cell;
+} /* end afs_get_cell_maybe() */
+
+/*****************************************************************************/
+/*
+ * destroy a cell record
+ */
+void afs_put_cell(struct afs_cell *cell)
+{
+	if (!cell)
+		return;
+
+	_enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name);
+
+	/* sanity check */
+	BUG_ON(atomic_read(&cell->usage) <= 0);
+
+	/* to prevent a race, the decrement and the dequeue must be effectively
+	 * atomic */
+	write_lock(&afs_cells_lock);
+
+	if (likely(!atomic_dec_and_test(&cell->usage))) {
+		write_unlock(&afs_cells_lock);
+		_leave("");
+		return;
+	}
+
+	write_unlock(&afs_cells_lock);
+
+	BUG_ON(!list_empty(&cell->sv_list));
+	BUG_ON(!list_empty(&cell->sv_graveyard));
+	BUG_ON(!list_empty(&cell->vl_list));
+	BUG_ON(!list_empty(&cell->vl_graveyard));
+
+	_leave(" [unused]");
+} /* end afs_put_cell() */
+
+/*****************************************************************************/
+/*
+ * destroy a cell record
+ */
+static void afs_cell_destroy(struct afs_cell *cell)
+{
+	_enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name);
+
+	/* to prevent a race, the decrement and the dequeue must be effectively
+	 * atomic */
+	write_lock(&afs_cells_lock);
+
+	/* sanity check */
+	BUG_ON(atomic_read(&cell->usage) != 0);
+
+	list_del_init(&cell->link);
+
+	write_unlock(&afs_cells_lock);
+
+	down_write(&afs_cells_sem);
+
+	afs_proc_cell_remove(cell);
+
+	down_write(&afs_proc_cells_sem);
+	list_del_init(&cell->proc_link);
+	up_write(&afs_proc_cells_sem);
+
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_relinquish_cookie(cell->cache, 0);
+#endif
+
+	up_write(&afs_cells_sem);
+
+	BUG_ON(!list_empty(&cell->sv_list));
+	BUG_ON(!list_empty(&cell->sv_graveyard));
+	BUG_ON(!list_empty(&cell->vl_list));
+	BUG_ON(!list_empty(&cell->vl_graveyard));
+
+	/* finish cleaning up the cell */
+	kfree(cell);
+
+	_leave(" [destroyed]");
+} /* end afs_cell_destroy() */
+
+/*****************************************************************************/
+/*
+ * lookup the server record corresponding to an Rx RPC peer
+ */
+int afs_server_find_by_peer(const struct rxrpc_peer *peer,
+			    struct afs_server **_server)
+{
+	struct afs_server *server;
+	struct afs_cell *cell;
+
+	_enter("%p{a=%08x},", peer, ntohl(peer->addr.s_addr));
+
+	/* search the cell list */
+	read_lock(&afs_cells_lock);
+
+	list_for_each_entry(cell, &afs_cells, link) {
+
+		_debug("? cell %s",cell->name);
+
+		write_lock(&cell->sv_lock);
+
+		/* check the active list */
+		list_for_each_entry(server, &cell->sv_list, link) {
+			_debug("?? server %08x", ntohl(server->addr.s_addr));
+
+			if (memcmp(&server->addr, &peer->addr,
+				   sizeof(struct in_addr)) == 0)
+				goto found_server;
+		}
+
+		/* check the inactive list */
+		spin_lock(&cell->sv_gylock);
+		list_for_each_entry(server, &cell->sv_graveyard, link) {
+			_debug("?? dead server %08x",
+			       ntohl(server->addr.s_addr));
+
+			if (memcmp(&server->addr, &peer->addr,
+				   sizeof(struct in_addr)) == 0)
+				goto found_dead_server;
+		}
+		spin_unlock(&cell->sv_gylock);
+
+		write_unlock(&cell->sv_lock);
+	}
+	read_unlock(&afs_cells_lock);
+
+	_leave(" = -ENOENT");
+	return -ENOENT;
+
+	/* we found it in the graveyard - resurrect it */
+ found_dead_server:
+	list_del(&server->link);
+	list_add_tail(&server->link, &cell->sv_list);
+	afs_get_server(server);
+	afs_kafstimod_del_timer(&server->timeout);
+	spin_unlock(&cell->sv_gylock);
+	goto success;
+
+	/* we found it - increment its ref count and return it */
+ found_server:
+	afs_get_server(server);
+
+ success:
+	write_unlock(&cell->sv_lock);
+	read_unlock(&afs_cells_lock);
+
+	*_server = server;
+	_leave(" = 0 (s=%p c=%p)", server, cell);
+	return 0;
+
+} /* end afs_server_find_by_peer() */
+
+/*****************************************************************************/
+/*
+ * purge in-memory cell database on module unload or afs_init() failure
+ * - the timeout daemon is stopped before calling this
+ */
+void afs_cell_purge(void)
+{
+	struct afs_vlocation *vlocation;
+	struct afs_cell *cell;
+
+	_enter("");
+
+	afs_put_cell(afs_cell_root);
+
+	while (!list_empty(&afs_cells)) {
+		cell = NULL;
+
+		/* remove the next cell from the front of the list */
+		write_lock(&afs_cells_lock);
+
+		if (!list_empty(&afs_cells)) {
+			cell = list_entry(afs_cells.next,
+					  struct afs_cell, link);
+			list_del_init(&cell->link);
+		}
+
+		write_unlock(&afs_cells_lock);
+
+		if (cell) {
+			_debug("PURGING CELL %s (%d)",
+			       cell->name, atomic_read(&cell->usage));
+
+			BUG_ON(!list_empty(&cell->sv_list));
+			BUG_ON(!list_empty(&cell->vl_list));
+
+			/* purge the cell's VL graveyard list */
+			_debug(" - clearing VL graveyard");
+
+			spin_lock(&cell->vl_gylock);
+
+			while (!list_empty(&cell->vl_graveyard)) {
+				vlocation = list_entry(cell->vl_graveyard.next,
+						       struct afs_vlocation,
+						       link);
+				list_del_init(&vlocation->link);
+
+				afs_kafstimod_del_timer(&vlocation->timeout);
+
+				spin_unlock(&cell->vl_gylock);
+
+				afs_vlocation_do_timeout(vlocation);
+				/* TODO: race if move to use krxtimod instead
+				 * of kafstimod */
+
+				spin_lock(&cell->vl_gylock);
+			}
+
+			spin_unlock(&cell->vl_gylock);
+
+			/* purge the cell's server graveyard list */
+			_debug(" - clearing server graveyard");
+
+			spin_lock(&cell->sv_gylock);
+
+			while (!list_empty(&cell->sv_graveyard)) {
+				struct afs_server *server;
+
+				server = list_entry(cell->sv_graveyard.next,
+						    struct afs_server, link);
+				list_del_init(&server->link);
+
+				afs_kafstimod_del_timer(&server->timeout);
+
+				spin_unlock(&cell->sv_gylock);
+
+				afs_server_do_timeout(server);
+
+				spin_lock(&cell->sv_gylock);
+			}
+
+			spin_unlock(&cell->sv_gylock);
+
+			/* now the cell should be left with no references */
+			afs_cell_destroy(cell);
+		}
+	}
+
+	_leave("");
+} /* end afs_cell_purge() */
+
+/*****************************************************************************/
+/*
+ * match a cell record obtained from the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_cell_cache_match(void *target,
+						const void *entry)
+{
+	const struct afs_cache_cell *ccell = entry;
+	struct afs_cell *cell = target;
+
+	_enter("{%s},{%s}", ccell->name, cell->name);
+
+	if (strncmp(ccell->name, cell->name, sizeof(ccell->name)) == 0) {
+		_leave(" = SUCCESS");
+		return CACHEFS_MATCH_SUCCESS;
+	}
+
+	_leave(" = FAILED");
+	return CACHEFS_MATCH_FAILED;
+} /* end afs_cell_cache_match() */
+#endif
+
+/*****************************************************************************/
+/*
+ * update a cell record in the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static void afs_cell_cache_update(void *source, void *entry)
+{
+	struct afs_cache_cell *ccell = entry;
+	struct afs_cell *cell = source;
+
+	_enter("%p,%p", source, entry);
+
+	strncpy(ccell->name, cell->name, sizeof(ccell->name));
+
+	memcpy(ccell->vl_servers,
+	       cell->vl_addrs,
+	       min(sizeof(ccell->vl_servers), sizeof(cell->vl_addrs)));
+
+} /* end afs_cell_cache_update() */
+#endif
diff --git a/fs/afs/cell.h b/fs/afs/cell.h
new file mode 100644
index 000000000000..48349108fb00
--- /dev/null
+++ b/fs/afs/cell.h
@@ -0,0 +1,78 @@
+/* cell.h: AFS cell record
+ *
+ * Copyright (C) 2002 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_AFS_CELL_H
+#define _LINUX_AFS_CELL_H
+
+#include "types.h"
+#include "cache.h"
+
+#define AFS_CELL_MAX_ADDRS 15
+
+extern volatile int afs_cells_being_purged; /* T when cells are being purged by rmmod */
+
+/*****************************************************************************/
+/*
+ * entry in the cached cell catalogue
+ */
+struct afs_cache_cell
+{
+	char			name[64];	/* cell name (padded with NULs) */
+	struct in_addr		vl_servers[15];	/* cached cell VL servers */
+};
+
+/*****************************************************************************/
+/*
+ * AFS cell record
+ */
+struct afs_cell
+{
+	atomic_t		usage;
+	struct list_head	link;		/* main cell list link */
+	struct list_head	proc_link;	/* /proc cell list link */
+	struct proc_dir_entry	*proc_dir;	/* /proc dir for this cell */
+#ifdef AFS_CACHING_SUPPORT
+	struct cachefs_cookie	*cache;		/* caching cookie */
+#endif
+
+	/* server record management */
+	rwlock_t		sv_lock;	/* active server list lock */
+	struct list_head	sv_list;	/* active server list */
+	struct list_head	sv_graveyard;	/* inactive server list */
+	spinlock_t		sv_gylock;	/* inactive server list lock */
+
+	/* volume location record management */
+	struct rw_semaphore	vl_sem;		/* volume management serialisation semaphore */
+	struct list_head	vl_list;	/* cell's active VL record list */
+	struct list_head	vl_graveyard;	/* cell's inactive VL record list */
+	spinlock_t		vl_gylock;	/* graveyard lock */
+	unsigned short		vl_naddrs;	/* number of VL servers in addr list */
+	unsigned short		vl_curr_svix;	/* current server index */
+	struct in_addr		vl_addrs[AFS_CELL_MAX_ADDRS];	/* cell VL server addresses */
+
+	char			name[0];	/* cell name - must go last */
+};
+
+extern int afs_cell_init(char *rootcell);
+
+extern int afs_cell_create(const char *name, char *vllist, struct afs_cell **_cell);
+
+extern int afs_cell_lookup(const char *name, unsigned nmsize, struct afs_cell **_cell);
+
+#define afs_get_cell(C) do { atomic_inc(&(C)->usage); } while(0)
+
+extern struct afs_cell *afs_get_cell_maybe(struct afs_cell **_cell);
+
+extern void afs_put_cell(struct afs_cell *cell);
+
+extern void afs_cell_purge(void);
+
+#endif /* _LINUX_AFS_CELL_H */
diff --git a/fs/afs/cmservice.c b/fs/afs/cmservice.c
new file mode 100644
index 000000000000..0a57fd7c726f
--- /dev/null
+++ b/fs/afs/cmservice.c
@@ -0,0 +1,652 @@
+/* cmservice.c: AFS Cache Manager Service
+ *
+ * Copyright (C) 2002 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 "server.h"
+#include "cell.h"
+#include "transport.h"
+#include <rxrpc/rxrpc.h>
+#include <rxrpc/transport.h>
+#include <rxrpc/connection.h>
+#include <rxrpc/call.h>
+#include "cmservice.h"
+#include "internal.h"
+
+static unsigned afscm_usage;		/* AFS cache manager usage count */
+static struct rw_semaphore afscm_sem;	/* AFS cache manager start/stop semaphore */
+
+static int afscm_new_call(struct rxrpc_call *call);
+static void afscm_attention(struct rxrpc_call *call);
+static void afscm_error(struct rxrpc_call *call);
+static void afscm_aemap(struct rxrpc_call *call);
+
+static void _SRXAFSCM_CallBack(struct rxrpc_call *call);
+static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call);
+static void _SRXAFSCM_Probe(struct rxrpc_call *call);
+
+typedef void (*_SRXAFSCM_xxxx_t)(struct rxrpc_call *call);
+
+static const struct rxrpc_operation AFSCM_ops[] = {
+	{
+		.id	= 204,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "CallBack",
+		.user	= _SRXAFSCM_CallBack,
+	},
+	{
+		.id	= 205,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "InitCallBackState",
+		.user	= _SRXAFSCM_InitCallBackState,
+	},
+	{
+		.id	= 206,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "Probe",
+		.user	= _SRXAFSCM_Probe,
+	},
+#if 0
+	{
+		.id	= 207,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "GetLock",
+		.user	= _SRXAFSCM_GetLock,
+	},
+	{
+		.id	= 208,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "GetCE",
+		.user	= _SRXAFSCM_GetCE,
+	},
+	{
+		.id	= 209,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "GetXStatsVersion",
+		.user	= _SRXAFSCM_GetXStatsVersion,
+	},
+	{
+		.id	= 210,
+		.asize	= RXRPC_APP_MARK_EOF,
+		.name	= "GetXStats",
+		.user	= _SRXAFSCM_GetXStats,
+	}
+#endif
+};
+
+static struct rxrpc_service AFSCM_service = {
+	.name		= "AFS/CM",
+	.owner		= THIS_MODULE,
+	.link		= LIST_HEAD_INIT(AFSCM_service.link),
+	.new_call	= afscm_new_call,
+	.service_id	= 1,
+	.attn_func	= afscm_attention,
+	.error_func	= afscm_error,
+	.aemap_func	= afscm_aemap,
+	.ops_begin	= &AFSCM_ops[0],
+	.ops_end	= &AFSCM_ops[sizeof(AFSCM_ops) / sizeof(AFSCM_ops[0])],
+};
+
+static DECLARE_COMPLETION(kafscmd_alive);
+static DECLARE_COMPLETION(kafscmd_dead);
+static DECLARE_WAIT_QUEUE_HEAD(kafscmd_sleepq);
+static LIST_HEAD(kafscmd_attention_list);
+static LIST_HEAD(afscm_calls);
+static DEFINE_SPINLOCK(afscm_calls_lock);
+static DEFINE_SPINLOCK(kafscmd_attention_lock);
+static int kafscmd_die;
+
+/*****************************************************************************/
+/*
+ * AFS Cache Manager kernel thread
+ */
+static int kafscmd(void *arg)
+{
+	DECLARE_WAITQUEUE(myself, current);
+
+	struct rxrpc_call *call;
+	_SRXAFSCM_xxxx_t func;
+	int die;
+
+	printk("kAFS: Started kafscmd %d\n", current->pid);
+
+	daemonize("kafscmd");
+
+	complete(&kafscmd_alive);
+
+	/* loop around looking for things to attend to */
+	do {
+		if (list_empty(&kafscmd_attention_list)) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			add_wait_queue(&kafscmd_sleepq, &myself);
+
+			for (;;) {
+				set_current_state(TASK_INTERRUPTIBLE);
+				if (!list_empty(&kafscmd_attention_list) ||
+				    signal_pending(current) ||
+				    kafscmd_die)
+					break;
+
+				schedule();
+			}
+
+			remove_wait_queue(&kafscmd_sleepq, &myself);
+			set_current_state(TASK_RUNNING);
+		}
+
+		die = kafscmd_die;
+
+		/* dequeue the next call requiring attention */
+		call = NULL;
+		spin_lock(&kafscmd_attention_lock);
+
+		if (!list_empty(&kafscmd_attention_list)) {
+			call = list_entry(kafscmd_attention_list.next,
+					  struct rxrpc_call,
+					  app_attn_link);
+			list_del_init(&call->app_attn_link);
+			die = 0;
+		}
+
+		spin_unlock(&kafscmd_attention_lock);
+
+		if (call) {
+			/* act upon it */
+			_debug("@@@ Begin Attend Call %p", call);
+
+			func = call->app_user;
+			if (func)
+				func(call);
+
+			rxrpc_put_call(call);
+
+			_debug("@@@ End Attend Call %p", call);
+		}
+
+	} while(!die);
+
+	/* and that's all */
+	complete_and_exit(&kafscmd_dead, 0);
+
+} /* end kafscmd() */
+
+/*****************************************************************************/
+/*
+ * handle a call coming in to the cache manager
+ * - if I want to keep the call, I must increment its usage count
+ * - the return value will be negated and passed back in an abort packet if
+ *   non-zero
+ * - serialised by virtue of there only being one krxiod
+ */
+static int afscm_new_call(struct rxrpc_call *call)
+{
+	_enter("%p{cid=%u u=%d}",
+	       call, ntohl(call->call_id), atomic_read(&call->usage));
+
+	rxrpc_get_call(call);
+
+	/* add to my current call list */
+	spin_lock(&afscm_calls_lock);
+	list_add(&call->app_link,&afscm_calls);
+	spin_unlock(&afscm_calls_lock);
+
+	_leave(" = 0");
+	return 0;
+
+} /* end afscm_new_call() */
+
+/*****************************************************************************/
+/*
+ * queue on the kafscmd queue for attention
+ */
+static void afscm_attention(struct rxrpc_call *call)
+{
+	_enter("%p{cid=%u u=%d}",
+	       call, ntohl(call->call_id), atomic_read(&call->usage));
+
+	spin_lock(&kafscmd_attention_lock);
+
+	if (list_empty(&call->app_attn_link)) {
+		list_add_tail(&call->app_attn_link, &kafscmd_attention_list);
+		rxrpc_get_call(call);
+	}
+
+	spin_unlock(&kafscmd_attention_lock);
+
+	wake_up(&kafscmd_sleepq);
+
+	_leave(" {u=%d}", atomic_read(&call->usage));
+} /* end afscm_attention() */
+
+/*****************************************************************************/
+/*
+ * handle my call being aborted
+ * - clean up, dequeue and put my ref to the call
+ */
+static void afscm_error(struct rxrpc_call *call)
+{
+	int removed;
+
+	_enter("%p{est=%s ac=%u er=%d}",
+	       call,
+	       rxrpc_call_error_states[call->app_err_state],
+	       call->app_abort_code,
+	       call->app_errno);
+
+	spin_lock(&kafscmd_attention_lock);
+
+	if (list_empty(&call->app_attn_link)) {
+		list_add_tail(&call->app_attn_link, &kafscmd_attention_list);
+		rxrpc_get_call(call);
+	}
+
+	spin_unlock(&kafscmd_attention_lock);
+
+	removed = 0;
+	spin_lock(&afscm_calls_lock);
+	if (!list_empty(&call->app_link)) {
+		list_del_init(&call->app_link);
+		removed = 1;
+	}
+	spin_unlock(&afscm_calls_lock);
+
+	if (removed)
+		rxrpc_put_call(call);
+
+	wake_up(&kafscmd_sleepq);
+
+	_leave("");
+} /* end afscm_error() */
+
+/*****************************************************************************/
+/*
+ * map afs abort codes to/from Linux error codes
+ * - called with call->lock held
+ */
+static void afscm_aemap(struct rxrpc_call *call)
+{
+	switch (call->app_err_state) {
+	case RXRPC_ESTATE_LOCAL_ABORT:
+		call->app_abort_code = -call->app_errno;
+		break;
+	case RXRPC_ESTATE_PEER_ABORT:
+		call->app_errno = -ECONNABORTED;
+		break;
+	default:
+		break;
+	}
+} /* end afscm_aemap() */
+
+/*****************************************************************************/
+/*
+ * start the cache manager service if not already started
+ */
+int afscm_start(void)
+{
+	int ret;
+
+	down_write(&afscm_sem);
+	if (!afscm_usage) {
+		ret = kernel_thread(kafscmd, NULL, 0);
+		if (ret < 0)
+			goto out;
+
+		wait_for_completion(&kafscmd_alive);
+
+		ret = rxrpc_add_service(afs_transport, &AFSCM_service);
+		if (ret < 0)
+			goto kill;
+
+		afs_kafstimod_add_timer(&afs_mntpt_expiry_timer,
+					afs_mntpt_expiry_timeout * HZ);
+	}
+
+	afscm_usage++;
+	up_write(&afscm_sem);
+
+	return 0;
+
+ kill:
+	kafscmd_die = 1;
+	wake_up(&kafscmd_sleepq);
+	wait_for_completion(&kafscmd_dead);
+
+ out:
+	up_write(&afscm_sem);
+	return ret;
+
+} /* end afscm_start() */
+
+/*****************************************************************************/
+/*
+ * stop the cache manager service
+ */
+void afscm_stop(void)
+{
+	struct rxrpc_call *call;
+
+	down_write(&afscm_sem);
+
+	BUG_ON(afscm_usage == 0);
+	afscm_usage--;
+
+	if (afscm_usage == 0) {
+		/* don't want more incoming calls */
+		rxrpc_del_service(afs_transport, &AFSCM_service);
+
+		/* abort any calls I've still got open (the afscm_error() will
+		 * dequeue them) */
+		spin_lock(&afscm_calls_lock);
+		while (!list_empty(&afscm_calls)) {
+			call = list_entry(afscm_calls.next,
+					  struct rxrpc_call,
+					  app_link);
+
+			list_del_init(&call->app_link);
+			rxrpc_get_call(call);
+			spin_unlock(&afscm_calls_lock);
+
+			rxrpc_call_abort(call, -ESRCH); /* abort, dequeue and
+							 * put */
+
+			_debug("nuking active call %08x.%d",
+			       ntohl(call->conn->conn_id),
+			       ntohl(call->call_id));
+			rxrpc_put_call(call);
+			rxrpc_put_call(call);
+
+			spin_lock(&afscm_calls_lock);
+		}
+		spin_unlock(&afscm_calls_lock);
+
+		/* get rid of my daemon */
+		kafscmd_die = 1;
+		wake_up(&kafscmd_sleepq);
+		wait_for_completion(&kafscmd_dead);
+
+		/* dispose of any calls waiting for attention */
+		spin_lock(&kafscmd_attention_lock);
+		while (!list_empty(&kafscmd_attention_list)) {
+			call = list_entry(kafscmd_attention_list.next,
+					  struct rxrpc_call,
+					  app_attn_link);
+
+			list_del_init(&call->app_attn_link);
+			spin_unlock(&kafscmd_attention_lock);
+
+			rxrpc_put_call(call);
+
+			spin_lock(&kafscmd_attention_lock);
+		}
+		spin_unlock(&kafscmd_attention_lock);
+
+		afs_kafstimod_del_timer(&afs_mntpt_expiry_timer);
+	}
+
+	up_write(&afscm_sem);
+
+} /* end afscm_stop() */
+
+/*****************************************************************************/
+/*
+ * handle the fileserver breaking a set of callbacks
+ */
+static void _SRXAFSCM_CallBack(struct rxrpc_call *call)
+{
+	struct afs_server *server;
+	size_t count, qty, tmp;
+	int ret = 0, removed;
+
+	_enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]);
+
+	server = afs_server_get_from_peer(call->conn->peer);
+
+	switch (call->app_call_state) {
+		/* we've received the last packet
+		 * - drain all the data from the call and send the reply
+		 */
+	case RXRPC_CSTATE_SRVR_GOT_ARGS:
+		ret = -EBADMSG;
+		qty = call->app_ready_qty;
+		if (qty < 8 || qty > 50 * (6 * 4) + 8)
+			break;
+
+		{
+			struct afs_callback *cb, *pcb;
+			int loop;
+			__be32 *fp, *bp;
+
+			fp = rxrpc_call_alloc_scratch(call, qty);
+
+			/* drag the entire argument block out to the scratch
+			 * space */
+			ret = rxrpc_call_read_data(call, fp, qty, 0);
+			if (ret < 0)
+				break;
+
+			/* and unmarshall the parameter block */
+			ret = -EBADMSG;
+			count = ntohl(*fp++);
+			if (count>AFSCBMAX ||
+			    (count * (3 * 4) + 8 != qty &&
+			     count * (6 * 4) + 8 != qty))
+				break;
+
+			bp = fp + count*3;
+			tmp = ntohl(*bp++);
+			if (tmp > 0 && tmp != count)
+				break;
+			if (tmp == 0)
+				bp = NULL;
+
+			pcb = cb = rxrpc_call_alloc_scratch_s(
+				call, struct afs_callback);
+
+			for (loop = count - 1; loop >= 0; loop--) {
+				pcb->fid.vid	= ntohl(*fp++);
+				pcb->fid.vnode	= ntohl(*fp++);
+				pcb->fid.unique	= ntohl(*fp++);
+				if (bp) {
+					pcb->version	= ntohl(*bp++);
+					pcb->expiry	= ntohl(*bp++);
+					pcb->type	= ntohl(*bp++);
+				}
+				else {
+					pcb->version	= 0;
+					pcb->expiry	= 0;
+					pcb->type	= AFSCM_CB_UNTYPED;
+				}
+				pcb++;
+			}
+
+			/* invoke the actual service routine */
+			ret = SRXAFSCM_CallBack(server, count, cb);
+			if (ret < 0)
+				break;
+		}
+
+		/* send the reply */
+		ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET,
+					    GFP_KERNEL, 0, &count);
+		if (ret < 0)
+			break;
+		break;
+
+		/* operation complete */
+	case RXRPC_CSTATE_COMPLETE:
+		call->app_user = NULL;
+		removed = 0;
+		spin_lock(&afscm_calls_lock);
+		if (!list_empty(&call->app_link)) {
+			list_del_init(&call->app_link);
+			removed = 1;
+		}
+		spin_unlock(&afscm_calls_lock);
+
+		if (removed)
+			rxrpc_put_call(call);
+		break;
+
+		/* operation terminated on error */
+	case RXRPC_CSTATE_ERROR:
+		call->app_user = NULL;
+		break;
+
+	default:
+		break;
+	}
+
+	if (ret < 0)
+		rxrpc_call_abort(call, ret);
+
+	afs_put_server(server);
+
+	_leave(" = %d", ret);
+
+} /* end _SRXAFSCM_CallBack() */
+
+/*****************************************************************************/
+/*
+ * handle the fileserver asking us to initialise our callback state
+ */
+static void _SRXAFSCM_InitCallBackState(struct rxrpc_call *call)
+{
+	struct afs_server *server;
+	size_t count;
+	int ret = 0, removed;
+
+	_enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]);
+
+	server = afs_server_get_from_peer(call->conn->peer);
+
+	switch (call->app_call_state) {
+		/* we've received the last packet - drain all the data from the
+		 * call */
+	case RXRPC_CSTATE_SRVR_GOT_ARGS:
+		/* shouldn't be any args */
+		ret = -EBADMSG;
+		break;
+
+		/* send the reply when asked for it */
+	case RXRPC_CSTATE_SRVR_SND_REPLY:
+		/* invoke the actual service routine */
+		ret = SRXAFSCM_InitCallBackState(server);
+		if (ret < 0)
+			break;
+
+		ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET,
+					    GFP_KERNEL, 0, &count);
+		if (ret < 0)
+			break;
+		break;
+
+		/* operation complete */
+	case RXRPC_CSTATE_COMPLETE:
+		call->app_user = NULL;
+		removed = 0;
+		spin_lock(&afscm_calls_lock);
+		if (!list_empty(&call->app_link)) {
+			list_del_init(&call->app_link);
+			removed = 1;
+		}
+		spin_unlock(&afscm_calls_lock);
+
+		if (removed)
+			rxrpc_put_call(call);
+		break;
+
+		/* operation terminated on error */
+	case RXRPC_CSTATE_ERROR:
+		call->app_user = NULL;
+		break;
+
+	default:
+		break;
+	}
+
+	if (ret < 0)
+		rxrpc_call_abort(call, ret);
+
+	afs_put_server(server);
+
+	_leave(" = %d", ret);
+
+} /* end _SRXAFSCM_InitCallBackState() */
+
+/*****************************************************************************/
+/*
+ * handle a probe from a fileserver
+ */
+static void _SRXAFSCM_Probe(struct rxrpc_call *call)
+{
+	struct afs_server *server;
+	size_t count;
+	int ret = 0, removed;
+
+	_enter("%p{acs=%s}", call, rxrpc_call_states[call->app_call_state]);
+
+	server = afs_server_get_from_peer(call->conn->peer);
+
+	switch (call->app_call_state) {
+		/* we've received the last packet - drain all the data from the
+		 * call */
+	case RXRPC_CSTATE_SRVR_GOT_ARGS:
+		/* shouldn't be any args */
+		ret = -EBADMSG;
+		break;
+
+		/* send the reply when asked for it */
+	case RXRPC_CSTATE_SRVR_SND_REPLY:
+		/* invoke the actual service routine */
+		ret = SRXAFSCM_Probe(server);
+		if (ret < 0)
+			break;
+
+		ret = rxrpc_call_write_data(call, 0, NULL, RXRPC_LAST_PACKET,
+					    GFP_KERNEL, 0, &count);
+		if (ret < 0)
+			break;
+		break;
+
+		/* operation complete */
+	case RXRPC_CSTATE_COMPLETE:
+		call->app_user = NULL;
+		removed = 0;
+		spin_lock(&afscm_calls_lock);
+		if (!list_empty(&call->app_link)) {
+			list_del_init(&call->app_link);
+			removed = 1;
+		}
+		spin_unlock(&afscm_calls_lock);
+
+		if (removed)
+			rxrpc_put_call(call);
+		break;
+
+		/* operation terminated on error */
+	case RXRPC_CSTATE_ERROR:
+		call->app_user = NULL;
+		break;
+
+	default:
+		break;
+	}
+
+	if (ret < 0)
+		rxrpc_call_abort(call, ret);
+
+	afs_put_server(server);
+
+	_leave(" = %d", ret);
+
+} /* end _SRXAFSCM_Probe() */
diff --git a/fs/afs/cmservice.h b/fs/afs/cmservice.h
new file mode 100644
index 000000000000..af8d4d689cb2
--- /dev/null
+++ b/fs/afs/cmservice.h
@@ -0,0 +1,29 @@
+/* cmservice.h: AFS Cache Manager Service declarations
+ *
+ * Copyright (C) 2002 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_AFS_CMSERVICE_H
+#define _LINUX_AFS_CMSERVICE_H
+
+#include <rxrpc/transport.h>
+#include "types.h"
+
+/* cache manager start/stop */
+extern int afscm_start(void);
+extern void afscm_stop(void);
+
+/* cache manager server functions */
+extern int SRXAFSCM_InitCallBackState(struct afs_server *server);
+extern int SRXAFSCM_CallBack(struct afs_server *server,
+			     size_t count,
+			     struct afs_callback callbacks[]);
+extern int SRXAFSCM_Probe(struct afs_server *server);
+
+#endif /* _LINUX_AFS_CMSERVICE_H */
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
new file mode 100644
index 000000000000..6682d6d7f294
--- /dev/null
+++ b/fs/afs/dir.c
@@ -0,0 +1,666 @@
+/* dir.c: AFS filesystem directory handling
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/smp_lock.h>
+#include "vnode.h"
+#include "volume.h"
+#include <rxrpc/call.h>
+#include "super.h"
+#include "internal.h"
+
+static struct dentry *afs_dir_lookup(struct inode *dir, struct dentry *dentry,
+				     struct nameidata *nd);
+static int afs_dir_open(struct inode *inode, struct file *file);
+static int afs_dir_readdir(struct file *file, void *dirent, filldir_t filldir);
+static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd);
+static int afs_d_delete(struct dentry *dentry);
+static int afs_dir_lookup_filldir(void *_cookie, const char *name, int nlen,
+				  loff_t fpos, ino_t ino, unsigned dtype);
+
+struct file_operations afs_dir_file_operations = {
+	.open		= afs_dir_open,
+	.readdir	= afs_dir_readdir,
+};
+
+struct inode_operations afs_dir_inode_operations = {
+	.lookup		= afs_dir_lookup,
+	.getattr	= afs_inode_getattr,
+#if 0 /* TODO */
+	.create		= afs_dir_create,
+	.link		= afs_dir_link,
+	.unlink		= afs_dir_unlink,
+	.symlink	= afs_dir_symlink,
+	.mkdir		= afs_dir_mkdir,
+	.rmdir		= afs_dir_rmdir,
+	.mknod		= afs_dir_mknod,
+	.rename		= afs_dir_rename,
+#endif
+};
+
+static struct dentry_operations afs_fs_dentry_operations = {
+	.d_revalidate	= afs_d_revalidate,
+	.d_delete	= afs_d_delete,
+};
+
+#define AFS_DIR_HASHTBL_SIZE	128
+#define AFS_DIR_DIRENT_SIZE	32
+#define AFS_DIRENT_PER_BLOCK	64
+
+union afs_dirent {
+	struct {
+		uint8_t		valid;
+		uint8_t		unused[1];
+		__be16		hash_next;
+		__be32		vnode;
+		__be32		unique;
+		uint8_t		name[16];
+		uint8_t		overflow[4];	/* if any char of the name (inc
+						 * NUL) reaches here, consume
+						 * the next dirent too */
+	} u;
+	uint8_t	extended_name[32];
+};
+
+/* AFS directory page header (one at the beginning of every 2048-byte chunk) */
+struct afs_dir_pagehdr {
+	__be16		npages;
+	__be16		magic;
+#define AFS_DIR_MAGIC htons(1234)
+	uint8_t		nentries;
+	uint8_t		bitmap[8];
+	uint8_t		pad[19];
+};
+
+/* directory block layout */
+union afs_dir_block {
+
+	struct afs_dir_pagehdr pagehdr;
+
+	struct {
+		struct afs_dir_pagehdr	pagehdr;
+		uint8_t			alloc_ctrs[128];
+		/* dir hash table */
+		uint16_t		hashtable[AFS_DIR_HASHTBL_SIZE];
+	} hdr;
+
+	union afs_dirent dirents[AFS_DIRENT_PER_BLOCK];
+};
+
+/* layout on a linux VM page */
+struct afs_dir_page {
+	union afs_dir_block blocks[PAGE_SIZE / sizeof(union afs_dir_block)];
+};
+
+struct afs_dir_lookup_cookie {
+	struct afs_fid	fid;
+	const char	*name;
+	size_t		nlen;
+	int		found;
+};
+
+/*****************************************************************************/
+/*
+ * check that a directory page is valid
+ */
+static inline void afs_dir_check_page(struct inode *dir, struct page *page)
+{
+	struct afs_dir_page *dbuf;
+	loff_t latter;
+	int tmp, qty;
+
+#if 0
+	/* check the page count */
+	qty = desc.size / sizeof(dbuf->blocks[0]);
+	if (qty == 0)
+		goto error;
+
+	if (page->index==0 && qty!=ntohs(dbuf->blocks[0].pagehdr.npages)) {
+		printk("kAFS: %s(%lu): wrong number of dir blocks %d!=%hu\n",
+		       __FUNCTION__,dir->i_ino,qty,ntohs(dbuf->blocks[0].pagehdr.npages));
+		goto error;
+	}
+#endif
+
+	/* determine how many magic numbers there should be in this page */
+	latter = dir->i_size - (page->index << PAGE_CACHE_SHIFT);
+	if (latter >= PAGE_SIZE)
+		qty = PAGE_SIZE;
+	else
+		qty = latter;
+	qty /= sizeof(union afs_dir_block);
+
+	/* check them */
+	dbuf = page_address(page);
+	for (tmp = 0; tmp < qty; tmp++) {
+		if (dbuf->blocks[tmp].pagehdr.magic != AFS_DIR_MAGIC) {
+			printk("kAFS: %s(%lu): bad magic %d/%d is %04hx\n",
+			       __FUNCTION__, dir->i_ino, tmp, qty,
+			       ntohs(dbuf->blocks[tmp].pagehdr.magic));
+			goto error;
+		}
+	}
+
+	SetPageChecked(page);
+	return;
+
+ error:
+	SetPageChecked(page);
+	SetPageError(page);
+
+} /* end afs_dir_check_page() */
+
+/*****************************************************************************/
+/*
+ * discard a page cached in the pagecache
+ */
+static inline void afs_dir_put_page(struct page *page)
+{
+	kunmap(page);
+	page_cache_release(page);
+
+} /* end afs_dir_put_page() */
+
+/*****************************************************************************/
+/*
+ * get a page into the pagecache
+ */
+static struct page *afs_dir_get_page(struct inode *dir, unsigned long index)
+{
+	struct page *page;
+
+	_enter("{%lu},%lu", dir->i_ino, index);
+
+	page = read_cache_page(dir->i_mapping,index,
+			       (filler_t *) dir->i_mapping->a_ops->readpage,
+			       NULL);
+	if (!IS_ERR(page)) {
+		wait_on_page_locked(page);
+		kmap(page);
+		if (!PageUptodate(page))
+			goto fail;
+		if (!PageChecked(page))
+			afs_dir_check_page(dir, page);
+		if (PageError(page))
+			goto fail;
+	}
+	return page;
+
+ fail:
+	afs_dir_put_page(page);
+	return ERR_PTR(-EIO);
+} /* end afs_dir_get_page() */
+
+/*****************************************************************************/
+/*
+ * open an AFS directory file
+ */
+static int afs_dir_open(struct inode *inode, struct file *file)
+{
+	_enter("{%lu}", inode->i_ino);
+
+	BUG_ON(sizeof(union afs_dir_block) != 2048);
+	BUG_ON(sizeof(union afs_dirent) != 32);
+
+	if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED)
+		return -ENOENT;
+
+	_leave(" = 0");
+	return 0;
+
+} /* end afs_dir_open() */
+
+/*****************************************************************************/
+/*
+ * deal with one block in an AFS directory
+ */
+static int afs_dir_iterate_block(unsigned *fpos,
+				 union afs_dir_block *block,
+				 unsigned blkoff,
+				 void *cookie,
+				 filldir_t filldir)
+{
+	union afs_dirent *dire;
+	unsigned offset, next, curr;
+	size_t nlen;
+	int tmp, ret;
+
+	_enter("%u,%x,%p,,",*fpos,blkoff,block);
+
+	curr = (*fpos - blkoff) / sizeof(union afs_dirent);
+
+	/* walk through the block, an entry at a time */
+	for (offset = AFS_DIRENT_PER_BLOCK - block->pagehdr.nentries;
+	     offset < AFS_DIRENT_PER_BLOCK;
+	     offset = next
+	     ) {
+		next = offset + 1;
+
+		/* skip entries marked unused in the bitmap */
+		if (!(block->pagehdr.bitmap[offset / 8] &
+		      (1 << (offset % 8)))) {
+			_debug("ENT[%Zu.%u]: unused\n",
+			       blkoff / sizeof(union afs_dir_block), offset);
+			if (offset >= curr)
+				*fpos = blkoff +
+					next * sizeof(union afs_dirent);
+			continue;
+		}
+
+		/* got a valid entry */
+		dire = &block->dirents[offset];
+		nlen = strnlen(dire->u.name,
+			       sizeof(*block) -
+			       offset * sizeof(union afs_dirent));
+
+		_debug("ENT[%Zu.%u]: %s %Zu \"%s\"\n",
+		       blkoff / sizeof(union afs_dir_block), offset,
+		       (offset < curr ? "skip" : "fill"),
+		       nlen, dire->u.name);
+
+		/* work out where the next possible entry is */
+		for (tmp = nlen; tmp > 15; tmp -= sizeof(union afs_dirent)) {
+			if (next >= AFS_DIRENT_PER_BLOCK) {
+				_debug("ENT[%Zu.%u]:"
+				       " %u travelled beyond end dir block"
+				       " (len %u/%Zu)\n",
+				       blkoff / sizeof(union afs_dir_block),
+				       offset, next, tmp, nlen);
+				return -EIO;
+			}
+			if (!(block->pagehdr.bitmap[next / 8] &
+			      (1 << (next % 8)))) {
+				_debug("ENT[%Zu.%u]:"
+				       " %u unmarked extension (len %u/%Zu)\n",
+				       blkoff / sizeof(union afs_dir_block),
+				       offset, next, tmp, nlen);
+				return -EIO;
+			}
+
+			_debug("ENT[%Zu.%u]: ext %u/%Zu\n",
+			       blkoff / sizeof(union afs_dir_block),
+			       next, tmp, nlen);
+			next++;
+		}
+
+		/* skip if starts before the current position */
+		if (offset < curr)
+			continue;
+
+		/* found the next entry */
+		ret = filldir(cookie,
+			      dire->u.name,
+			      nlen,
+			      blkoff + offset * sizeof(union afs_dirent),
+			      ntohl(dire->u.vnode),
+			      filldir == afs_dir_lookup_filldir ?
+			      ntohl(dire->u.unique) : DT_UNKNOWN);
+		if (ret < 0) {
+			_leave(" = 0 [full]");
+			return 0;
+		}
+
+		*fpos = blkoff + next * sizeof(union afs_dirent);
+	}
+
+	_leave(" = 1 [more]");
+	return 1;
+} /* end afs_dir_iterate_block() */
+
+/*****************************************************************************/
+/*
+ * read an AFS directory
+ */
+static int afs_dir_iterate(struct inode *dir, unsigned *fpos, void *cookie,
+			   filldir_t filldir)
+{
+	union afs_dir_block	*dblock;
+	struct afs_dir_page *dbuf;
+	struct page *page;
+	unsigned blkoff, limit;
+	int ret;
+
+	_enter("{%lu},%u,,", dir->i_ino, *fpos);
+
+	if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) {
+		_leave(" = -ESTALE");
+		return -ESTALE;
+	}
+
+	/* round the file position up to the next entry boundary */
+	*fpos += sizeof(union afs_dirent) - 1;
+	*fpos &= ~(sizeof(union afs_dirent) - 1);
+
+	/* walk through the blocks in sequence */
+	ret = 0;
+	while (*fpos < dir->i_size) {
+		blkoff = *fpos & ~(sizeof(union afs_dir_block) - 1);
+
+		/* fetch the appropriate page from the directory */
+		page = afs_dir_get_page(dir, blkoff / PAGE_SIZE);
+		if (IS_ERR(page)) {
+			ret = PTR_ERR(page);
+			break;
+		}
+
+		limit = blkoff & ~(PAGE_SIZE - 1);
+
+		dbuf = page_address(page);
+
+		/* deal with the individual blocks stashed on this page */
+		do {
+			dblock = &dbuf->blocks[(blkoff % PAGE_SIZE) /
+					       sizeof(union afs_dir_block)];
+			ret = afs_dir_iterate_block(fpos, dblock, blkoff,
+						    cookie, filldir);
+			if (ret != 1) {
+				afs_dir_put_page(page);
+				goto out;
+			}
+
+			blkoff += sizeof(union afs_dir_block);
+
+		} while (*fpos < dir->i_size && blkoff < limit);
+
+		afs_dir_put_page(page);
+		ret = 0;
+	}
+
+ out:
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_dir_iterate() */
+
+/*****************************************************************************/
+/*
+ * read an AFS directory
+ */
+static int afs_dir_readdir(struct file *file, void *cookie, filldir_t filldir)
+{
+	unsigned fpos;
+	int ret;
+
+	_enter("{%Ld,{%lu}}", file->f_pos, file->f_dentry->d_inode->i_ino);
+
+	fpos = file->f_pos;
+	ret = afs_dir_iterate(file->f_dentry->d_inode, &fpos, cookie, filldir);
+	file->f_pos = fpos;
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_dir_readdir() */
+
+/*****************************************************************************/
+/*
+ * search the directory for a name
+ * - if afs_dir_iterate_block() spots this function, it'll pass the FID
+ *   uniquifier through dtype
+ */
+static int afs_dir_lookup_filldir(void *_cookie, const char *name, int nlen,
+				  loff_t fpos, ino_t ino, unsigned dtype)
+{
+	struct afs_dir_lookup_cookie *cookie = _cookie;
+
+	_enter("{%s,%Zu},%s,%u,,%lu,%u",
+	       cookie->name, cookie->nlen, name, nlen, ino, dtype);
+
+	if (cookie->nlen != nlen || memcmp(cookie->name, name, nlen) != 0) {
+		_leave(" = 0 [no]");
+		return 0;
+	}
+
+	cookie->fid.vnode = ino;
+	cookie->fid.unique = dtype;
+	cookie->found = 1;
+
+	_leave(" = -1 [found]");
+	return -1;
+} /* end afs_dir_lookup_filldir() */
+
+/*****************************************************************************/
+/*
+ * look up an entry in a directory
+ */
+static struct dentry *afs_dir_lookup(struct inode *dir, struct dentry *dentry,
+				     struct nameidata *nd)
+{
+	struct afs_dir_lookup_cookie cookie;
+	struct afs_super_info *as;
+	struct afs_vnode *vnode;
+	struct inode *inode;
+	unsigned fpos;
+	int ret;
+
+	_enter("{%lu},%p{%s}", dir->i_ino, dentry, dentry->d_name.name);
+
+	/* insanity checks first */
+	BUG_ON(sizeof(union afs_dir_block) != 2048);
+	BUG_ON(sizeof(union afs_dirent) != 32);
+
+	if (dentry->d_name.len > 255) {
+		_leave(" = -ENAMETOOLONG");
+		return ERR_PTR(-ENAMETOOLONG);
+	}
+
+	vnode = AFS_FS_I(dir);
+	if (vnode->flags & AFS_VNODE_DELETED) {
+		_leave(" = -ESTALE");
+		return ERR_PTR(-ESTALE);
+	}
+
+	as = dir->i_sb->s_fs_info;
+
+	/* search the directory */
+	cookie.name	= dentry->d_name.name;
+	cookie.nlen	= dentry->d_name.len;
+	cookie.fid.vid	= as->volume->vid;
+	cookie.found	= 0;
+
+	fpos = 0;
+	ret = afs_dir_iterate(dir, &fpos, &cookie, afs_dir_lookup_filldir);
+	if (ret < 0) {
+		_leave(" = %d", ret);
+		return ERR_PTR(ret);
+	}
+
+	ret = -ENOENT;
+	if (!cookie.found) {
+		_leave(" = %d", ret);
+		return ERR_PTR(ret);
+	}
+
+	/* instantiate the dentry */
+	ret = afs_iget(dir->i_sb, &cookie.fid, &inode);
+	if (ret < 0) {
+		_leave(" = %d", ret);
+		return ERR_PTR(ret);
+	}
+
+	dentry->d_op = &afs_fs_dentry_operations;
+	dentry->d_fsdata = (void *) (unsigned long) vnode->status.version;
+
+	d_add(dentry, inode);
+	_leave(" = 0 { vn=%u u=%u } -> { ino=%lu v=%lu }",
+	       cookie.fid.vnode,
+	       cookie.fid.unique,
+	       dentry->d_inode->i_ino,
+	       dentry->d_inode->i_version);
+
+	return NULL;
+} /* end afs_dir_lookup() */
+
+/*****************************************************************************/
+/*
+ * check that a dentry lookup hit has found a valid entry
+ * - NOTE! the hit can be a negative hit too, so we can't assume we have an
+ *   inode
+ * (derived from nfs_lookup_revalidate)
+ */
+static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
+{
+	struct afs_dir_lookup_cookie cookie;
+	struct dentry *parent;
+	struct inode *inode, *dir;
+	unsigned fpos;
+	int ret;
+
+	_enter("{sb=%p n=%s},", dentry->d_sb, dentry->d_name.name);
+
+	/* lock down the parent dentry so we can peer at it */
+	parent = dget_parent(dentry->d_parent);
+
+	dir = parent->d_inode;
+	inode = dentry->d_inode;
+
+	/* handle a negative dentry */
+	if (!inode)
+		goto out_bad;
+
+	/* handle a bad inode */
+	if (is_bad_inode(inode)) {
+		printk("kAFS: afs_d_revalidate: %s/%s has bad inode\n",
+		       dentry->d_parent->d_name.name, dentry->d_name.name);
+		goto out_bad;
+	}
+
+	/* force a full look up if the parent directory changed since last the
+	 * server was consulted
+	 * - otherwise this inode must still exist, even if the inode details
+	 *   themselves have changed
+	 */
+	if (AFS_FS_I(dir)->flags & AFS_VNODE_CHANGED)
+		afs_vnode_fetch_status(AFS_FS_I(dir));
+
+	if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) {
+		_debug("%s: parent dir deleted", dentry->d_name.name);
+		goto out_bad;
+	}
+
+	if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED) {
+		_debug("%s: file already deleted", dentry->d_name.name);
+		goto out_bad;
+	}
+
+	if ((unsigned long) dentry->d_fsdata !=
+	    (unsigned long) AFS_FS_I(dir)->status.version) {
+		_debug("%s: parent changed %lu -> %u",
+		       dentry->d_name.name,
+		       (unsigned long) dentry->d_fsdata,
+		       (unsigned) AFS_FS_I(dir)->status.version);
+
+		/* search the directory for this vnode */
+		cookie.name	= dentry->d_name.name;
+		cookie.nlen	= dentry->d_name.len;
+		cookie.fid.vid	= AFS_FS_I(inode)->volume->vid;
+		cookie.found	= 0;
+
+		fpos = 0;
+		ret = afs_dir_iterate(dir, &fpos, &cookie,
+				      afs_dir_lookup_filldir);
+		if (ret < 0) {
+			_debug("failed to iterate dir %s: %d",
+			       parent->d_name.name, ret);
+			goto out_bad;
+		}
+
+		if (!cookie.found) {
+			_debug("%s: dirent not found", dentry->d_name.name);
+			goto not_found;
+		}
+
+		/* if the vnode ID has changed, then the dirent points to a
+		 * different file */
+		if (cookie.fid.vnode != AFS_FS_I(inode)->fid.vnode) {
+			_debug("%s: dirent changed", dentry->d_name.name);
+			goto not_found;
+		}
+
+		/* if the vnode ID uniqifier has changed, then the file has
+		 * been deleted */
+		if (cookie.fid.unique != AFS_FS_I(inode)->fid.unique) {
+			_debug("%s: file deleted (uq %u -> %u I:%lu)",
+			       dentry->d_name.name,
+			       cookie.fid.unique,
+			       AFS_FS_I(inode)->fid.unique,
+			       inode->i_version);
+			spin_lock(&AFS_FS_I(inode)->lock);
+			AFS_FS_I(inode)->flags |= AFS_VNODE_DELETED;
+			spin_unlock(&AFS_FS_I(inode)->lock);
+			invalidate_remote_inode(inode);
+			goto out_bad;
+		}
+
+		dentry->d_fsdata =
+			(void *) (unsigned long) AFS_FS_I(dir)->status.version;
+	}
+
+ out_valid:
+	dput(parent);
+	_leave(" = 1 [valid]");
+	return 1;
+
+	/* the dirent, if it exists, now points to a different vnode */
+ not_found:
+	spin_lock(&dentry->d_lock);
+	dentry->d_flags |= DCACHE_NFSFS_RENAMED;
+	spin_unlock(&dentry->d_lock);
+
+ out_bad:
+	if (inode) {
+		/* don't unhash if we have submounts */
+		if (have_submounts(dentry))
+			goto out_valid;
+	}
+
+	shrink_dcache_parent(dentry);
+
+	_debug("dropping dentry %s/%s",
+	       dentry->d_parent->d_name.name, dentry->d_name.name);
+	d_drop(dentry);
+
+	dput(parent);
+
+	_leave(" = 0 [bad]");
+	return 0;
+} /* end afs_d_revalidate() */
+
+/*****************************************************************************/
+/*
+ * allow the VFS to enquire as to whether a dentry should be unhashed (mustn't
+ * sleep)
+ * - called from dput() when d_count is going to 0.
+ * - return 1 to request dentry be unhashed, 0 otherwise
+ */
+static int afs_d_delete(struct dentry *dentry)
+{
+	_enter("%s", dentry->d_name.name);
+
+	if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
+		goto zap;
+
+	if (dentry->d_inode) {
+		if (AFS_FS_I(dentry->d_inode)->flags & AFS_VNODE_DELETED)
+			goto zap;
+	}
+
+	_leave(" = 0 [keep]");
+	return 0;
+
+ zap:
+	_leave(" = 1 [zap]");
+	return 1;
+} /* end afs_d_delete() */
diff --git a/fs/afs/errors.h b/fs/afs/errors.h
new file mode 100644
index 000000000000..574d94ac8d05
--- /dev/null
+++ b/fs/afs/errors.h
@@ -0,0 +1,34 @@
+/* errors.h: AFS abort/error codes
+ *
+ * Copyright (C) 2002 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_AFS_ERRORS_H
+#define _LINUX_AFS_ERRORS_H
+
+#include "types.h"
+
+/* file server abort codes */
+typedef enum {
+	VSALVAGE	= 101,	/* volume needs salvaging */
+	VNOVNODE	= 102,	/* no such file/dir (vnode) */
+	VNOVOL		= 103,	/* no such volume or volume unavailable */
+	VVOLEXISTS	= 104,	/* volume name already exists */
+	VNOSERVICE	= 105,	/* volume not currently in service */
+	VOFFLINE	= 106,	/* volume is currently offline (more info available [VVL-spec]) */
+	VONLINE		= 107,	/* volume is already online */
+	VDISKFULL	= 108,	/* disk partition is full */
+	VOVERQUOTA	= 109,	/* volume's maximum quota exceeded */
+	VBUSY		= 110,	/* volume is temporarily unavailable */
+	VMOVED		= 111,	/* volume moved to new server - ask this FS where */
+} afs_rxfs_abort_t;
+
+extern int afs_abort_to_error(int abortcode);
+
+#endif /* _LINUX_AFS_ERRORS_H */
diff --git a/fs/afs/file.c b/fs/afs/file.c
new file mode 100644
index 000000000000..6b6bb7c8abf6
--- /dev/null
+++ b/fs/afs/file.c
@@ -0,0 +1,305 @@
+/* file.c: AFS filesystem file handling
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/buffer_head.h>
+#include "volume.h"
+#include "vnode.h"
+#include <rxrpc/call.h>
+#include "internal.h"
+
+#if 0
+static int afs_file_open(struct inode *inode, struct file *file);
+static int afs_file_release(struct inode *inode, struct file *file);
+#endif
+
+static int afs_file_readpage(struct file *file, struct page *page);
+static int afs_file_invalidatepage(struct page *page, unsigned long offset);
+static int afs_file_releasepage(struct page *page, int gfp_flags);
+
+static ssize_t afs_file_write(struct file *file, const char __user *buf,
+			      size_t size, loff_t *off);
+
+struct inode_operations afs_file_inode_operations = {
+	.getattr	= afs_inode_getattr,
+};
+
+struct file_operations afs_file_file_operations = {
+	.read		= generic_file_read,
+	.write		= afs_file_write,
+	.mmap		= generic_file_mmap,
+#if 0
+	.open		= afs_file_open,
+	.release	= afs_file_release,
+	.fsync		= afs_file_fsync,
+#endif
+};
+
+struct address_space_operations afs_fs_aops = {
+	.readpage	= afs_file_readpage,
+	.sync_page	= block_sync_page,
+	.set_page_dirty	= __set_page_dirty_nobuffers,
+	.releasepage	= afs_file_releasepage,
+	.invalidatepage	= afs_file_invalidatepage,
+};
+
+/*****************************************************************************/
+/*
+ * AFS file write
+ */
+static ssize_t afs_file_write(struct file *file, const char __user *buf,
+			      size_t size, loff_t *off)
+{
+	struct afs_vnode *vnode;
+
+	vnode = AFS_FS_I(file->f_dentry->d_inode);
+	if (vnode->flags & AFS_VNODE_DELETED)
+		return -ESTALE;
+
+	return -EIO;
+} /* end afs_file_write() */
+
+/*****************************************************************************/
+/*
+ * deal with notification that a page was read from the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static void afs_file_readpage_read_complete(void *cookie_data,
+					    struct page *page,
+					    void *data,
+					    int error)
+{
+	_enter("%p,%p,%p,%d", cookie_data, page, data, error);
+
+	if (error)
+		SetPageError(page);
+	else
+		SetPageUptodate(page);
+	unlock_page(page);
+
+} /* end afs_file_readpage_read_complete() */
+#endif
+
+/*****************************************************************************/
+/*
+ * deal with notification that a page was written to the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static void afs_file_readpage_write_complete(void *cookie_data,
+					     struct page *page,
+					     void *data,
+					     int error)
+{
+	_enter("%p,%p,%p,%d", cookie_data, page, data, error);
+
+	unlock_page(page);
+
+} /* end afs_file_readpage_write_complete() */
+#endif
+
+/*****************************************************************************/
+/*
+ * AFS read page from file (or symlink)
+ */
+static int afs_file_readpage(struct file *file, struct page *page)
+{
+	struct afs_rxfs_fetch_descriptor desc;
+#ifdef AFS_CACHING_SUPPORT
+	struct cachefs_page *pageio;
+#endif
+	struct afs_vnode *vnode;
+	struct inode *inode;
+	int ret;
+
+	inode = page->mapping->host;
+
+	_enter("{%lu},{%lu}", inode->i_ino, page->index);
+
+	vnode = AFS_FS_I(inode);
+
+	if (!PageLocked(page))
+		PAGE_BUG(page);
+
+	ret = -ESTALE;
+	if (vnode->flags & AFS_VNODE_DELETED)
+		goto error;
+
+#ifdef AFS_CACHING_SUPPORT
+	ret = cachefs_page_get_private(page, &pageio, GFP_NOIO);
+	if (ret < 0)
+		goto error;
+
+	/* is it cached? */
+	ret = cachefs_read_or_alloc_page(vnode->cache,
+					 page,
+					 afs_file_readpage_read_complete,
+					 NULL,
+					 GFP_KERNEL);
+#else
+	ret = -ENOBUFS;
+#endif
+
+	switch (ret) {
+		/* read BIO submitted and wb-journal entry found */
+	case 1:
+		BUG(); // TODO - handle wb-journal match
+
+		/* read BIO submitted (page in cache) */
+	case 0:
+		break;
+
+		/* no page available in cache */
+	case -ENOBUFS:
+	case -ENODATA:
+	default:
+		desc.fid	= vnode->fid;
+		desc.offset	= page->index << PAGE_CACHE_SHIFT;
+		desc.size	= min((size_t) (inode->i_size - desc.offset),
+				      (size_t) PAGE_SIZE);
+		desc.buffer	= kmap(page);
+
+		clear_page(desc.buffer);
+
+		/* read the contents of the file from the server into the
+		 * page */
+		ret = afs_vnode_fetch_data(vnode, &desc);
+		kunmap(page);
+		if (ret < 0) {
+			if (ret==-ENOENT) {
+				_debug("got NOENT from server"
+				       " - marking file deleted and stale");
+				vnode->flags |= AFS_VNODE_DELETED;
+				ret = -ESTALE;
+			}
+
+#ifdef AFS_CACHING_SUPPORT
+			cachefs_uncache_page(vnode->cache, page);
+#endif
+			goto error;
+		}
+
+		SetPageUptodate(page);
+
+#ifdef AFS_CACHING_SUPPORT
+		if (cachefs_write_page(vnode->cache,
+				       page,
+				       afs_file_readpage_write_complete,
+				       NULL,
+				       GFP_KERNEL) != 0
+		    ) {
+			cachefs_uncache_page(vnode->cache, page);
+			unlock_page(page);
+		}
+#else
+		unlock_page(page);
+#endif
+	}
+
+	_leave(" = 0");
+	return 0;
+
+ error:
+	SetPageError(page);
+	unlock_page(page);
+
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_file_readpage() */
+
+/*****************************************************************************/
+/*
+ * get a page cookie for the specified page
+ */
+#ifdef AFS_CACHING_SUPPORT
+int afs_cache_get_page_cookie(struct page *page,
+			      struct cachefs_page **_page_cookie)
+{
+	int ret;
+
+	_enter("");
+	ret = cachefs_page_get_private(page,_page_cookie, GFP_NOIO);
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_cache_get_page_cookie() */
+#endif
+
+/*****************************************************************************/
+/*
+ * invalidate part or all of a page
+ */
+static int afs_file_invalidatepage(struct page *page, unsigned long offset)
+{
+	int ret = 1;
+
+	_enter("{%lu},%lu", page->index, offset);
+
+	BUG_ON(!PageLocked(page));
+
+	if (PagePrivate(page)) {
+#ifdef AFS_CACHING_SUPPORT
+		struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
+		cachefs_uncache_page(vnode->cache,page);
+#endif
+
+		/* We release buffers only if the entire page is being
+		 * invalidated.
+		 * The get_block cached value has been unconditionally
+		 * invalidated, so real IO is not possible anymore.
+		 */
+		if (offset == 0) {
+			BUG_ON(!PageLocked(page));
+
+			ret = 0;
+			if (!PageWriteback(page))
+				ret = page->mapping->a_ops->releasepage(page,
+									0);
+		}
+	}
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_file_invalidatepage() */
+
+/*****************************************************************************/
+/*
+ * release a page and cleanup its private data
+ */
+static int afs_file_releasepage(struct page *page, int gfp_flags)
+{
+	struct cachefs_page *pageio;
+
+	_enter("{%lu},%x", page->index, gfp_flags);
+
+	if (PagePrivate(page)) {
+#ifdef AFS_CACHING_SUPPORT
+		struct afs_vnode *vnode = AFS_FS_I(page->mapping->host);
+		cachefs_uncache_page(vnode->cache, page);
+#endif
+
+		pageio = (struct cachefs_page *) page->private;
+		page->private = 0;
+		ClearPagePrivate(page);
+
+		if (pageio)
+			kfree(pageio);
+	}
+
+	_leave(" = 0");
+	return 0;
+} /* end afs_file_releasepage() */
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
new file mode 100644
index 000000000000..61bc371532ab
--- /dev/null
+++ b/fs/afs/fsclient.c
@@ -0,0 +1,837 @@
+/* fsclient.c: AFS File Server client stubs
+ *
+ * Copyright (C) 2002 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/init.h>
+#include <linux/sched.h>
+#include <rxrpc/rxrpc.h>
+#include <rxrpc/transport.h>
+#include <rxrpc/connection.h>
+#include <rxrpc/call.h>
+#include "fsclient.h"
+#include "cmservice.h"
+#include "vnode.h"
+#include "server.h"
+#include "errors.h"
+#include "internal.h"
+
+#define FSFETCHSTATUS		132	/* AFS Fetch file status */
+#define FSFETCHDATA		130	/* AFS Fetch file data */
+#define FSGIVEUPCALLBACKS	147	/* AFS Discard callback promises */
+#define FSGETVOLUMEINFO		148	/* AFS Get root volume information */
+#define FSGETROOTVOLUME		151	/* AFS Get root volume name */
+#define FSLOOKUP		161	/* AFS lookup file in directory */
+
+/*****************************************************************************/
+/*
+ * map afs abort codes to/from Linux error codes
+ * - called with call->lock held
+ */
+static void afs_rxfs_aemap(struct rxrpc_call *call)
+{
+	switch (call->app_err_state) {
+	case RXRPC_ESTATE_LOCAL_ABORT:
+		call->app_abort_code = -call->app_errno;
+		break;
+	case RXRPC_ESTATE_PEER_ABORT:
+		call->app_errno = afs_abort_to_error(call->app_abort_code);
+		break;
+	default:
+		break;
+	}
+} /* end afs_rxfs_aemap() */
+
+/*****************************************************************************/
+/*
+ * get the root volume name from a fileserver
+ * - this operation doesn't seem to work correctly in OpenAFS server 1.2.2
+ */
+#if 0
+int afs_rxfs_get_root_volume(struct afs_server *server,
+			     char *buf, size_t *buflen)
+{
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[2];
+	size_t sent;
+	int ret;
+	u32 param[1];
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	kenter("%p,%p,%u",server, buf, *buflen);
+
+	/* get hold of the fileserver connection */
+	ret = afs_server_get_fsconn(server, &conn);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = FSGETROOTVOLUME;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	param[0] = htonl(FSGETROOTVOLUME);
+
+	piov[0].iov_len = sizeof(param);
+	piov[0].iov_base = param;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY ||
+		    signal_pending(current))
+			break;
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+
+	ret = -EINTR;
+	if (signal_pending(current))
+		goto abort;
+
+	switch (call->app_call_state) {
+	case RXRPC_CSTATE_ERROR:
+		ret = call->app_errno;
+		kdebug("Got Error: %d", ret);
+		goto out_unwait;
+
+	case RXRPC_CSTATE_CLNT_GOT_REPLY:
+		/* read the reply */
+		kdebug("Got Reply: qty=%d", call->app_ready_qty);
+
+		ret = -EBADMSG;
+		if (call->app_ready_qty <= 4)
+			goto abort;
+
+		ret = rxrpc_call_read_data(call, NULL, call->app_ready_qty, 0);
+		if (ret < 0)
+			goto abort;
+
+#if 0
+		/* unmarshall the reply */
+		bp = buffer;
+		for (loop = 0; loop < 65; loop++)
+			entry->name[loop] = ntohl(*bp++);
+		entry->name[64] = 0;
+
+		entry->type = ntohl(*bp++);
+		entry->num_servers = ntohl(*bp++);
+
+		for (loop = 0; loop < 8; loop++)
+			entry->servers[loop].addr.s_addr = *bp++;
+
+		for (loop = 0; loop < 8; loop++)
+			entry->servers[loop].partition = ntohl(*bp++);
+
+		for (loop = 0; loop < 8; loop++)
+			entry->servers[loop].flags = ntohl(*bp++);
+
+		for (loop = 0; loop < 3; loop++)
+			entry->volume_ids[loop] = ntohl(*bp++);
+
+		entry->clone_id = ntohl(*bp++);
+		entry->flags = ntohl(*bp);
+#endif
+
+		/* success */
+		ret = 0;
+		goto out_unwait;
+
+	default:
+		BUG();
+	}
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	afs_server_release_fsconn(server, conn);
+ out:
+	kleave("");
+	return ret;
+} /* end afs_rxfs_get_root_volume() */
+#endif
+
+/*****************************************************************************/
+/*
+ * get information about a volume
+ */
+#if 0
+int afs_rxfs_get_volume_info(struct afs_server *server,
+			     const char *name,
+			     struct afs_volume_info *vinfo)
+{
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[3];
+	size_t sent;
+	int ret;
+	u32 param[2], *bp, zero;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	_enter("%p,%s,%p", server, name, vinfo);
+
+	/* get hold of the fileserver connection */
+	ret = afs_server_get_fsconn(server, &conn);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = FSGETVOLUMEINFO;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	piov[1].iov_len = strlen(name);
+	piov[1].iov_base = (char *) name;
+
+	zero = 0;
+	piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3;
+	piov[2].iov_base = &zero;
+
+	param[0] = htonl(FSGETVOLUMEINFO);
+	param[1] = htonl(piov[1].iov_len);
+
+	piov[0].iov_len = sizeof(param);
+	piov[0].iov_base = param;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	bp = rxrpc_call_alloc_scratch(call, 64);
+
+	ret = rxrpc_call_read_data(call, bp, 64,
+				   RXRPC_CALL_READ_BLOCK |
+				   RXRPC_CALL_READ_ALL);
+	if (ret < 0) {
+		if (ret == -ECONNABORTED) {
+			ret = call->app_errno;
+			goto out_unwait;
+		}
+		goto abort;
+	}
+
+	/* unmarshall the reply */
+	vinfo->vid = ntohl(*bp++);
+	vinfo->type = ntohl(*bp++);
+
+	vinfo->type_vids[0] = ntohl(*bp++);
+	vinfo->type_vids[1] = ntohl(*bp++);
+	vinfo->type_vids[2] = ntohl(*bp++);
+	vinfo->type_vids[3] = ntohl(*bp++);
+	vinfo->type_vids[4] = ntohl(*bp++);
+
+	vinfo->nservers = ntohl(*bp++);
+	vinfo->servers[0].addr.s_addr = *bp++;
+	vinfo->servers[1].addr.s_addr = *bp++;
+	vinfo->servers[2].addr.s_addr = *bp++;
+	vinfo->servers[3].addr.s_addr = *bp++;
+	vinfo->servers[4].addr.s_addr = *bp++;
+	vinfo->servers[5].addr.s_addr = *bp++;
+	vinfo->servers[6].addr.s_addr = *bp++;
+	vinfo->servers[7].addr.s_addr = *bp++;
+
+	ret = -EBADMSG;
+	if (vinfo->nservers > 8)
+		goto abort;
+
+	/* success */
+	ret = 0;
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	afs_server_release_fsconn(server, conn);
+ out:
+	_leave("");
+	return ret;
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+
+} /* end afs_rxfs_get_volume_info() */
+#endif
+
+/*****************************************************************************/
+/*
+ * fetch the status information for a file
+ */
+int afs_rxfs_fetch_file_status(struct afs_server *server,
+			       struct afs_vnode *vnode,
+			       struct afs_volsync *volsync)
+{
+	struct afs_server_callslot callslot;
+	struct rxrpc_call *call;
+	struct kvec piov[1];
+	size_t sent;
+	int ret;
+	__be32 *bp;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	_enter("%p,{%u,%u,%u}",
+	       server, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique);
+
+	/* get hold of the fileserver connection */
+	ret = afs_server_request_callslot(server, &callslot);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap,
+				&call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = FSFETCHSTATUS;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	bp = rxrpc_call_alloc_scratch(call, 16);
+	bp[0] = htonl(FSFETCHSTATUS);
+	bp[1] = htonl(vnode->fid.vid);
+	bp[2] = htonl(vnode->fid.vnode);
+	bp[3] = htonl(vnode->fid.unique);
+
+	piov[0].iov_len = 16;
+	piov[0].iov_base = bp;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	bp = rxrpc_call_alloc_scratch(call, 120);
+
+	ret = rxrpc_call_read_data(call, bp, 120,
+				   RXRPC_CALL_READ_BLOCK |
+				   RXRPC_CALL_READ_ALL);
+	if (ret < 0) {
+		if (ret == -ECONNABORTED) {
+			ret = call->app_errno;
+			goto out_unwait;
+		}
+		goto abort;
+	}
+
+	/* unmarshall the reply */
+	vnode->status.if_version	= ntohl(*bp++);
+	vnode->status.type		= ntohl(*bp++);
+	vnode->status.nlink		= ntohl(*bp++);
+	vnode->status.size		= ntohl(*bp++);
+	vnode->status.version		= ntohl(*bp++);
+	vnode->status.author		= ntohl(*bp++);
+	vnode->status.owner		= ntohl(*bp++);
+	vnode->status.caller_access	= ntohl(*bp++);
+	vnode->status.anon_access	= ntohl(*bp++);
+	vnode->status.mode		= ntohl(*bp++);
+	vnode->status.parent.vid	= vnode->fid.vid;
+	vnode->status.parent.vnode	= ntohl(*bp++);
+	vnode->status.parent.unique	= ntohl(*bp++);
+	bp++; /* seg size */
+	vnode->status.mtime_client	= ntohl(*bp++);
+	vnode->status.mtime_server	= ntohl(*bp++);
+	bp++; /* group */
+	bp++; /* sync counter */
+	vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
+	bp++; /* spare2 */
+	bp++; /* spare3 */
+	bp++; /* spare4 */
+
+	vnode->cb_version		= ntohl(*bp++);
+	vnode->cb_expiry		= ntohl(*bp++);
+	vnode->cb_type			= ntohl(*bp++);
+
+	if (volsync) {
+		volsync->creation	= ntohl(*bp++);
+		bp++; /* spare2 */
+		bp++; /* spare3 */
+		bp++; /* spare4 */
+		bp++; /* spare5 */
+		bp++; /* spare6 */
+	}
+
+	/* success */
+	ret = 0;
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	afs_server_release_callslot(server, &callslot);
+ out:
+	_leave("");
+	return ret;
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+} /* end afs_rxfs_fetch_file_status() */
+
+/*****************************************************************************/
+/*
+ * fetch the contents of a file or directory
+ */
+int afs_rxfs_fetch_file_data(struct afs_server *server,
+			     struct afs_vnode *vnode,
+			     struct afs_rxfs_fetch_descriptor *desc,
+			     struct afs_volsync *volsync)
+{
+	struct afs_server_callslot callslot;
+	struct rxrpc_call *call;
+	struct kvec piov[1];
+	size_t sent;
+	int ret;
+	__be32 *bp;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	_enter("%p,{fid={%u,%u,%u},sz=%Zu,of=%lu}",
+	       server,
+	       desc->fid.vid,
+	       desc->fid.vnode,
+	       desc->fid.unique,
+	       desc->size,
+	       desc->offset);
+
+	/* get hold of the fileserver connection */
+	ret = afs_server_request_callslot(server, &callslot);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = FSFETCHDATA;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	bp = rxrpc_call_alloc_scratch(call, 24);
+	bp[0] = htonl(FSFETCHDATA);
+	bp[1] = htonl(desc->fid.vid);
+	bp[2] = htonl(desc->fid.vnode);
+	bp[3] = htonl(desc->fid.unique);
+	bp[4] = htonl(desc->offset);
+	bp[5] = htonl(desc->size);
+
+	piov[0].iov_len = 24;
+	piov[0].iov_base = bp;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the data count to arrive */
+	ret = rxrpc_call_read_data(call, bp, 4, RXRPC_CALL_READ_BLOCK);
+	if (ret < 0)
+		goto read_failed;
+
+	desc->actual = ntohl(bp[0]);
+	if (desc->actual != desc->size) {
+		ret = -EBADMSG;
+		goto abort;
+	}
+
+	/* call the app to read the actual data */
+	rxrpc_call_reset_scratch(call);
+
+	ret = rxrpc_call_read_data(call, desc->buffer, desc->actual,
+				   RXRPC_CALL_READ_BLOCK);
+	if (ret < 0)
+		goto read_failed;
+
+	/* wait for the rest of the reply to completely arrive */
+	rxrpc_call_reset_scratch(call);
+	bp = rxrpc_call_alloc_scratch(call, 120);
+
+	ret = rxrpc_call_read_data(call, bp, 120,
+				   RXRPC_CALL_READ_BLOCK |
+				   RXRPC_CALL_READ_ALL);
+	if (ret < 0)
+		goto read_failed;
+
+	/* unmarshall the reply */
+	vnode->status.if_version	= ntohl(*bp++);
+	vnode->status.type		= ntohl(*bp++);
+	vnode->status.nlink		= ntohl(*bp++);
+	vnode->status.size		= ntohl(*bp++);
+	vnode->status.version		= ntohl(*bp++);
+	vnode->status.author		= ntohl(*bp++);
+	vnode->status.owner		= ntohl(*bp++);
+	vnode->status.caller_access	= ntohl(*bp++);
+	vnode->status.anon_access	= ntohl(*bp++);
+	vnode->status.mode		= ntohl(*bp++);
+	vnode->status.parent.vid	= desc->fid.vid;
+	vnode->status.parent.vnode	= ntohl(*bp++);
+	vnode->status.parent.unique	= ntohl(*bp++);
+	bp++; /* seg size */
+	vnode->status.mtime_client	= ntohl(*bp++);
+	vnode->status.mtime_server	= ntohl(*bp++);
+	bp++; /* group */
+	bp++; /* sync counter */
+	vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
+	bp++; /* spare2 */
+	bp++; /* spare3 */
+	bp++; /* spare4 */
+
+	vnode->cb_version		= ntohl(*bp++);
+	vnode->cb_expiry		= ntohl(*bp++);
+	vnode->cb_type			= ntohl(*bp++);
+
+	if (volsync) {
+		volsync->creation	= ntohl(*bp++);
+		bp++; /* spare2 */
+		bp++; /* spare3 */
+		bp++; /* spare4 */
+		bp++; /* spare5 */
+		bp++; /* spare6 */
+	}
+
+	/* success */
+	ret = 0;
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq,&myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	afs_server_release_callslot(server, &callslot);
+ out:
+	_leave(" = %d", ret);
+	return ret;
+
+ read_failed:
+	if (ret == -ECONNABORTED) {
+		ret = call->app_errno;
+		goto out_unwait;
+	}
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+
+} /* end afs_rxfs_fetch_file_data() */
+
+/*****************************************************************************/
+/*
+ * ask the AFS fileserver to discard a callback request on a file
+ */
+int afs_rxfs_give_up_callback(struct afs_server *server,
+			      struct afs_vnode *vnode)
+{
+	struct afs_server_callslot callslot;
+	struct rxrpc_call *call;
+	struct kvec piov[1];
+	size_t sent;
+	int ret;
+	__be32 *bp;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	_enter("%p,{%u,%u,%u}",
+	       server, vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique);
+
+	/* get hold of the fileserver connection */
+	ret = afs_server_request_callslot(server, &callslot);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(callslot.conn, NULL, NULL, afs_rxfs_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = FSGIVEUPCALLBACKS;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	bp = rxrpc_call_alloc_scratch(call, (1 + 4 + 4) * 4);
+
+	piov[0].iov_len = (1 + 4 + 4) * 4;
+	piov[0].iov_base = bp;
+
+	*bp++ = htonl(FSGIVEUPCALLBACKS);
+	*bp++ = htonl(1);
+	*bp++ = htonl(vnode->fid.vid);
+	*bp++ = htonl(vnode->fid.vnode);
+	*bp++ = htonl(vnode->fid.unique);
+	*bp++ = htonl(1);
+	*bp++ = htonl(vnode->cb_version);
+	*bp++ = htonl(vnode->cb_expiry);
+	*bp++ = htonl(vnode->cb_type);
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY ||
+		    signal_pending(current))
+			break;
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+
+	ret = -EINTR;
+	if (signal_pending(current))
+		goto abort;
+
+	switch (call->app_call_state) {
+	case RXRPC_CSTATE_ERROR:
+		ret = call->app_errno;
+		goto out_unwait;
+
+	case RXRPC_CSTATE_CLNT_GOT_REPLY:
+		ret = 0;
+		goto out_unwait;
+
+	default:
+		BUG();
+	}
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	afs_server_release_callslot(server, &callslot);
+ out:
+	_leave("");
+	return ret;
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+} /* end afs_rxfs_give_up_callback() */
+
+/*****************************************************************************/
+/*
+ * look a filename up in a directory
+ * - this operation doesn't seem to work correctly in OpenAFS server 1.2.2
+ */
+#if 0
+int afs_rxfs_lookup(struct afs_server *server,
+		    struct afs_vnode *dir,
+		    const char *filename,
+		    struct afs_vnode *vnode,
+		    struct afs_volsync *volsync)
+{
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[3];
+	size_t sent;
+	int ret;
+	u32 *bp, zero;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	kenter("%p,{%u,%u,%u},%s",
+	       server, fid->vid, fid->vnode, fid->unique, filename);
+
+	/* get hold of the fileserver connection */
+	ret = afs_server_get_fsconn(server, &conn);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn, NULL, NULL, afs_rxfs_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = FSLOOKUP;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq,&myself);
+
+	/* marshall the parameters */
+	bp = rxrpc_call_alloc_scratch(call, 20);
+
+	zero = 0;
+
+	piov[0].iov_len = 20;
+	piov[0].iov_base = bp;
+	piov[1].iov_len = strlen(filename);
+	piov[1].iov_base = (char *) filename;
+	piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3;
+	piov[2].iov_base = &zero;
+
+	*bp++ = htonl(FSLOOKUP);
+	*bp++ = htonl(dirfid->vid);
+	*bp++ = htonl(dirfid->vnode);
+	*bp++ = htonl(dirfid->unique);
+	*bp++ = htonl(piov[1].iov_len);
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	bp = rxrpc_call_alloc_scratch(call, 220);
+
+	ret = rxrpc_call_read_data(call, bp, 220,
+				   RXRPC_CALL_READ_BLOCK |
+				   RXRPC_CALL_READ_ALL);
+	if (ret < 0) {
+		if (ret == -ECONNABORTED) {
+			ret = call->app_errno;
+			goto out_unwait;
+		}
+		goto abort;
+	}
+
+	/* unmarshall the reply */
+	fid->vid		= ntohl(*bp++);
+	fid->vnode		= ntohl(*bp++);
+	fid->unique		= ntohl(*bp++);
+
+	vnode->status.if_version	= ntohl(*bp++);
+	vnode->status.type		= ntohl(*bp++);
+	vnode->status.nlink		= ntohl(*bp++);
+	vnode->status.size		= ntohl(*bp++);
+	vnode->status.version		= ntohl(*bp++);
+	vnode->status.author		= ntohl(*bp++);
+	vnode->status.owner		= ntohl(*bp++);
+	vnode->status.caller_access	= ntohl(*bp++);
+	vnode->status.anon_access	= ntohl(*bp++);
+	vnode->status.mode		= ntohl(*bp++);
+	vnode->status.parent.vid	= dirfid->vid;
+	vnode->status.parent.vnode	= ntohl(*bp++);
+	vnode->status.parent.unique	= ntohl(*bp++);
+	bp++; /* seg size */
+	vnode->status.mtime_client	= ntohl(*bp++);
+	vnode->status.mtime_server	= ntohl(*bp++);
+	bp++; /* group */
+	bp++; /* sync counter */
+	vnode->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
+	bp++; /* spare2 */
+	bp++; /* spare3 */
+	bp++; /* spare4 */
+
+	dir->status.if_version		= ntohl(*bp++);
+	dir->status.type		= ntohl(*bp++);
+	dir->status.nlink		= ntohl(*bp++);
+	dir->status.size		= ntohl(*bp++);
+	dir->status.version		= ntohl(*bp++);
+	dir->status.author		= ntohl(*bp++);
+	dir->status.owner		= ntohl(*bp++);
+	dir->status.caller_access	= ntohl(*bp++);
+	dir->status.anon_access		= ntohl(*bp++);
+	dir->status.mode		= ntohl(*bp++);
+	dir->status.parent.vid		= dirfid->vid;
+	dir->status.parent.vnode	= ntohl(*bp++);
+	dir->status.parent.unique	= ntohl(*bp++);
+	bp++; /* seg size */
+	dir->status.mtime_client	= ntohl(*bp++);
+	dir->status.mtime_server	= ntohl(*bp++);
+	bp++; /* group */
+	bp++; /* sync counter */
+	dir->status.version |= ((unsigned long long) ntohl(*bp++)) << 32;
+	bp++; /* spare2 */
+	bp++; /* spare3 */
+	bp++; /* spare4 */
+
+	callback->fid		= *fid;
+	callback->version	= ntohl(*bp++);
+	callback->expiry	= ntohl(*bp++);
+	callback->type		= ntohl(*bp++);
+
+	if (volsync) {
+		volsync->creation	= ntohl(*bp++);
+		bp++; /* spare2 */
+		bp++; /* spare3 */
+		bp++; /* spare4 */
+		bp++; /* spare5 */
+		bp++; /* spare6 */
+	}
+
+	/* success */
+	ret = 0;
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	afs_server_release_fsconn(server, conn);
+ out:
+	kleave("");
+	return ret;
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+} /* end afs_rxfs_lookup() */
+#endif
diff --git a/fs/afs/fsclient.h b/fs/afs/fsclient.h
new file mode 100644
index 000000000000..8ba3e749ee3c
--- /dev/null
+++ b/fs/afs/fsclient.h
@@ -0,0 +1,54 @@
+/* fsclient.h: AFS File Server client stub declarations
+ *
+ * Copyright (C) 2002 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_AFS_FSCLIENT_H
+#define _LINUX_AFS_FSCLIENT_H
+
+#include "server.h"
+
+extern int afs_rxfs_get_volume_info(struct afs_server *server,
+				    const char *name,
+				    struct afs_volume_info *vinfo);
+
+extern int afs_rxfs_fetch_file_status(struct afs_server *server,
+				      struct afs_vnode *vnode,
+				      struct afs_volsync *volsync);
+
+struct afs_rxfs_fetch_descriptor {
+	struct afs_fid	fid;		/* file ID to fetch */
+	size_t		size;		/* total number of bytes to fetch */
+	off_t		offset;		/* offset in file to start from */
+	void		*buffer;	/* read buffer */
+	size_t		actual;		/* actual size sent back by server */
+};
+
+extern int afs_rxfs_fetch_file_data(struct afs_server *server,
+				    struct afs_vnode *vnode,
+				    struct afs_rxfs_fetch_descriptor *desc,
+				    struct afs_volsync *volsync);
+
+extern int afs_rxfs_give_up_callback(struct afs_server *server,
+				     struct afs_vnode *vnode);
+
+/* this doesn't appear to work in OpenAFS server */
+extern int afs_rxfs_lookup(struct afs_server *server,
+			   struct afs_vnode *dir,
+			   const char *filename,
+			   struct afs_vnode *vnode,
+			   struct afs_volsync *volsync);
+
+/* this is apparently mis-implemented in OpenAFS server */
+extern int afs_rxfs_get_root_volume(struct afs_server *server,
+				    char *buf,
+				    size_t *buflen);
+
+
+#endif /* _LINUX_AFS_FSCLIENT_H */
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
new file mode 100644
index 000000000000..c476fde33fbc
--- /dev/null
+++ b/fs/afs/inode.c
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2002 Red Hat, Inc. All rights reserved.
+ *
+ * This software may be freely redistributed under the terms of the
+ * GNU General Public License.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: David Woodhouse <dwmw2@cambridge.redhat.com>
+ *          David Howells <dhowells@redhat.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include "volume.h"
+#include "vnode.h"
+#include "super.h"
+#include "internal.h"
+
+struct afs_iget_data {
+	struct afs_fid		fid;
+	struct afs_volume	*volume;	/* volume on which resides */
+};
+
+/*****************************************************************************/
+/*
+ * map the AFS file status to the inode member variables
+ */
+static int afs_inode_map_status(struct afs_vnode *vnode)
+{
+	struct inode *inode = AFS_VNODE_TO_I(vnode);
+
+	_debug("FS: ft=%d lk=%d sz=%Zu ver=%Lu mod=%hu",
+	       vnode->status.type,
+	       vnode->status.nlink,
+	       vnode->status.size,
+	       vnode->status.version,
+	       vnode->status.mode);
+
+	switch (vnode->status.type) {
+	case AFS_FTYPE_FILE:
+		inode->i_mode	= S_IFREG | vnode->status.mode;
+		inode->i_op	= &afs_file_inode_operations;
+		inode->i_fop	= &afs_file_file_operations;
+		break;
+	case AFS_FTYPE_DIR:
+		inode->i_mode	= S_IFDIR | vnode->status.mode;
+		inode->i_op	= &afs_dir_inode_operations;
+		inode->i_fop	= &afs_dir_file_operations;
+		break;
+	case AFS_FTYPE_SYMLINK:
+		inode->i_mode	= S_IFLNK | vnode->status.mode;
+		inode->i_op	= &page_symlink_inode_operations;
+		break;
+	default:
+		printk("kAFS: AFS vnode with undefined type\n");
+		return -EBADMSG;
+	}
+
+	inode->i_nlink		= vnode->status.nlink;
+	inode->i_uid		= vnode->status.owner;
+	inode->i_gid		= 0;
+	inode->i_size		= vnode->status.size;
+	inode->i_ctime.tv_sec	= vnode->status.mtime_server;
+	inode->i_ctime.tv_nsec	= 0;
+	inode->i_atime		= inode->i_mtime = inode->i_ctime;
+	inode->i_blksize	= PAGE_CACHE_SIZE;
+	inode->i_blocks		= 0;
+	inode->i_version	= vnode->fid.unique;
+	inode->i_mapping->a_ops	= &afs_fs_aops;
+
+	/* check to see whether a symbolic link is really a mountpoint */
+	if (vnode->status.type == AFS_FTYPE_SYMLINK) {
+		afs_mntpt_check_symlink(vnode);
+
+		if (vnode->flags & AFS_VNODE_MOUNTPOINT) {
+			inode->i_mode	= S_IFDIR | vnode->status.mode;
+			inode->i_op	= &afs_mntpt_inode_operations;
+			inode->i_fop	= &afs_mntpt_file_operations;
+		}
+	}
+
+	return 0;
+} /* end afs_inode_map_status() */
+
+/*****************************************************************************/
+/*
+ * attempt to fetch the status of an inode, coelescing multiple simultaneous
+ * fetches
+ */
+static int afs_inode_fetch_status(struct inode *inode)
+{
+	struct afs_vnode *vnode;
+	int ret;
+
+	vnode = AFS_FS_I(inode);
+
+	ret = afs_vnode_fetch_status(vnode);
+
+	if (ret == 0)
+		ret = afs_inode_map_status(vnode);
+
+	return ret;
+
+} /* end afs_inode_fetch_status() */
+
+/*****************************************************************************/
+/*
+ * iget5() comparator
+ */
+static int afs_iget5_test(struct inode *inode, void *opaque)
+{
+	struct afs_iget_data *data = opaque;
+
+	return inode->i_ino == data->fid.vnode &&
+		inode->i_version == data->fid.unique;
+} /* end afs_iget5_test() */
+
+/*****************************************************************************/
+/*
+ * iget5() inode initialiser
+ */
+static int afs_iget5_set(struct inode *inode, void *opaque)
+{
+	struct afs_iget_data *data = opaque;
+	struct afs_vnode *vnode = AFS_FS_I(inode);
+
+	inode->i_ino = data->fid.vnode;
+	inode->i_version = data->fid.unique;
+	vnode->fid = data->fid;
+	vnode->volume = data->volume;
+
+	return 0;
+} /* end afs_iget5_set() */
+
+/*****************************************************************************/
+/*
+ * inode retrieval
+ */
+inline int afs_iget(struct super_block *sb, struct afs_fid *fid,
+		    struct inode **_inode)
+{
+	struct afs_iget_data data = { .fid = *fid };
+	struct afs_super_info *as;
+	struct afs_vnode *vnode;
+	struct inode *inode;
+	int ret;
+
+	_enter(",{%u,%u,%u},,", fid->vid, fid->vnode, fid->unique);
+
+	as = sb->s_fs_info;
+	data.volume = as->volume;
+
+	inode = iget5_locked(sb, fid->vnode, afs_iget5_test, afs_iget5_set,
+			     &data);
+	if (!inode) {
+		_leave(" = -ENOMEM");
+		return -ENOMEM;
+	}
+
+	vnode = AFS_FS_I(inode);
+
+	/* deal with an existing inode */
+	if (!(inode->i_state & I_NEW)) {
+		ret = afs_vnode_fetch_status(vnode);
+		if (ret==0)
+			*_inode = inode;
+		else
+			iput(inode);
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+#ifdef AFS_CACHING_SUPPORT
+	/* set up caching before reading the status, as fetch-status reads the
+	 * first page of symlinks to see if they're really mntpts */
+	cachefs_acquire_cookie(vnode->volume->cache,
+			       NULL,
+			       vnode,
+			       &vnode->cache);
+#endif
+
+	/* okay... it's a new inode */
+	inode->i_flags |= S_NOATIME;
+	vnode->flags |= AFS_VNODE_CHANGED;
+	ret = afs_inode_fetch_status(inode);
+	if (ret<0)
+		goto bad_inode;
+
+	/* success */
+	unlock_new_inode(inode);
+
+	*_inode = inode;
+	_leave(" = 0 [CB { v=%u x=%lu t=%u }]",
+	       vnode->cb_version,
+	       vnode->cb_timeout.timo_jif,
+	       vnode->cb_type);
+	return 0;
+
+	/* failure */
+ bad_inode:
+	make_bad_inode(inode);
+	unlock_new_inode(inode);
+	iput(inode);
+
+	_leave(" = %d [bad]", ret);
+	return ret;
+} /* end afs_iget() */
+
+/*****************************************************************************/
+/*
+ * read the attributes of an inode
+ */
+int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry,
+		      struct kstat *stat)
+{
+	struct afs_vnode *vnode;
+	struct inode *inode;
+	int ret;
+
+	inode = dentry->d_inode;
+
+	_enter("{ ino=%lu v=%lu }", inode->i_ino, inode->i_version);
+
+	vnode = AFS_FS_I(inode);
+
+	ret = afs_inode_fetch_status(inode);
+	if (ret == -ENOENT) {
+		_leave(" = %d [%d %p]",
+		       ret, atomic_read(&dentry->d_count), dentry->d_inode);
+		return ret;
+	}
+	else if (ret < 0) {
+		make_bad_inode(inode);
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+	/* transfer attributes from the inode structure to the stat
+	 * structure */
+	generic_fillattr(inode, stat);
+
+	_leave(" = 0 CB { v=%u x=%u t=%u }",
+	       vnode->cb_version,
+	       vnode->cb_expiry,
+	       vnode->cb_type);
+
+	return 0;
+} /* end afs_inode_getattr() */
+
+/*****************************************************************************/
+/*
+ * clear an AFS inode
+ */
+void afs_clear_inode(struct inode *inode)
+{
+	struct afs_vnode *vnode;
+
+	vnode = AFS_FS_I(inode);
+
+	_enter("ino=%lu { vn=%08x v=%u x=%u t=%u }",
+	       inode->i_ino,
+	       vnode->fid.vnode,
+	       vnode->cb_version,
+	       vnode->cb_expiry,
+	       vnode->cb_type
+	       );
+
+	BUG_ON(inode->i_ino != vnode->fid.vnode);
+
+	afs_vnode_give_up_callback(vnode);
+
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_relinquish_cookie(vnode->cache, 0);
+	vnode->cache = NULL;
+#endif
+
+	_leave("");
+} /* end afs_clear_inode() */
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
new file mode 100644
index 000000000000..f09860b45c1a
--- /dev/null
+++ b/fs/afs/internal.h
@@ -0,0 +1,140 @@
+/* internal.h: internal AFS stuff
+ *
+ * Copyright (C) 2002 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 AFS_INTERNAL_H
+#define AFS_INTERNAL_H
+
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+
+/*
+ * debug tracing
+ */
+#define kenter(FMT, a...)	printk("==> %s("FMT")\n",__FUNCTION__ , ## a)
+#define kleave(FMT, a...)	printk("<== %s()"FMT"\n",__FUNCTION__ , ## a)
+#define kdebug(FMT, a...)	printk(FMT"\n" , ## a)
+#define kproto(FMT, a...)	printk("### "FMT"\n" , ## a)
+#define knet(FMT, a...)		printk(FMT"\n" , ## a)
+
+#ifdef __KDEBUG
+#define _enter(FMT, a...)	kenter(FMT , ## a)
+#define _leave(FMT, a...)	kleave(FMT , ## a)
+#define _debug(FMT, a...)	kdebug(FMT , ## a)
+#define _proto(FMT, a...)	kproto(FMT , ## a)
+#define _net(FMT, a...)		knet(FMT , ## a)
+#else
+#define _enter(FMT, a...)	do { } while(0)
+#define _leave(FMT, a...)	do { } while(0)
+#define _debug(FMT, a...)	do { } while(0)
+#define _proto(FMT, a...)	do { } while(0)
+#define _net(FMT, a...)		do { } while(0)
+#endif
+
+static inline void afs_discard_my_signals(void)
+{
+	while (signal_pending(current)) {
+		siginfo_t sinfo;
+
+		spin_lock_irq(&current->sighand->siglock);
+		dequeue_signal(current,&current->blocked, &sinfo);
+		spin_unlock_irq(&current->sighand->siglock);
+	}
+}
+
+/*
+ * cell.c
+ */
+extern struct rw_semaphore afs_proc_cells_sem;
+extern struct list_head afs_proc_cells;
+#ifdef AFS_CACHING_SUPPORT
+extern struct cachefs_index_def afs_cache_cell_index_def;
+#endif
+
+/*
+ * dir.c
+ */
+extern struct inode_operations afs_dir_inode_operations;
+extern struct file_operations afs_dir_file_operations;
+
+/*
+ * file.c
+ */
+extern struct address_space_operations afs_fs_aops;
+extern struct inode_operations afs_file_inode_operations;
+extern struct file_operations afs_file_file_operations;
+
+#ifdef AFS_CACHING_SUPPORT
+extern int afs_cache_get_page_cookie(struct page *page,
+				     struct cachefs_page **_page_cookie);
+#endif
+
+/*
+ * inode.c
+ */
+extern int afs_iget(struct super_block *sb, struct afs_fid *fid,
+		    struct inode **_inode);
+extern int afs_inode_getattr(struct vfsmount *mnt, struct dentry *dentry,
+			     struct kstat *stat);
+extern void afs_clear_inode(struct inode *inode);
+
+/*
+ * key_afs.c
+ */
+#ifdef CONFIG_KEYS
+extern int afs_key_register(void);
+extern void afs_key_unregister(void);
+#endif
+
+/*
+ * main.c
+ */
+#ifdef AFS_CACHING_SUPPORT
+extern struct cachefs_netfs afs_cache_netfs;
+#endif
+
+/*
+ * mntpt.c
+ */
+extern struct inode_operations afs_mntpt_inode_operations;
+extern struct file_operations afs_mntpt_file_operations;
+extern struct afs_timer afs_mntpt_expiry_timer;
+extern struct afs_timer_ops afs_mntpt_expiry_timer_ops;
+extern unsigned long afs_mntpt_expiry_timeout;
+
+extern int afs_mntpt_check_symlink(struct afs_vnode *vnode);
+
+/*
+ * super.c
+ */
+extern int afs_fs_init(void);
+extern void afs_fs_exit(void);
+
+#define AFS_CB_HASH_COUNT (PAGE_SIZE / sizeof(struct list_head))
+
+extern struct list_head afs_cb_hash_tbl[];
+extern spinlock_t afs_cb_hash_lock;
+
+#define afs_cb_hash(SRV,FID) \
+	afs_cb_hash_tbl[((unsigned long)(SRV) + \
+			(FID)->vid + (FID)->vnode + (FID)->unique) % \
+			AFS_CB_HASH_COUNT]
+
+/*
+ * proc.c
+ */
+extern int afs_proc_init(void);
+extern void afs_proc_cleanup(void);
+extern int afs_proc_cell_setup(struct afs_cell *cell);
+extern void afs_proc_cell_remove(struct afs_cell *cell);
+
+#endif /* AFS_INTERNAL_H */
diff --git a/fs/afs/kafsasyncd.c b/fs/afs/kafsasyncd.c
new file mode 100644
index 000000000000..6fc88ae8ad94
--- /dev/null
+++ b/fs/afs/kafsasyncd.c
@@ -0,0 +1,257 @@
+/* kafsasyncd.c: AFS asynchronous operation daemon
+ *
+ * Copyright (C) 2002 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.
+ *
+ *
+ * The AFS async daemon is used to the following:
+ * - probe "dead" servers to see whether they've come back to life yet.
+ * - probe "live" servers that we haven't talked to for a while to see if they are better
+ *   candidates for serving than what we're currently using
+ * - poll volume location servers to keep up to date volume location lists
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include "cell.h"
+#include "server.h"
+#include "volume.h"
+#include "kafsasyncd.h"
+#include "kafstimod.h"
+#include <rxrpc/call.h>
+#include <asm/errno.h>
+#include "internal.h"
+
+static DECLARE_COMPLETION(kafsasyncd_alive);
+static DECLARE_COMPLETION(kafsasyncd_dead);
+static DECLARE_WAIT_QUEUE_HEAD(kafsasyncd_sleepq);
+static struct task_struct *kafsasyncd_task;
+static int kafsasyncd_die;
+
+static int kafsasyncd(void *arg);
+
+static LIST_HEAD(kafsasyncd_async_attnq);
+static LIST_HEAD(kafsasyncd_async_busyq);
+static DEFINE_SPINLOCK(kafsasyncd_async_lock);
+
+static void kafsasyncd_null_call_attn_func(struct rxrpc_call *call)
+{
+}
+
+static void kafsasyncd_null_call_error_func(struct rxrpc_call *call)
+{
+}
+
+/*****************************************************************************/
+/*
+ * start the async daemon
+ */
+int afs_kafsasyncd_start(void)
+{
+	int ret;
+
+	ret = kernel_thread(kafsasyncd, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	wait_for_completion(&kafsasyncd_alive);
+
+	return ret;
+} /* end afs_kafsasyncd_start() */
+
+/*****************************************************************************/
+/*
+ * stop the async daemon
+ */
+void afs_kafsasyncd_stop(void)
+{
+	/* get rid of my daemon */
+	kafsasyncd_die = 1;
+	wake_up(&kafsasyncd_sleepq);
+	wait_for_completion(&kafsasyncd_dead);
+
+} /* end afs_kafsasyncd_stop() */
+
+/*****************************************************************************/
+/*
+ * probing daemon
+ */
+static int kafsasyncd(void *arg)
+{
+	struct afs_async_op *op;
+	int die;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	kafsasyncd_task = current;
+
+	printk("kAFS: Started kafsasyncd %d\n", current->pid);
+
+	daemonize("kafsasyncd");
+
+	complete(&kafsasyncd_alive);
+
+	/* loop around looking for things to attend to */
+	do {
+		set_current_state(TASK_INTERRUPTIBLE);
+		add_wait_queue(&kafsasyncd_sleepq, &myself);
+
+		for (;;) {
+			if (!list_empty(&kafsasyncd_async_attnq) ||
+			    signal_pending(current) ||
+			    kafsasyncd_die)
+				break;
+
+			schedule();
+			set_current_state(TASK_INTERRUPTIBLE);
+		}
+
+		remove_wait_queue(&kafsasyncd_sleepq, &myself);
+		set_current_state(TASK_RUNNING);
+
+		try_to_freeze(PF_FREEZE);
+
+		/* discard pending signals */
+		afs_discard_my_signals();
+
+		die = kafsasyncd_die;
+
+		/* deal with the next asynchronous operation requiring
+		 * attention */
+		if (!list_empty(&kafsasyncd_async_attnq)) {
+			struct afs_async_op *op;
+
+			_debug("@@@ Begin Asynchronous Operation");
+
+			op = NULL;
+			spin_lock(&kafsasyncd_async_lock);
+
+			if (!list_empty(&kafsasyncd_async_attnq)) {
+				op = list_entry(kafsasyncd_async_attnq.next,
+						struct afs_async_op, link);
+				list_del(&op->link);
+				list_add_tail(&op->link,
+					      &kafsasyncd_async_busyq);
+			}
+
+			spin_unlock(&kafsasyncd_async_lock);
+
+			_debug("@@@ Operation %p {%p}\n",
+			       op, op ? op->ops : NULL);
+
+			if (op)
+				op->ops->attend(op);
+
+			_debug("@@@ End Asynchronous Operation");
+		}
+
+	} while(!die);
+
+	/* need to kill all outstanding asynchronous operations before
+	 * exiting */
+	kafsasyncd_task = NULL;
+	spin_lock(&kafsasyncd_async_lock);
+
+	/* fold the busy and attention queues together */
+	list_splice_init(&kafsasyncd_async_busyq,
+			 &kafsasyncd_async_attnq);
+
+	/* dequeue kafsasyncd from all their wait queues */
+	list_for_each_entry(op, &kafsasyncd_async_attnq, link) {
+		op->call->app_attn_func = kafsasyncd_null_call_attn_func;
+		op->call->app_error_func = kafsasyncd_null_call_error_func;
+		remove_wait_queue(&op->call->waitq, &op->waiter);
+	}
+
+	spin_unlock(&kafsasyncd_async_lock);
+
+	/* abort all the operations */
+	while (!list_empty(&kafsasyncd_async_attnq)) {
+		op = list_entry(kafsasyncd_async_attnq.next, struct afs_async_op, link);
+		list_del_init(&op->link);
+
+		rxrpc_call_abort(op->call, -EIO);
+		rxrpc_put_call(op->call);
+		op->call = NULL;
+
+		op->ops->discard(op);
+	}
+
+	/* and that's all */
+	_leave("");
+	complete_and_exit(&kafsasyncd_dead, 0);
+
+} /* end kafsasyncd() */
+
+/*****************************************************************************/
+/*
+ * begin an operation
+ * - place operation on busy queue
+ */
+void afs_kafsasyncd_begin_op(struct afs_async_op *op)
+{
+	_enter("");
+
+	spin_lock(&kafsasyncd_async_lock);
+
+	init_waitqueue_entry(&op->waiter, kafsasyncd_task);
+	add_wait_queue(&op->call->waitq, &op->waiter);
+
+	list_del(&op->link);
+	list_add_tail(&op->link, &kafsasyncd_async_busyq);
+
+	spin_unlock(&kafsasyncd_async_lock);
+
+	_leave("");
+} /* end afs_kafsasyncd_begin_op() */
+
+/*****************************************************************************/
+/*
+ * request attention for an operation
+ * - move to attention queue
+ */
+void afs_kafsasyncd_attend_op(struct afs_async_op *op)
+{
+	_enter("");
+
+	spin_lock(&kafsasyncd_async_lock);
+
+	list_del(&op->link);
+	list_add_tail(&op->link, &kafsasyncd_async_attnq);
+
+	spin_unlock(&kafsasyncd_async_lock);
+
+	wake_up(&kafsasyncd_sleepq);
+
+	_leave("");
+} /* end afs_kafsasyncd_attend_op() */
+
+/*****************************************************************************/
+/*
+ * terminate an operation
+ * - remove from either queue
+ */
+void afs_kafsasyncd_terminate_op(struct afs_async_op *op)
+{
+	_enter("");
+
+	spin_lock(&kafsasyncd_async_lock);
+
+	if (!list_empty(&op->link)) {
+		list_del_init(&op->link);
+		remove_wait_queue(&op->call->waitq, &op->waiter);
+	}
+
+	spin_unlock(&kafsasyncd_async_lock);
+
+	wake_up(&kafsasyncd_sleepq);
+
+	_leave("");
+} /* end afs_kafsasyncd_terminate_op() */
diff --git a/fs/afs/kafsasyncd.h b/fs/afs/kafsasyncd.h
new file mode 100644
index 000000000000..791803f9a6fb
--- /dev/null
+++ b/fs/afs/kafsasyncd.h
@@ -0,0 +1,52 @@
+/* kafsasyncd.h: AFS asynchronous operation daemon
+ *
+ * Copyright (C) 2002 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_AFS_KAFSASYNCD_H
+#define _LINUX_AFS_KAFSASYNCD_H
+
+#include "types.h"
+
+struct afs_async_op;
+
+struct afs_async_op_ops {
+	void (*attend)(struct afs_async_op *op);
+	void (*discard)(struct afs_async_op *op);
+};
+
+/*****************************************************************************/
+/*
+ * asynchronous operation record
+ */
+struct afs_async_op
+{
+	struct list_head		link;
+	struct afs_server		*server;	/* server being contacted */
+	struct rxrpc_call		*call;		/* RxRPC call performing op */
+	wait_queue_t			waiter;		/* wait queue for kafsasyncd */
+	const struct afs_async_op_ops	*ops;		/* operations */
+};
+
+static inline void afs_async_op_init(struct afs_async_op *op,
+				     const struct afs_async_op_ops *ops)
+{
+	INIT_LIST_HEAD(&op->link);
+	op->call = NULL;
+	op->ops = ops;
+}
+
+extern int afs_kafsasyncd_start(void);
+extern void afs_kafsasyncd_stop(void);
+
+extern void afs_kafsasyncd_begin_op(struct afs_async_op *op);
+extern void afs_kafsasyncd_attend_op(struct afs_async_op *op);
+extern void afs_kafsasyncd_terminate_op(struct afs_async_op *op);
+
+#endif /* _LINUX_AFS_KAFSASYNCD_H */
diff --git a/fs/afs/kafstimod.c b/fs/afs/kafstimod.c
new file mode 100644
index 000000000000..86e710dd057e
--- /dev/null
+++ b/fs/afs/kafstimod.c
@@ -0,0 +1,204 @@
+/* kafstimod.c: AFS timeout daemon
+ *
+ * Copyright (C) 2002 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 "cell.h"
+#include "volume.h"
+#include "kafstimod.h"
+#include <asm/errno.h>
+#include "internal.h"
+
+static DECLARE_COMPLETION(kafstimod_alive);
+static DECLARE_COMPLETION(kafstimod_dead);
+static DECLARE_WAIT_QUEUE_HEAD(kafstimod_sleepq);
+static int kafstimod_die;
+
+static LIST_HEAD(kafstimod_list);
+static DEFINE_SPINLOCK(kafstimod_lock);
+
+static int kafstimod(void *arg);
+
+/*****************************************************************************/
+/*
+ * start the timeout daemon
+ */
+int afs_kafstimod_start(void)
+{
+	int ret;
+
+	ret = kernel_thread(kafstimod, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	wait_for_completion(&kafstimod_alive);
+
+	return ret;
+} /* end afs_kafstimod_start() */
+
+/*****************************************************************************/
+/*
+ * stop the timeout daemon
+ */
+void afs_kafstimod_stop(void)
+{
+	/* get rid of my daemon */
+	kafstimod_die = 1;
+	wake_up(&kafstimod_sleepq);
+	wait_for_completion(&kafstimod_dead);
+
+} /* end afs_kafstimod_stop() */
+
+/*****************************************************************************/
+/*
+ * timeout processing daemon
+ */
+static int kafstimod(void *arg)
+{
+	struct afs_timer *timer;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	printk("kAFS: Started kafstimod %d\n", current->pid);
+
+	daemonize("kafstimod");
+
+	complete(&kafstimod_alive);
+
+	/* loop around looking for things to attend to */
+ loop:
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&kafstimod_sleepq, &myself);
+
+	for (;;) {
+		unsigned long jif;
+		signed long timeout;
+
+		/* deal with the server being asked to die */
+		if (kafstimod_die) {
+			remove_wait_queue(&kafstimod_sleepq, &myself);
+			_leave("");
+			complete_and_exit(&kafstimod_dead, 0);
+		}
+
+		try_to_freeze(PF_FREEZE);
+
+		/* discard pending signals */
+		afs_discard_my_signals();
+
+		/* work out the time to elapse before the next event */
+		spin_lock(&kafstimod_lock);
+		if (list_empty(&kafstimod_list)) {
+			timeout = MAX_SCHEDULE_TIMEOUT;
+		}
+		else {
+			timer = list_entry(kafstimod_list.next,
+					   struct afs_timer, link);
+			timeout = timer->timo_jif;
+			jif = jiffies;
+
+			if (time_before_eq((unsigned long) timeout, jif))
+				goto immediate;
+
+			else {
+				timeout = (long) timeout - (long) jiffies;
+			}
+		}
+		spin_unlock(&kafstimod_lock);
+
+		schedule_timeout(timeout);
+
+		set_current_state(TASK_INTERRUPTIBLE);
+	}
+
+	/* the thing on the front of the queue needs processing
+	 * - we come here with the lock held and timer pointing to the expired
+	 *   entry
+	 */
+ immediate:
+	remove_wait_queue(&kafstimod_sleepq, &myself);
+	set_current_state(TASK_RUNNING);
+
+	_debug("@@@ Begin Timeout of %p", timer);
+
+	/* dequeue the timer */
+	list_del_init(&timer->link);
+	spin_unlock(&kafstimod_lock);
+
+	/* call the timeout function */
+	timer->ops->timed_out(timer);
+
+	_debug("@@@ End Timeout");
+	goto loop;
+
+} /* end kafstimod() */
+
+/*****************************************************************************/
+/*
+ * (re-)queue a timer
+ */
+void afs_kafstimod_add_timer(struct afs_timer *timer, unsigned long timeout)
+{
+	struct afs_timer *ptimer;
+	struct list_head *_p;
+
+	_enter("%p,%lu", timer, timeout);
+
+	spin_lock(&kafstimod_lock);
+
+	list_del(&timer->link);
+
+	/* the timer was deferred or reset - put it back in the queue at the
+	 * right place */
+	timer->timo_jif = jiffies + timeout;
+
+	list_for_each(_p, &kafstimod_list) {
+		ptimer = list_entry(_p, struct afs_timer, link);
+		if (time_before(timer->timo_jif, ptimer->timo_jif))
+			break;
+	}
+
+	list_add_tail(&timer->link, _p); /* insert before stopping point */
+
+	spin_unlock(&kafstimod_lock);
+
+	wake_up(&kafstimod_sleepq);
+
+	_leave("");
+} /* end afs_kafstimod_add_timer() */
+
+/*****************************************************************************/
+/*
+ * dequeue a timer
+ * - returns 0 if the timer was deleted or -ENOENT if it wasn't queued
+ */
+int afs_kafstimod_del_timer(struct afs_timer *timer)
+{
+	int ret = 0;
+
+	_enter("%p", timer);
+
+	spin_lock(&kafstimod_lock);
+
+	if (list_empty(&timer->link))
+		ret = -ENOENT;
+	else
+		list_del_init(&timer->link);
+
+	spin_unlock(&kafstimod_lock);
+
+	wake_up(&kafstimod_sleepq);
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_kafstimod_del_timer() */
diff --git a/fs/afs/kafstimod.h b/fs/afs/kafstimod.h
new file mode 100644
index 000000000000..e312f1a61a7f
--- /dev/null
+++ b/fs/afs/kafstimod.h
@@ -0,0 +1,49 @@
+/* kafstimod.h: AFS timeout daemon
+ *
+ * Copyright (C) 2002 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_AFS_KAFSTIMOD_H
+#define _LINUX_AFS_KAFSTIMOD_H
+
+#include "types.h"
+
+struct afs_timer;
+
+struct afs_timer_ops {
+	/* called when the front of the timer queue has timed out */
+	void (*timed_out)(struct afs_timer *timer);
+};
+
+/*****************************************************************************/
+/*
+ * AFS timer/timeout record
+ */
+struct afs_timer
+{
+	struct list_head		link;		/* link in timer queue */
+	unsigned long			timo_jif;	/* timeout time */
+	const struct afs_timer_ops	*ops;		/* timeout expiry function */
+};
+
+static inline void afs_timer_init(struct afs_timer *timer,
+				  const struct afs_timer_ops *ops)
+{
+	INIT_LIST_HEAD(&timer->link);
+	timer->ops = ops;
+}
+
+extern int afs_kafstimod_start(void);
+extern void afs_kafstimod_stop(void);
+
+extern void afs_kafstimod_add_timer(struct afs_timer *timer,
+				    unsigned long timeout);
+extern int afs_kafstimod_del_timer(struct afs_timer *timer);
+
+#endif /* _LINUX_AFS_KAFSTIMOD_H */
diff --git a/fs/afs/main.c b/fs/afs/main.c
new file mode 100644
index 000000000000..913c689bdb35
--- /dev/null
+++ b/fs/afs/main.c
@@ -0,0 +1,286 @@
+/* main.c: AFS client file system
+ *
+ * Copyright (C) 2002 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/moduleparam.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/completion.h>
+#include <rxrpc/rxrpc.h>
+#include <rxrpc/transport.h>
+#include <rxrpc/call.h>
+#include <rxrpc/peer.h>
+#include "cache.h"
+#include "cell.h"
+#include "server.h"
+#include "fsclient.h"
+#include "cmservice.h"
+#include "kafstimod.h"
+#include "kafsasyncd.h"
+#include "internal.h"
+
+struct rxrpc_transport *afs_transport;
+
+static int afs_adding_peer(struct rxrpc_peer *peer);
+static void afs_discarding_peer(struct rxrpc_peer *peer);
+
+
+MODULE_DESCRIPTION("AFS Client File System");
+MODULE_AUTHOR("Red Hat, Inc.");
+MODULE_LICENSE("GPL");
+
+static char *rootcell;
+
+module_param(rootcell, charp, 0);
+MODULE_PARM_DESC(rootcell, "root AFS cell name and VL server IP addr list");
+
+
+static struct rxrpc_peer_ops afs_peer_ops = {
+	.adding		= afs_adding_peer,
+	.discarding	= afs_discarding_peer,
+};
+
+struct list_head afs_cb_hash_tbl[AFS_CB_HASH_COUNT];
+DEFINE_SPINLOCK(afs_cb_hash_lock);
+
+#ifdef AFS_CACHING_SUPPORT
+static struct cachefs_netfs_operations afs_cache_ops = {
+	.get_page_cookie	= afs_cache_get_page_cookie,
+};
+
+struct cachefs_netfs afs_cache_netfs = {
+	.name			= "afs",
+	.version		= 0,
+	.ops			= &afs_cache_ops,
+};
+#endif
+
+/*****************************************************************************/
+/*
+ * initialise the AFS client FS module
+ */
+static int __init afs_init(void)
+{
+	int loop, ret;
+
+	printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 registering.\n");
+
+	/* initialise the callback hash table */
+	spin_lock_init(&afs_cb_hash_lock);
+	for (loop = AFS_CB_HASH_COUNT - 1; loop >= 0; loop--)
+		INIT_LIST_HEAD(&afs_cb_hash_tbl[loop]);
+
+	/* register the /proc stuff */
+	ret = afs_proc_init();
+	if (ret < 0)
+		return ret;
+
+#ifdef AFS_CACHING_SUPPORT
+	/* we want to be able to cache */
+	ret = cachefs_register_netfs(&afs_cache_netfs,
+				     &afs_cache_cell_index_def);
+	if (ret < 0)
+		goto error;
+#endif
+
+#ifdef CONFIG_KEYS_TURNED_OFF
+	ret = afs_key_register();
+	if (ret < 0)
+		goto error_cache;
+#endif
+
+	/* initialise the cell DB */
+	ret = afs_cell_init(rootcell);
+	if (ret < 0)
+		goto error_keys;
+
+	/* start the timeout daemon */
+	ret = afs_kafstimod_start();
+	if (ret < 0)
+		goto error_keys;
+
+	/* start the async operation daemon */
+	ret = afs_kafsasyncd_start();
+	if (ret < 0)
+		goto error_kafstimod;
+
+	/* create the RxRPC transport */
+	ret = rxrpc_create_transport(7001, &afs_transport);
+	if (ret < 0)
+		goto error_kafsasyncd;
+
+	afs_transport->peer_ops = &afs_peer_ops;
+
+	/* register the filesystems */
+	ret = afs_fs_init();
+	if (ret < 0)
+		goto error_transport;
+
+	return ret;
+
+ error_transport:
+	rxrpc_put_transport(afs_transport);
+ error_kafsasyncd:
+	afs_kafsasyncd_stop();
+ error_kafstimod:
+	afs_kafstimod_stop();
+ error_keys:
+#ifdef CONFIG_KEYS_TURNED_OFF
+	afs_key_unregister();
+ error_cache:
+#endif
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_unregister_netfs(&afs_cache_netfs);
+ error:
+#endif
+	afs_cell_purge();
+	afs_proc_cleanup();
+	printk(KERN_ERR "kAFS: failed to register: %d\n", ret);
+	return ret;
+} /* end afs_init() */
+
+/* XXX late_initcall is kludgy, but the only alternative seems to create
+ * a transport upon the first mount, which is worse. Or is it?
+ */
+late_initcall(afs_init);	/* must be called after net/ to create socket */
+/*****************************************************************************/
+/*
+ * clean up on module removal
+ */
+static void __exit afs_exit(void)
+{
+	printk(KERN_INFO "kAFS: Red Hat AFS client v0.1 unregistering.\n");
+
+	afs_fs_exit();
+	rxrpc_put_transport(afs_transport);
+	afs_kafstimod_stop();
+	afs_kafsasyncd_stop();
+	afs_cell_purge();
+#ifdef CONFIG_KEYS_TURNED_OFF
+	afs_key_unregister();
+#endif
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_unregister_netfs(&afs_cache_netfs);
+#endif
+	afs_proc_cleanup();
+
+} /* end afs_exit() */
+
+module_exit(afs_exit);
+
+/*****************************************************************************/
+/*
+ * notification that new peer record is being added
+ * - called from krxsecd
+ * - return an error to induce an abort
+ * - mustn't sleep (caller holds an rwlock)
+ */
+static int afs_adding_peer(struct rxrpc_peer *peer)
+{
+	struct afs_server *server;
+	int ret;
+
+	_debug("kAFS: Adding new peer %08x\n", ntohl(peer->addr.s_addr));
+
+	/* determine which server the peer resides in (if any) */
+	ret = afs_server_find_by_peer(peer, &server);
+	if (ret < 0)
+		return ret; /* none that we recognise, so abort */
+
+	_debug("Server %p{u=%d}\n", server, atomic_read(&server->usage));
+
+	_debug("Cell %p{u=%d}\n",
+	       server->cell, atomic_read(&server->cell->usage));
+
+	/* cross-point the structs under a global lock */
+	spin_lock(&afs_server_peer_lock);
+	peer->user = server;
+	server->peer = peer;
+	spin_unlock(&afs_server_peer_lock);
+
+	afs_put_server(server);
+
+	return 0;
+} /* end afs_adding_peer() */
+
+/*****************************************************************************/
+/*
+ * notification that a peer record is being discarded
+ * - called from krxiod or krxsecd
+ */
+static void afs_discarding_peer(struct rxrpc_peer *peer)
+{
+	struct afs_server *server;
+
+	_enter("%p",peer);
+
+	_debug("Discarding peer %08x (rtt=%lu.%lumS)\n",
+	       ntohl(peer->addr.s_addr),
+	       (long) (peer->rtt / 1000),
+	       (long) (peer->rtt % 1000));
+
+	/* uncross-point the structs under a global lock */
+	spin_lock(&afs_server_peer_lock);
+	server = peer->user;
+	if (server) {
+		peer->user = NULL;
+		server->peer = NULL;
+	}
+	spin_unlock(&afs_server_peer_lock);
+
+	_leave("");
+
+} /* end afs_discarding_peer() */
+
+/*****************************************************************************/
+/*
+ * 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 --git a/fs/afs/misc.c b/fs/afs/misc.c
new file mode 100644
index 000000000000..e4fce66d76e0
--- /dev/null
+++ b/fs/afs/misc.c
@@ -0,0 +1,39 @@
+/* misc.c: miscellaneous bits
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include "errors.h"
+#include "internal.h"
+
+/*****************************************************************************/
+/*
+ * convert an AFS abort code to a Linux error number
+ */
+int afs_abort_to_error(int abortcode)
+{
+	switch (abortcode) {
+	case VSALVAGE:		return -EIO;
+	case VNOVNODE:		return -ENOENT;
+	case VNOVOL:		return -ENXIO;
+	case VVOLEXISTS:	return -EEXIST;
+	case VNOSERVICE:	return -EIO;
+	case VOFFLINE:		return -ENOENT;
+	case VONLINE:		return -EEXIST;
+	case VDISKFULL:		return -ENOSPC;
+	case VOVERQUOTA:	return -EDQUOT;
+	case VBUSY:		return -EBUSY;
+	case VMOVED:		return -ENXIO;
+	default:		return -EIO;
+	}
+
+} /* end afs_abort_to_error() */
diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c
new file mode 100644
index 000000000000..bfc28abe1cb1
--- /dev/null
+++ b/fs/afs/mntpt.c
@@ -0,0 +1,287 @@
+/* mntpt.c: mountpoint management
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/namespace.h>
+#include "super.h"
+#include "cell.h"
+#include "volume.h"
+#include "vnode.h"
+#include "internal.h"
+
+
+static struct dentry *afs_mntpt_lookup(struct inode *dir,
+				       struct dentry *dentry,
+				       struct nameidata *nd);
+static int afs_mntpt_open(struct inode *inode, struct file *file);
+static int afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd);
+
+struct file_operations afs_mntpt_file_operations = {
+	.open		= afs_mntpt_open,
+};
+
+struct inode_operations afs_mntpt_inode_operations = {
+	.lookup		= afs_mntpt_lookup,
+	.follow_link	= afs_mntpt_follow_link,
+	.readlink	= page_readlink,
+	.getattr	= afs_inode_getattr,
+};
+
+static LIST_HEAD(afs_vfsmounts);
+
+static void afs_mntpt_expiry_timed_out(struct afs_timer *timer);
+
+struct afs_timer_ops afs_mntpt_expiry_timer_ops = {
+	.timed_out	= afs_mntpt_expiry_timed_out,
+};
+
+struct afs_timer afs_mntpt_expiry_timer;
+
+unsigned long afs_mntpt_expiry_timeout = 20;
+
+/*****************************************************************************/
+/*
+ * check a symbolic link to see whether it actually encodes a mountpoint
+ * - sets the AFS_VNODE_MOUNTPOINT flag on the vnode appropriately
+ */
+int afs_mntpt_check_symlink(struct afs_vnode *vnode)
+{
+	struct page *page;
+	filler_t *filler;
+	size_t size;
+	char *buf;
+	int ret;
+
+	_enter("{%u,%u}", vnode->fid.vnode, vnode->fid.unique);
+
+	/* read the contents of the symlink into the pagecache */
+	filler = (filler_t *) AFS_VNODE_TO_I(vnode)->i_mapping->a_ops->readpage;
+
+	page = read_cache_page(AFS_VNODE_TO_I(vnode)->i_mapping, 0,
+			       filler, NULL);
+	if (IS_ERR(page)) {
+		ret = PTR_ERR(page);
+		goto out;
+	}
+
+	ret = -EIO;
+	wait_on_page_locked(page);
+	buf = kmap(page);
+	if (!PageUptodate(page))
+		goto out_free;
+	if (PageError(page))
+		goto out_free;
+
+	/* examine the symlink's contents */
+	size = vnode->status.size;
+	_debug("symlink to %*.*s", size, (int) size, buf);
+
+	if (size > 2 &&
+	    (buf[0] == '%' || buf[0] == '#') &&
+	    buf[size - 1] == '.'
+	    ) {
+		_debug("symlink is a mountpoint");
+		spin_lock(&vnode->lock);
+		vnode->flags |= AFS_VNODE_MOUNTPOINT;
+		spin_unlock(&vnode->lock);
+	}
+
+	ret = 0;
+
+ out_free:
+	kunmap(page);
+	page_cache_release(page);
+ out:
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_mntpt_check_symlink() */
+
+/*****************************************************************************/
+/*
+ * no valid lookup procedure on this sort of dir
+ */
+static struct dentry *afs_mntpt_lookup(struct inode *dir,
+				       struct dentry *dentry,
+				       struct nameidata *nd)
+{
+	kenter("%p,%p{%p{%s},%s}",
+	       dir,
+	       dentry,
+	       dentry->d_parent,
+	       dentry->d_parent ?
+	       dentry->d_parent->d_name.name : (const unsigned char *) "",
+	       dentry->d_name.name);
+
+	return ERR_PTR(-EREMOTE);
+} /* end afs_mntpt_lookup() */
+
+/*****************************************************************************/
+/*
+ * no valid open procedure on this sort of dir
+ */
+static int afs_mntpt_open(struct inode *inode, struct file *file)
+{
+	kenter("%p,%p{%p{%s},%s}",
+	       inode, file,
+	       file->f_dentry->d_parent,
+	       file->f_dentry->d_parent ?
+	       file->f_dentry->d_parent->d_name.name :
+	       (const unsigned char *) "",
+	       file->f_dentry->d_name.name);
+
+	return -EREMOTE;
+} /* end afs_mntpt_open() */
+
+/*****************************************************************************/
+/*
+ * create a vfsmount to be automounted
+ */
+static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt)
+{
+	struct afs_super_info *super;
+	struct vfsmount *mnt;
+	struct page *page = NULL;
+	size_t size;
+	char *buf, *devname = NULL, *options = NULL;
+	filler_t *filler;
+	int ret;
+
+	kenter("{%s}", mntpt->d_name.name);
+
+	BUG_ON(!mntpt->d_inode);
+
+	ret = -EINVAL;
+	size = mntpt->d_inode->i_size;
+	if (size > PAGE_SIZE - 1)
+		goto error;
+
+	ret = -ENOMEM;
+	devname = (char *) get_zeroed_page(GFP_KERNEL);
+	if (!devname)
+		goto error;
+
+	options = (char *) get_zeroed_page(GFP_KERNEL);
+	if (!options)
+		goto error;
+
+	/* read the contents of the AFS special symlink */
+	filler = (filler_t *)mntpt->d_inode->i_mapping->a_ops->readpage;
+
+	page = read_cache_page(mntpt->d_inode->i_mapping, 0, filler, NULL);
+	if (IS_ERR(page)) {
+		ret = PTR_ERR(page);
+		goto error;
+	}
+
+	ret = -EIO;
+	wait_on_page_locked(page);
+	if (!PageUptodate(page) || PageError(page))
+		goto error;
+
+	buf = kmap(page);
+	memcpy(devname, buf, size);
+	kunmap(page);
+	page_cache_release(page);
+	page = NULL;
+
+	/* work out what options we want */
+	super = AFS_FS_S(mntpt->d_sb);
+	memcpy(options, "cell=", 5);
+	strcpy(options + 5, super->volume->cell->name);
+	if (super->volume->type == AFSVL_RWVOL)
+		strcat(options, ",rwpath");
+
+	/* try and do the mount */
+	kdebug("--- attempting mount %s -o %s ---", devname, options);
+	mnt = do_kern_mount("afs", 0, devname, options);
+	kdebug("--- mount result %p ---", mnt);
+
+	free_page((unsigned long) devname);
+	free_page((unsigned long) options);
+	kleave(" = %p", mnt);
+	return mnt;
+
+ error:
+	if (page)
+		page_cache_release(page);
+	if (devname)
+		free_page((unsigned long) devname);
+	if (options)
+		free_page((unsigned long) options);
+	kleave(" = %d", ret);
+	return ERR_PTR(ret);
+} /* end afs_mntpt_do_automount() */
+
+/*****************************************************************************/
+/*
+ * follow a link from a mountpoint directory, thus causing it to be mounted
+ */
+static int afs_mntpt_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+	struct vfsmount *newmnt;
+	struct dentry *old_dentry;
+	int err;
+
+	kenter("%p{%s},{%s:%p{%s}}",
+	       dentry,
+	       dentry->d_name.name,
+	       nd->mnt->mnt_devname,
+	       dentry,
+	       nd->dentry->d_name.name);
+
+	newmnt = afs_mntpt_do_automount(dentry);
+	if (IS_ERR(newmnt)) {
+		path_release(nd);
+		return PTR_ERR(newmnt);
+	}
+
+	old_dentry = nd->dentry;
+	nd->dentry = dentry;
+	err = do_add_mount(newmnt, nd, 0, &afs_vfsmounts);
+	nd->dentry = old_dentry;
+
+	path_release(nd);
+
+	if (!err) {
+		mntget(newmnt);
+		nd->mnt = newmnt;
+		dget(newmnt->mnt_root);
+		nd->dentry = newmnt->mnt_root;
+	}
+
+	kleave(" = %d", err);
+	return err;
+} /* end afs_mntpt_follow_link() */
+
+/*****************************************************************************/
+/*
+ * handle mountpoint expiry timer going off
+ */
+static void afs_mntpt_expiry_timed_out(struct afs_timer *timer)
+{
+	kenter("");
+
+	mark_mounts_for_expiry(&afs_vfsmounts);
+
+	afs_kafstimod_add_timer(&afs_mntpt_expiry_timer,
+				afs_mntpt_expiry_timeout * HZ);
+
+	kleave("");
+} /* end afs_mntpt_expiry_timed_out() */
diff --git a/fs/afs/mount.h b/fs/afs/mount.h
new file mode 100644
index 000000000000..9d2f46ec549f
--- /dev/null
+++ b/fs/afs/mount.h
@@ -0,0 +1,23 @@
+/* mount.h: mount parameters
+ *
+ * Copyright (C) 2002 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_AFS_MOUNT_H
+#define _LINUX_AFS_MOUNT_H
+
+struct afs_mountdata {
+	const char		*volume;	/* name of volume */
+	const char		*cell;		/* name of cell containing volume */
+	const char		*cache;		/* name of cache block device */
+	size_t			nservers;	/* number of server addresses listed */
+	uint32_t		servers[10];	/* IP addresses of servers in this cell */
+};
+
+#endif /* _LINUX_AFS_MOUNT_H */
diff --git a/fs/afs/proc.c b/fs/afs/proc.c
new file mode 100644
index 000000000000..9c81b8f7eef0
--- /dev/null
+++ b/fs/afs/proc.c
@@ -0,0 +1,857 @@
+/* proc.c: /proc interface for AFS
+ *
+ * Copyright (C) 2002 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/sched.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include "cell.h"
+#include "volume.h"
+#include <asm/uaccess.h>
+#include "internal.h"
+
+static struct proc_dir_entry *proc_afs;
+
+
+static int afs_proc_cells_open(struct inode *inode, struct file *file);
+static void *afs_proc_cells_start(struct seq_file *p, loff_t *pos);
+static void *afs_proc_cells_next(struct seq_file *p, void *v, loff_t *pos);
+static void afs_proc_cells_stop(struct seq_file *p, void *v);
+static int afs_proc_cells_show(struct seq_file *m, void *v);
+static ssize_t afs_proc_cells_write(struct file *file, const char __user *buf,
+				    size_t size, loff_t *_pos);
+
+static struct seq_operations afs_proc_cells_ops = {
+	.start	= afs_proc_cells_start,
+	.next	= afs_proc_cells_next,
+	.stop	= afs_proc_cells_stop,
+	.show	= afs_proc_cells_show,
+};
+
+static struct file_operations afs_proc_cells_fops = {
+	.open		= afs_proc_cells_open,
+	.read		= seq_read,
+	.write		= afs_proc_cells_write,
+	.llseek		= seq_lseek,
+	.release	= seq_release,
+};
+
+static int afs_proc_rootcell_open(struct inode *inode, struct file *file);
+static int afs_proc_rootcell_release(struct inode *inode, struct file *file);
+static ssize_t afs_proc_rootcell_read(struct file *file, char __user *buf,
+				      size_t size, loff_t *_pos);
+static ssize_t afs_proc_rootcell_write(struct file *file,
+				       const char __user *buf,
+				       size_t size, loff_t *_pos);
+
+static struct file_operations afs_proc_rootcell_fops = {
+	.open		= afs_proc_rootcell_open,
+	.read		= afs_proc_rootcell_read,
+	.write		= afs_proc_rootcell_write,
+	.llseek		= no_llseek,
+	.release	= afs_proc_rootcell_release
+};
+
+static int afs_proc_cell_volumes_open(struct inode *inode, struct file *file);
+static int afs_proc_cell_volumes_release(struct inode *inode,
+					 struct file *file);
+static void *afs_proc_cell_volumes_start(struct seq_file *p, loff_t *pos);
+static void *afs_proc_cell_volumes_next(struct seq_file *p, void *v,
+					loff_t *pos);
+static void afs_proc_cell_volumes_stop(struct seq_file *p, void *v);
+static int afs_proc_cell_volumes_show(struct seq_file *m, void *v);
+
+static struct seq_operations afs_proc_cell_volumes_ops = {
+	.start	= afs_proc_cell_volumes_start,
+	.next	= afs_proc_cell_volumes_next,
+	.stop	= afs_proc_cell_volumes_stop,
+	.show	= afs_proc_cell_volumes_show,
+};
+
+static struct file_operations afs_proc_cell_volumes_fops = {
+	.open		= afs_proc_cell_volumes_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= afs_proc_cell_volumes_release,
+};
+
+static int afs_proc_cell_vlservers_open(struct inode *inode,
+					struct file *file);
+static int afs_proc_cell_vlservers_release(struct inode *inode,
+					   struct file *file);
+static void *afs_proc_cell_vlservers_start(struct seq_file *p, loff_t *pos);
+static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v,
+					  loff_t *pos);
+static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v);
+static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v);
+
+static struct seq_operations afs_proc_cell_vlservers_ops = {
+	.start	= afs_proc_cell_vlservers_start,
+	.next	= afs_proc_cell_vlservers_next,
+	.stop	= afs_proc_cell_vlservers_stop,
+	.show	= afs_proc_cell_vlservers_show,
+};
+
+static struct file_operations afs_proc_cell_vlservers_fops = {
+	.open		= afs_proc_cell_vlservers_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= afs_proc_cell_vlservers_release,
+};
+
+static int afs_proc_cell_servers_open(struct inode *inode, struct file *file);
+static int afs_proc_cell_servers_release(struct inode *inode,
+					 struct file *file);
+static void *afs_proc_cell_servers_start(struct seq_file *p, loff_t *pos);
+static void *afs_proc_cell_servers_next(struct seq_file *p, void *v,
+					loff_t *pos);
+static void afs_proc_cell_servers_stop(struct seq_file *p, void *v);
+static int afs_proc_cell_servers_show(struct seq_file *m, void *v);
+
+static struct seq_operations afs_proc_cell_servers_ops = {
+	.start	= afs_proc_cell_servers_start,
+	.next	= afs_proc_cell_servers_next,
+	.stop	= afs_proc_cell_servers_stop,
+	.show	= afs_proc_cell_servers_show,
+};
+
+static struct file_operations afs_proc_cell_servers_fops = {
+	.open		= afs_proc_cell_servers_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= afs_proc_cell_servers_release,
+};
+
+/*****************************************************************************/
+/*
+ * initialise the /proc/fs/afs/ directory
+ */
+int afs_proc_init(void)
+{
+	struct proc_dir_entry *p;
+
+	_enter("");
+
+	proc_afs = proc_mkdir("fs/afs", NULL);
+	if (!proc_afs)
+		goto error;
+	proc_afs->owner = THIS_MODULE;
+
+	p = create_proc_entry("cells", 0, proc_afs);
+	if (!p)
+		goto error_proc;
+	p->proc_fops = &afs_proc_cells_fops;
+	p->owner = THIS_MODULE;
+
+	p = create_proc_entry("rootcell", 0, proc_afs);
+	if (!p)
+		goto error_cells;
+	p->proc_fops = &afs_proc_rootcell_fops;
+	p->owner = THIS_MODULE;
+
+	_leave(" = 0");
+	return 0;
+
+ error_cells:
+ 	remove_proc_entry("cells", proc_afs);
+ error_proc:
+	remove_proc_entry("fs/afs", NULL);
+ error:
+	_leave(" = -ENOMEM");
+	return -ENOMEM;
+
+} /* end afs_proc_init() */
+
+/*****************************************************************************/
+/*
+ * clean up the /proc/fs/afs/ directory
+ */
+void afs_proc_cleanup(void)
+{
+	remove_proc_entry("cells", proc_afs);
+
+	remove_proc_entry("fs/afs", NULL);
+
+} /* end afs_proc_cleanup() */
+
+/*****************************************************************************/
+/*
+ * open "/proc/fs/afs/cells" which provides a summary of extant cells
+ */
+static int afs_proc_cells_open(struct inode *inode, struct file *file)
+{
+	struct seq_file *m;
+	int ret;
+
+	ret = seq_open(file, &afs_proc_cells_ops);
+	if (ret < 0)
+		return ret;
+
+	m = file->private_data;
+	m->private = PDE(inode)->data;
+
+	return 0;
+} /* end afs_proc_cells_open() */
+
+/*****************************************************************************/
+/*
+ * set up the iterator to start reading from the cells list and return the
+ * first item
+ */
+static void *afs_proc_cells_start(struct seq_file *m, loff_t *_pos)
+{
+	struct list_head *_p;
+	loff_t pos = *_pos;
+
+	/* lock the list against modification */
+	down_read(&afs_proc_cells_sem);
+
+	/* allow for the header line */
+	if (!pos)
+		return (void *) 1;
+	pos--;
+
+	/* find the n'th element in the list */
+	list_for_each(_p, &afs_proc_cells)
+		if (!pos--)
+			break;
+
+	return _p != &afs_proc_cells ? _p : NULL;
+} /* end afs_proc_cells_start() */
+
+/*****************************************************************************/
+/*
+ * move to next cell in cells list
+ */
+static void *afs_proc_cells_next(struct seq_file *p, void *v, loff_t *pos)
+{
+	struct list_head *_p;
+
+	(*pos)++;
+
+	_p = v;
+	_p = v == (void *) 1 ? afs_proc_cells.next : _p->next;
+
+	return _p != &afs_proc_cells ? _p : NULL;
+} /* end afs_proc_cells_next() */
+
+/*****************************************************************************/
+/*
+ * clean up after reading from the cells list
+ */
+static void afs_proc_cells_stop(struct seq_file *p, void *v)
+{
+	up_read(&afs_proc_cells_sem);
+
+} /* end afs_proc_cells_stop() */
+
+/*****************************************************************************/
+/*
+ * display a header line followed by a load of cell lines
+ */
+static int afs_proc_cells_show(struct seq_file *m, void *v)
+{
+	struct afs_cell *cell = list_entry(v, struct afs_cell, proc_link);
+
+	/* display header on line 1 */
+	if (v == (void *) 1) {
+		seq_puts(m, "USE NAME\n");
+		return 0;
+	}
+
+	/* display one cell per line on subsequent lines */
+	seq_printf(m, "%3d %s\n", atomic_read(&cell->usage), cell->name);
+
+	return 0;
+} /* end afs_proc_cells_show() */
+
+/*****************************************************************************/
+/*
+ * handle writes to /proc/fs/afs/cells
+ * - to add cells: echo "add <cellname> <IP>[:<IP>][:<IP>]"
+ */
+static ssize_t afs_proc_cells_write(struct file *file, const char __user *buf,
+				    size_t size, loff_t *_pos)
+{
+	char *kbuf, *name, *args;
+	int ret;
+
+	/* start by dragging the command into memory */
+	if (size <= 1 || size >= PAGE_SIZE)
+		return -EINVAL;
+
+	kbuf = kmalloc(size + 1, GFP_KERNEL);
+	if (!kbuf)
+		return -ENOMEM;
+
+	ret = -EFAULT;
+	if (copy_from_user(kbuf, buf, size) != 0)
+		goto done;
+	kbuf[size] = 0;
+
+	/* trim to first NL */
+	name = memchr(kbuf, '\n', size);
+	if (name)
+		*name = 0;
+
+	/* split into command, name and argslist */
+	name = strchr(kbuf, ' ');
+	if (!name)
+		goto inval;
+	do {
+		*name++ = 0;
+	} while(*name == ' ');
+	if (!*name)
+		goto inval;
+
+	args = strchr(name, ' ');
+	if (!args)
+		goto inval;
+	do {
+		*args++ = 0;
+	} while(*args == ' ');
+	if (!*args)
+		goto inval;
+
+	/* determine command to perform */
+	_debug("cmd=%s name=%s args=%s", kbuf, name, args);
+
+	if (strcmp(kbuf, "add") == 0) {
+		struct afs_cell *cell;
+		ret = afs_cell_create(name, args, &cell);
+		if (ret < 0)
+			goto done;
+
+		printk("kAFS: Added new cell '%s'\n", name);
+	}
+	else {
+		goto inval;
+	}
+
+	ret = size;
+
+ done:
+	kfree(kbuf);
+	_leave(" = %d", ret);
+	return ret;
+
+ inval:
+	ret = -EINVAL;
+	printk("kAFS: Invalid Command on /proc/fs/afs/cells file\n");
+	goto done;
+} /* end afs_proc_cells_write() */
+
+/*****************************************************************************/
+/*
+ * Stubs for /proc/fs/afs/rootcell
+ */
+static int afs_proc_rootcell_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int afs_proc_rootcell_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static ssize_t afs_proc_rootcell_read(struct file *file, char __user *buf,
+				      size_t size, loff_t *_pos)
+{
+	return 0;
+}
+
+/*****************************************************************************/
+/*
+ * handle writes to /proc/fs/afs/rootcell
+ * - to initialize rootcell: echo "cell.name:192.168.231.14"
+ */
+static ssize_t afs_proc_rootcell_write(struct file *file,
+				       const char __user *buf,
+				       size_t size, loff_t *_pos)
+{
+	char *kbuf, *s;
+	int ret;
+
+	/* start by dragging the command into memory */
+	if (size <= 1 || size >= PAGE_SIZE)
+		return -EINVAL;
+
+	ret = -ENOMEM;
+	kbuf = kmalloc(size + 1, GFP_KERNEL);
+	if (!kbuf)
+		goto nomem;
+
+	ret = -EFAULT;
+	if (copy_from_user(kbuf, buf, size) != 0)
+		goto infault;
+	kbuf[size] = 0;
+
+	/* trim to first NL */
+	s = memchr(kbuf, '\n', size);
+	if (s)
+		*s = 0;
+
+	/* determine command to perform */
+	_debug("rootcell=%s", kbuf);
+
+	ret = afs_cell_init(kbuf);
+	if (ret >= 0)
+		ret = size;	/* consume everything, always */
+
+ infault:
+	kfree(kbuf);
+ nomem:
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_proc_rootcell_write() */
+
+/*****************************************************************************/
+/*
+ * initialise /proc/fs/afs/<cell>/
+ */
+int afs_proc_cell_setup(struct afs_cell *cell)
+{
+	struct proc_dir_entry *p;
+
+	_enter("%p{%s}", cell, cell->name);
+
+	cell->proc_dir = proc_mkdir(cell->name, proc_afs);
+	if (!cell->proc_dir)
+		return -ENOMEM;
+
+	p = create_proc_entry("servers", 0, cell->proc_dir);
+	if (!p)
+		goto error_proc;
+	p->proc_fops = &afs_proc_cell_servers_fops;
+	p->owner = THIS_MODULE;
+	p->data = cell;
+
+	p = create_proc_entry("vlservers", 0, cell->proc_dir);
+	if (!p)
+		goto error_servers;
+	p->proc_fops = &afs_proc_cell_vlservers_fops;
+	p->owner = THIS_MODULE;
+	p->data = cell;
+
+	p = create_proc_entry("volumes", 0, cell->proc_dir);
+	if (!p)
+		goto error_vlservers;
+	p->proc_fops = &afs_proc_cell_volumes_fops;
+	p->owner = THIS_MODULE;
+	p->data = cell;
+
+	_leave(" = 0");
+	return 0;
+
+ error_vlservers:
+	remove_proc_entry("vlservers", cell->proc_dir);
+ error_servers:
+	remove_proc_entry("servers", cell->proc_dir);
+ error_proc:
+	remove_proc_entry(cell->name, proc_afs);
+	_leave(" = -ENOMEM");
+	return -ENOMEM;
+} /* end afs_proc_cell_setup() */
+
+/*****************************************************************************/
+/*
+ * remove /proc/fs/afs/<cell>/
+ */
+void afs_proc_cell_remove(struct afs_cell *cell)
+{
+	_enter("");
+
+	remove_proc_entry("volumes", cell->proc_dir);
+	remove_proc_entry("vlservers", cell->proc_dir);
+	remove_proc_entry("servers", cell->proc_dir);
+	remove_proc_entry(cell->name, proc_afs);
+
+	_leave("");
+} /* end afs_proc_cell_remove() */
+
+/*****************************************************************************/
+/*
+ * open "/proc/fs/afs/<cell>/volumes" which provides a summary of extant cells
+ */
+static int afs_proc_cell_volumes_open(struct inode *inode, struct file *file)
+{
+	struct afs_cell *cell;
+	struct seq_file *m;
+	int ret;
+
+	cell = afs_get_cell_maybe((struct afs_cell **) &PDE(inode)->data);
+	if (!cell)
+		return -ENOENT;
+
+	ret = seq_open(file, &afs_proc_cell_volumes_ops);
+	if (ret < 0)
+		return ret;
+
+	m = file->private_data;
+	m->private = cell;
+
+	return 0;
+} /* end afs_proc_cell_volumes_open() */
+
+/*****************************************************************************/
+/*
+ * close the file and release the ref to the cell
+ */
+static int afs_proc_cell_volumes_release(struct inode *inode, struct file *file)
+{
+	struct afs_cell *cell = PDE(inode)->data;
+	int ret;
+
+	ret = seq_release(inode,file);
+
+	afs_put_cell(cell);
+
+	return ret;
+} /* end afs_proc_cell_volumes_release() */
+
+/*****************************************************************************/
+/*
+ * set up the iterator to start reading from the cells list and return the
+ * first item
+ */
+static void *afs_proc_cell_volumes_start(struct seq_file *m, loff_t *_pos)
+{
+	struct list_head *_p;
+	struct afs_cell *cell = m->private;
+	loff_t pos = *_pos;
+
+	_enter("cell=%p pos=%Ld", cell, *_pos);
+
+	/* lock the list against modification */
+	down_read(&cell->vl_sem);
+
+	/* allow for the header line */
+	if (!pos)
+		return (void *) 1;
+	pos--;
+
+	/* find the n'th element in the list */
+	list_for_each(_p, &cell->vl_list)
+		if (!pos--)
+			break;
+
+	return _p != &cell->vl_list ? _p : NULL;
+} /* end afs_proc_cell_volumes_start() */
+
+/*****************************************************************************/
+/*
+ * move to next cell in cells list
+ */
+static void *afs_proc_cell_volumes_next(struct seq_file *p, void *v,
+					loff_t *_pos)
+{
+	struct list_head *_p;
+	struct afs_cell *cell = p->private;
+
+	_enter("cell=%p pos=%Ld", cell, *_pos);
+
+	(*_pos)++;
+
+	_p = v;
+	_p = v == (void *) 1 ? cell->vl_list.next : _p->next;
+
+	return _p != &cell->vl_list ? _p : NULL;
+} /* end afs_proc_cell_volumes_next() */
+
+/*****************************************************************************/
+/*
+ * clean up after reading from the cells list
+ */
+static void afs_proc_cell_volumes_stop(struct seq_file *p, void *v)
+{
+	struct afs_cell *cell = p->private;
+
+	up_read(&cell->vl_sem);
+
+} /* end afs_proc_cell_volumes_stop() */
+
+/*****************************************************************************/
+/*
+ * display a header line followed by a load of volume lines
+ */
+static int afs_proc_cell_volumes_show(struct seq_file *m, void *v)
+{
+	struct afs_vlocation *vlocation =
+		list_entry(v, struct afs_vlocation, link);
+
+	/* display header on line 1 */
+	if (v == (void *) 1) {
+		seq_puts(m, "USE VLID[0]  VLID[1]  VLID[2]  NAME\n");
+		return 0;
+	}
+
+	/* display one cell per line on subsequent lines */
+	seq_printf(m, "%3d %08x %08x %08x %s\n",
+		   atomic_read(&vlocation->usage),
+		   vlocation->vldb.vid[0],
+		   vlocation->vldb.vid[1],
+		   vlocation->vldb.vid[2],
+		   vlocation->vldb.name
+		   );
+
+	return 0;
+} /* end afs_proc_cell_volumes_show() */
+
+/*****************************************************************************/
+/*
+ * open "/proc/fs/afs/<cell>/vlservers" which provides a list of volume
+ * location server
+ */
+static int afs_proc_cell_vlservers_open(struct inode *inode, struct file *file)
+{
+	struct afs_cell *cell;
+	struct seq_file *m;
+	int ret;
+
+	cell = afs_get_cell_maybe((struct afs_cell**)&PDE(inode)->data);
+	if (!cell)
+		return -ENOENT;
+
+	ret = seq_open(file,&afs_proc_cell_vlservers_ops);
+	if (ret<0)
+		return ret;
+
+	m = file->private_data;
+	m->private = cell;
+
+	return 0;
+} /* end afs_proc_cell_vlservers_open() */
+
+/*****************************************************************************/
+/*
+ * close the file and release the ref to the cell
+ */
+static int afs_proc_cell_vlservers_release(struct inode *inode,
+					   struct file *file)
+{
+	struct afs_cell *cell = PDE(inode)->data;
+	int ret;
+
+	ret = seq_release(inode,file);
+
+	afs_put_cell(cell);
+
+	return ret;
+} /* end afs_proc_cell_vlservers_release() */
+
+/*****************************************************************************/
+/*
+ * set up the iterator to start reading from the cells list and return the
+ * first item
+ */
+static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos)
+{
+	struct afs_cell *cell = m->private;
+	loff_t pos = *_pos;
+
+	_enter("cell=%p pos=%Ld", cell, *_pos);
+
+	/* lock the list against modification */
+	down_read(&cell->vl_sem);
+
+	/* allow for the header line */
+	if (!pos)
+		return (void *) 1;
+	pos--;
+
+	if (pos >= cell->vl_naddrs)
+		return NULL;
+
+	return &cell->vl_addrs[pos];
+} /* end afs_proc_cell_vlservers_start() */
+
+/*****************************************************************************/
+/*
+ * move to next cell in cells list
+ */
+static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v,
+					  loff_t *_pos)
+{
+	struct afs_cell *cell = p->private;
+	loff_t pos;
+
+	_enter("cell=%p{nad=%u} pos=%Ld", cell, cell->vl_naddrs, *_pos);
+
+	pos = *_pos;
+	(*_pos)++;
+	if (pos >= cell->vl_naddrs)
+		return NULL;
+
+	return &cell->vl_addrs[pos];
+} /* end afs_proc_cell_vlservers_next() */
+
+/*****************************************************************************/
+/*
+ * clean up after reading from the cells list
+ */
+static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v)
+{
+	struct afs_cell *cell = p->private;
+
+	up_read(&cell->vl_sem);
+
+} /* end afs_proc_cell_vlservers_stop() */
+
+/*****************************************************************************/
+/*
+ * display a header line followed by a load of volume lines
+ */
+static int afs_proc_cell_vlservers_show(struct seq_file *m, void *v)
+{
+	struct in_addr *addr = v;
+
+	/* display header on line 1 */
+	if (v == (struct in_addr *) 1) {
+		seq_puts(m, "ADDRESS\n");
+		return 0;
+	}
+
+	/* display one cell per line on subsequent lines */
+	seq_printf(m, "%u.%u.%u.%u\n", NIPQUAD(addr->s_addr));
+
+	return 0;
+} /* end afs_proc_cell_vlservers_show() */
+
+/*****************************************************************************/
+/*
+ * open "/proc/fs/afs/<cell>/servers" which provides a summary of active
+ * servers
+ */
+static int afs_proc_cell_servers_open(struct inode *inode, struct file *file)
+{
+	struct afs_cell *cell;
+	struct seq_file *m;
+	int ret;
+
+	cell = afs_get_cell_maybe((struct afs_cell **) &PDE(inode)->data);
+	if (!cell)
+		return -ENOENT;
+
+	ret = seq_open(file, &afs_proc_cell_servers_ops);
+	if (ret < 0)
+		return ret;
+
+	m = file->private_data;
+	m->private = cell;
+
+	return 0;
+} /* end afs_proc_cell_servers_open() */
+
+/*****************************************************************************/
+/*
+ * close the file and release the ref to the cell
+ */
+static int afs_proc_cell_servers_release(struct inode *inode,
+					 struct file *file)
+{
+	struct afs_cell *cell = PDE(inode)->data;
+	int ret;
+
+	ret = seq_release(inode, file);
+
+	afs_put_cell(cell);
+
+	return ret;
+} /* end afs_proc_cell_servers_release() */
+
+/*****************************************************************************/
+/*
+ * set up the iterator to start reading from the cells list and return the
+ * first item
+ */
+static void *afs_proc_cell_servers_start(struct seq_file *m, loff_t *_pos)
+{
+	struct list_head *_p;
+	struct afs_cell *cell = m->private;
+	loff_t pos = *_pos;
+
+	_enter("cell=%p pos=%Ld", cell, *_pos);
+
+	/* lock the list against modification */
+	read_lock(&cell->sv_lock);
+
+	/* allow for the header line */
+	if (!pos)
+		return (void *) 1;
+	pos--;
+
+	/* find the n'th element in the list */
+	list_for_each(_p, &cell->sv_list)
+		if (!pos--)
+			break;
+
+	return _p != &cell->sv_list ? _p : NULL;
+} /* end afs_proc_cell_servers_start() */
+
+/*****************************************************************************/
+/*
+ * move to next cell in cells list
+ */
+static void *afs_proc_cell_servers_next(struct seq_file *p, void *v,
+					loff_t *_pos)
+{
+	struct list_head *_p;
+	struct afs_cell *cell = p->private;
+
+	_enter("cell=%p pos=%Ld", cell, *_pos);
+
+	(*_pos)++;
+
+	_p = v;
+	_p = v == (void *) 1 ? cell->sv_list.next : _p->next;
+
+	return _p != &cell->sv_list ? _p : NULL;
+} /* end afs_proc_cell_servers_next() */
+
+/*****************************************************************************/
+/*
+ * clean up after reading from the cells list
+ */
+static void afs_proc_cell_servers_stop(struct seq_file *p, void *v)
+{
+	struct afs_cell *cell = p->private;
+
+	read_unlock(&cell->sv_lock);
+
+} /* end afs_proc_cell_servers_stop() */
+
+/*****************************************************************************/
+/*
+ * display a header line followed by a load of volume lines
+ */
+static int afs_proc_cell_servers_show(struct seq_file *m, void *v)
+{
+	struct afs_server *server = list_entry(v, struct afs_server, link);
+	char ipaddr[20];
+
+	/* display header on line 1 */
+	if (v == (void *) 1) {
+		seq_puts(m, "USE ADDR            STATE\n");
+		return 0;
+	}
+
+	/* display one cell per line on subsequent lines */
+	sprintf(ipaddr, "%u.%u.%u.%u", NIPQUAD(server->addr));
+	seq_printf(m, "%3d %-15.15s %5d\n",
+		   atomic_read(&server->usage),
+		   ipaddr,
+		   server->fs_state
+		   );
+
+	return 0;
+} /* end afs_proc_cell_servers_show() */
diff --git a/fs/afs/server.c b/fs/afs/server.c
new file mode 100644
index 000000000000..62b093aa41c6
--- /dev/null
+++ b/fs/afs/server.c
@@ -0,0 +1,502 @@
+/* server.c: AFS server record management
+ *
+ * Copyright (C) 2002 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/sched.h>
+#include <linux/slab.h>
+#include <rxrpc/peer.h>
+#include <rxrpc/connection.h>
+#include "volume.h"
+#include "cell.h"
+#include "server.h"
+#include "transport.h"
+#include "vlclient.h"
+#include "kafstimod.h"
+#include "internal.h"
+
+DEFINE_SPINLOCK(afs_server_peer_lock);
+
+#define FS_SERVICE_ID		1	/* AFS Volume Location Service ID */
+#define VL_SERVICE_ID		52	/* AFS Volume Location Service ID */
+
+static void __afs_server_timeout(struct afs_timer *timer)
+{
+	struct afs_server *server =
+		list_entry(timer, struct afs_server, timeout);
+
+	_debug("SERVER TIMEOUT [%p{u=%d}]",
+	       server, atomic_read(&server->usage));
+
+	afs_server_do_timeout(server);
+}
+
+static const struct afs_timer_ops afs_server_timer_ops = {
+	.timed_out	= __afs_server_timeout,
+};
+
+/*****************************************************************************/
+/*
+ * lookup a server record in a cell
+ * - TODO: search the cell's server list
+ */
+int afs_server_lookup(struct afs_cell *cell, const struct in_addr *addr,
+		      struct afs_server **_server)
+{
+	struct afs_server *server, *active, *zombie;
+	int loop;
+
+	_enter("%p,%08x,", cell, ntohl(addr->s_addr));
+
+	/* allocate and initialise a server record */
+	server = kmalloc(sizeof(struct afs_server), GFP_KERNEL);
+	if (!server) {
+		_leave(" = -ENOMEM");
+		return -ENOMEM;
+	}
+
+	memset(server, 0, sizeof(struct afs_server));
+	atomic_set(&server->usage, 1);
+
+	INIT_LIST_HEAD(&server->link);
+	init_rwsem(&server->sem);
+	INIT_LIST_HEAD(&server->fs_callq);
+	spin_lock_init(&server->fs_lock);
+	INIT_LIST_HEAD(&server->cb_promises);
+	spin_lock_init(&server->cb_lock);
+
+	for (loop = 0; loop < AFS_SERVER_CONN_LIST_SIZE; loop++)
+		server->fs_conn_cnt[loop] = 4;
+
+	memcpy(&server->addr, addr, sizeof(struct in_addr));
+	server->addr.s_addr = addr->s_addr;
+
+	afs_timer_init(&server->timeout, &afs_server_timer_ops);
+
+	/* add to the cell */
+	write_lock(&cell->sv_lock);
+
+	/* check the active list */
+	list_for_each_entry(active, &cell->sv_list, link) {
+		if (active->addr.s_addr == addr->s_addr)
+			goto use_active_server;
+	}
+
+	/* check the inactive list */
+	spin_lock(&cell->sv_gylock);
+	list_for_each_entry(zombie, &cell->sv_graveyard, link) {
+		if (zombie->addr.s_addr == addr->s_addr)
+			goto resurrect_server;
+	}
+	spin_unlock(&cell->sv_gylock);
+
+	afs_get_cell(cell);
+	server->cell = cell;
+	list_add_tail(&server->link, &cell->sv_list);
+
+	write_unlock(&cell->sv_lock);
+
+	*_server = server;
+	_leave(" = 0 (%p)", server);
+	return 0;
+
+	/* found a matching active server */
+ use_active_server:
+	_debug("active server");
+	afs_get_server(active);
+	write_unlock(&cell->sv_lock);
+
+	kfree(server);
+
+	*_server = active;
+	_leave(" = 0 (%p)", active);
+	return 0;
+
+	/* found a matching server in the graveyard, so resurrect it and
+	 * dispose of the new record */
+ resurrect_server:
+	_debug("resurrecting server");
+
+	list_del(&zombie->link);
+	list_add_tail(&zombie->link, &cell->sv_list);
+	afs_get_server(zombie);
+	afs_kafstimod_del_timer(&zombie->timeout);
+	spin_unlock(&cell->sv_gylock);
+	write_unlock(&cell->sv_lock);
+
+	kfree(server);
+
+	*_server = zombie;
+	_leave(" = 0 (%p)", zombie);
+	return 0;
+
+} /* end afs_server_lookup() */
+
+/*****************************************************************************/
+/*
+ * destroy a server record
+ * - removes from the cell list
+ */
+void afs_put_server(struct afs_server *server)
+{
+	struct afs_cell *cell;
+
+	if (!server)
+		return;
+
+	_enter("%p", server);
+
+	cell = server->cell;
+
+	/* sanity check */
+	BUG_ON(atomic_read(&server->usage) <= 0);
+
+	/* to prevent a race, the decrement and the dequeue must be effectively
+	 * atomic */
+	write_lock(&cell->sv_lock);
+
+	if (likely(!atomic_dec_and_test(&server->usage))) {
+		write_unlock(&cell->sv_lock);
+		_leave("");
+		return;
+	}
+
+	spin_lock(&cell->sv_gylock);
+	list_del(&server->link);
+	list_add_tail(&server->link, &cell->sv_graveyard);
+
+	/* time out in 10 secs */
+	afs_kafstimod_add_timer(&server->timeout, 10 * HZ);
+
+	spin_unlock(&cell->sv_gylock);
+	write_unlock(&cell->sv_lock);
+
+	_leave(" [killed]");
+} /* end afs_put_server() */
+
+/*****************************************************************************/
+/*
+ * timeout server record
+ * - removes from the cell's graveyard if the usage count is zero
+ */
+void afs_server_do_timeout(struct afs_server *server)
+{
+	struct rxrpc_peer *peer;
+	struct afs_cell *cell;
+	int loop;
+
+	_enter("%p", server);
+
+	cell = server->cell;
+
+	BUG_ON(atomic_read(&server->usage) < 0);
+
+	/* remove from graveyard if still dead */
+	spin_lock(&cell->vl_gylock);
+	if (atomic_read(&server->usage) == 0)
+		list_del_init(&server->link);
+	else
+		server = NULL;
+	spin_unlock(&cell->vl_gylock);
+
+	if (!server) {
+		_leave("");
+		return; /* resurrected */
+	}
+
+	/* we can now destroy it properly */
+	afs_put_cell(cell);
+
+	/* uncross-point the structs under a global lock */
+	spin_lock(&afs_server_peer_lock);
+	peer = server->peer;
+	if (peer) {
+		server->peer = NULL;
+		peer->user = NULL;
+	}
+	spin_unlock(&afs_server_peer_lock);
+
+	/* finish cleaning up the server */
+	for (loop = AFS_SERVER_CONN_LIST_SIZE - 1; loop >= 0; loop--)
+		if (server->fs_conn[loop])
+			rxrpc_put_connection(server->fs_conn[loop]);
+
+	if (server->vlserver)
+		rxrpc_put_connection(server->vlserver);
+
+	kfree(server);
+
+	_leave(" [destroyed]");
+} /* end afs_server_do_timeout() */
+
+/*****************************************************************************/
+/*
+ * get a callslot on a connection to the fileserver on the specified server
+ */
+int afs_server_request_callslot(struct afs_server *server,
+				struct afs_server_callslot *callslot)
+{
+	struct afs_server_callslot *pcallslot;
+	struct rxrpc_connection *conn;
+	int nconn, ret;
+
+	_enter("%p,",server);
+
+	INIT_LIST_HEAD(&callslot->link);
+	callslot->task = current;
+	callslot->conn = NULL;
+	callslot->nconn = -1;
+	callslot->ready = 0;
+
+	ret = 0;
+	conn = NULL;
+
+	/* get hold of a callslot first */
+	spin_lock(&server->fs_lock);
+
+	/* resurrect the server if it's death timeout has expired */
+	if (server->fs_state) {
+		if (time_before(jiffies, server->fs_dead_jif)) {
+			ret = server->fs_state;
+			spin_unlock(&server->fs_lock);
+			_leave(" = %d [still dead]", ret);
+			return ret;
+		}
+
+		server->fs_state = 0;
+	}
+
+	/* try and find a connection that has spare callslots */
+	for (nconn = 0; nconn < AFS_SERVER_CONN_LIST_SIZE; nconn++) {
+		if (server->fs_conn_cnt[nconn] > 0) {
+			server->fs_conn_cnt[nconn]--;
+			spin_unlock(&server->fs_lock);
+			callslot->nconn = nconn;
+			goto obtained_slot;
+		}
+	}
+
+	/* none were available - wait interruptibly for one to become
+	 * available */
+	set_current_state(TASK_INTERRUPTIBLE);
+	list_add_tail(&callslot->link, &server->fs_callq);
+	spin_unlock(&server->fs_lock);
+
+	while (!callslot->ready && !signal_pending(current)) {
+		schedule();
+		set_current_state(TASK_INTERRUPTIBLE);
+	}
+
+	set_current_state(TASK_RUNNING);
+
+	/* even if we were interrupted we may still be queued */
+	if (!callslot->ready) {
+		spin_lock(&server->fs_lock);
+		list_del_init(&callslot->link);
+		spin_unlock(&server->fs_lock);
+	}
+
+	nconn = callslot->nconn;
+
+	/* if interrupted, we must release any slot we also got before
+	 * returning an error */
+	if (signal_pending(current)) {
+		ret = -EINTR;
+		goto error_release;
+	}
+
+	/* if we were woken up with an error, then pass that error back to the
+	 * called */
+	if (nconn < 0) {
+		_leave(" = %d", callslot->errno);
+		return callslot->errno;
+	}
+
+	/* were we given a connection directly? */
+	if (callslot->conn) {
+		/* yes - use it */
+		_leave(" = 0 (nc=%d)", nconn);
+		return 0;
+	}
+
+	/* got a callslot, but no connection */
+ obtained_slot:
+
+	/* need to get hold of the RxRPC connection */
+	down_write(&server->sem);
+
+	/* quick check to see if there's an outstanding error */
+	ret = server->fs_state;
+	if (ret)
+		goto error_release_upw;
+
+	if (server->fs_conn[nconn]) {
+		/* reuse an existing connection */
+		rxrpc_get_connection(server->fs_conn[nconn]);
+		callslot->conn = server->fs_conn[nconn];
+	}
+	else {
+		/* create a new connection */
+		ret = rxrpc_create_connection(afs_transport,
+					      htons(7000),
+					      server->addr.s_addr,
+					      FS_SERVICE_ID,
+					      NULL,
+					      &server->fs_conn[nconn]);
+
+		if (ret < 0)
+			goto error_release_upw;
+
+		callslot->conn = server->fs_conn[0];
+		rxrpc_get_connection(callslot->conn);
+	}
+
+	up_write(&server->sem);
+
+ 	_leave(" = 0");
+	return 0;
+
+	/* handle an error occurring */
+ error_release_upw:
+	up_write(&server->sem);
+
+ error_release:
+	/* either release the callslot or pass it along to another deserving
+	 * task */
+	spin_lock(&server->fs_lock);
+
+	if (nconn < 0) {
+		/* no callslot allocated */
+	}
+	else if (list_empty(&server->fs_callq)) {
+		/* no one waiting */
+		server->fs_conn_cnt[nconn]++;
+		spin_unlock(&server->fs_lock);
+	}
+	else {
+		/* someone's waiting - dequeue them and wake them up */
+		pcallslot = list_entry(server->fs_callq.next,
+				       struct afs_server_callslot, link);
+		list_del_init(&pcallslot->link);
+
+		pcallslot->errno = server->fs_state;
+		if (!pcallslot->errno) {
+			/* pass them out callslot details */
+			callslot->conn = xchg(&pcallslot->conn,
+					      callslot->conn);
+			pcallslot->nconn = nconn;
+			callslot->nconn = nconn = -1;
+		}
+		pcallslot->ready = 1;
+		wake_up_process(pcallslot->task);
+		spin_unlock(&server->fs_lock);
+	}
+
+	rxrpc_put_connection(callslot->conn);
+	callslot->conn = NULL;
+
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_server_request_callslot() */
+
+/*****************************************************************************/
+/*
+ * release a callslot back to the server
+ * - transfers the RxRPC connection to the next pending callslot if possible
+ */
+void afs_server_release_callslot(struct afs_server *server,
+				 struct afs_server_callslot *callslot)
+{
+	struct afs_server_callslot *pcallslot;
+
+	_enter("{ad=%08x,cnt=%u},{%d}",
+	       ntohl(server->addr.s_addr),
+	       server->fs_conn_cnt[callslot->nconn],
+	       callslot->nconn);
+
+	BUG_ON(callslot->nconn < 0);
+
+	spin_lock(&server->fs_lock);
+
+	if (list_empty(&server->fs_callq)) {
+		/* no one waiting */
+		server->fs_conn_cnt[callslot->nconn]++;
+		spin_unlock(&server->fs_lock);
+	}
+	else {
+		/* someone's waiting - dequeue them and wake them up */
+		pcallslot = list_entry(server->fs_callq.next,
+				       struct afs_server_callslot, link);
+		list_del_init(&pcallslot->link);
+
+		pcallslot->errno = server->fs_state;
+		if (!pcallslot->errno) {
+			/* pass them out callslot details */
+			callslot->conn = xchg(&pcallslot->conn, callslot->conn);
+			pcallslot->nconn = callslot->nconn;
+			callslot->nconn = -1;
+		}
+
+		pcallslot->ready = 1;
+		wake_up_process(pcallslot->task);
+		spin_unlock(&server->fs_lock);
+	}
+
+	rxrpc_put_connection(callslot->conn);
+
+	_leave("");
+} /* end afs_server_release_callslot() */
+
+/*****************************************************************************/
+/*
+ * get a handle to a connection to the vlserver (volume location) on the
+ * specified server
+ */
+int afs_server_get_vlconn(struct afs_server *server,
+			  struct rxrpc_connection **_conn)
+{
+	struct rxrpc_connection *conn;
+	int ret;
+
+	_enter("%p,", server);
+
+	ret = 0;
+	conn = NULL;
+	down_read(&server->sem);
+
+	if (server->vlserver) {
+		/* reuse an existing connection */
+		rxrpc_get_connection(server->vlserver);
+		conn = server->vlserver;
+		up_read(&server->sem);
+	}
+	else {
+		/* create a new connection */
+		up_read(&server->sem);
+		down_write(&server->sem);
+		if (!server->vlserver) {
+			ret = rxrpc_create_connection(afs_transport,
+						      htons(7003),
+						      server->addr.s_addr,
+						      VL_SERVICE_ID,
+						      NULL,
+						      &server->vlserver);
+		}
+		if (ret == 0) {
+			rxrpc_get_connection(server->vlserver);
+			conn = server->vlserver;
+		}
+		up_write(&server->sem);
+	}
+
+	*_conn = conn;
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_server_get_vlconn() */
diff --git a/fs/afs/server.h b/fs/afs/server.h
new file mode 100644
index 000000000000..c3d24115578f
--- /dev/null
+++ b/fs/afs/server.h
@@ -0,0 +1,102 @@
+/* server.h: AFS server record
+ *
+ * Copyright (C) 2002 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_AFS_SERVER_H
+#define _LINUX_AFS_SERVER_H
+
+#include "types.h"
+#include "kafstimod.h"
+#include <rxrpc/peer.h>
+#include <linux/rwsem.h>
+
+extern spinlock_t afs_server_peer_lock;
+
+/*****************************************************************************/
+/*
+ * AFS server record
+ */
+struct afs_server
+{
+	atomic_t		usage;
+	struct afs_cell		*cell;		/* cell in which server resides */
+	struct list_head	link;		/* link in cell's server list */
+	struct rw_semaphore	sem;		/* access lock */
+	struct afs_timer	timeout;	/* graveyard timeout */
+	struct in_addr		addr;		/* server address */
+	struct rxrpc_peer	*peer;		/* peer record for this server */
+	struct rxrpc_connection	*vlserver;	/* connection to the volume location service */
+
+	/* file service access */
+#define AFS_SERVER_CONN_LIST_SIZE 2
+	struct rxrpc_connection	*fs_conn[AFS_SERVER_CONN_LIST_SIZE]; /* FS connections */
+	unsigned		fs_conn_cnt[AFS_SERVER_CONN_LIST_SIZE];	/* per conn call count */
+	struct list_head	fs_callq;	/* queue of processes waiting to make a call */
+	spinlock_t		fs_lock;	/* access lock */
+	int			fs_state;      	/* 0 or reason FS currently marked dead (-errno) */
+	unsigned		fs_rtt;		/* FS round trip time */
+	unsigned long		fs_act_jif;	/* time at which last activity occurred */
+	unsigned long		fs_dead_jif;	/* time at which no longer to be considered dead */
+
+	/* callback promise management */
+	struct list_head	cb_promises;	/* as yet unbroken promises from this server */
+	spinlock_t		cb_lock;	/* access lock */
+};
+
+extern int afs_server_lookup(struct afs_cell *cell,
+			     const struct in_addr *addr,
+			     struct afs_server **_server);
+
+#define afs_get_server(S) do { atomic_inc(&(S)->usage); } while(0)
+
+extern void afs_put_server(struct afs_server *server);
+extern void afs_server_do_timeout(struct afs_server *server);
+
+extern int afs_server_find_by_peer(const struct rxrpc_peer *peer,
+				   struct afs_server **_server);
+
+extern int afs_server_get_vlconn(struct afs_server *server,
+				 struct rxrpc_connection **_conn);
+
+static inline
+struct afs_server *afs_server_get_from_peer(struct rxrpc_peer *peer)
+{
+	struct afs_server *server;
+
+	spin_lock(&afs_server_peer_lock);
+	server = peer->user;
+	if (server)
+		afs_get_server(server);
+	spin_unlock(&afs_server_peer_lock);
+
+	return server;
+}
+
+/*****************************************************************************/
+/*
+ * AFS server callslot grant record
+ */
+struct afs_server_callslot
+{
+	struct list_head	link;		/* link in server's list */
+	struct task_struct	*task;		/* process waiting to make call */
+	struct rxrpc_connection	*conn;		/* connection to use (or NULL on error) */
+	short			nconn;		/* connection slot number (-1 on error) */
+	char			ready;		/* T when ready */
+	int			errno;		/* error number if nconn==-1 */
+};
+
+extern int afs_server_request_callslot(struct afs_server *server,
+				       struct afs_server_callslot *callslot);
+
+extern void afs_server_release_callslot(struct afs_server *server,
+					struct afs_server_callslot *callslot);
+
+#endif /* _LINUX_AFS_SERVER_H */
diff --git a/fs/afs/super.c b/fs/afs/super.c
new file mode 100644
index 000000000000..d6fa8e5999df
--- /dev/null
+++ b/fs/afs/super.c
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 2002 Red Hat, Inc. All rights reserved.
+ *
+ * This software may be freely redistributed under the terms of the
+ * GNU General Public License.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: David Howells <dhowells@redhat.com>
+ *          David Woodhouse <dwmw2@cambridge.redhat.com>
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include "vnode.h"
+#include "volume.h"
+#include "cell.h"
+#include "cmservice.h"
+#include "fsclient.h"
+#include "super.h"
+#include "internal.h"
+
+#define AFS_FS_MAGIC 0x6B414653 /* 'kAFS' */
+
+struct afs_mount_params {
+	int			rwpath;
+	struct afs_cell		*default_cell;
+	struct afs_volume	*volume;
+};
+
+static void afs_i_init_once(void *foo, kmem_cache_t *cachep,
+			    unsigned long flags);
+
+static struct super_block *afs_get_sb(struct file_system_type *fs_type,
+				      int flags, const char *dev_name,
+				      void *data);
+
+static struct inode *afs_alloc_inode(struct super_block *sb);
+
+static void afs_put_super(struct super_block *sb);
+
+static void afs_destroy_inode(struct inode *inode);
+
+static struct file_system_type afs_fs_type = {
+	.owner		= THIS_MODULE,
+	.name		= "afs",
+	.get_sb		= afs_get_sb,
+	.kill_sb	= kill_anon_super,
+	.fs_flags	= FS_BINARY_MOUNTDATA,
+};
+
+static struct super_operations afs_super_ops = {
+	.statfs		= simple_statfs,
+	.alloc_inode	= afs_alloc_inode,
+	.drop_inode	= generic_delete_inode,
+	.destroy_inode	= afs_destroy_inode,
+	.clear_inode	= afs_clear_inode,
+	.put_super	= afs_put_super,
+};
+
+static kmem_cache_t *afs_inode_cachep;
+static atomic_t afs_count_active_inodes;
+
+/*****************************************************************************/
+/*
+ * initialise the filesystem
+ */
+int __init afs_fs_init(void)
+{
+	int ret;
+
+	_enter("");
+
+	afs_timer_init(&afs_mntpt_expiry_timer, &afs_mntpt_expiry_timer_ops);
+
+	/* create ourselves an inode cache */
+	atomic_set(&afs_count_active_inodes, 0);
+
+	ret = -ENOMEM;
+	afs_inode_cachep = kmem_cache_create("afs_inode_cache",
+					     sizeof(struct afs_vnode),
+					     0,
+					     SLAB_HWCACHE_ALIGN,
+					     afs_i_init_once,
+					     NULL);
+	if (!afs_inode_cachep) {
+		printk(KERN_NOTICE "kAFS: Failed to allocate inode cache\n");
+		return ret;
+	}
+
+	/* now export our filesystem to lesser mortals */
+	ret = register_filesystem(&afs_fs_type);
+	if (ret < 0) {
+		kmem_cache_destroy(afs_inode_cachep);
+		kleave(" = %d", ret);
+		return ret;
+	}
+
+	kleave(" = 0");
+	return 0;
+} /* end afs_fs_init() */
+
+/*****************************************************************************/
+/*
+ * clean up the filesystem
+ */
+void __exit afs_fs_exit(void)
+{
+	unregister_filesystem(&afs_fs_type);
+
+	if (atomic_read(&afs_count_active_inodes) != 0) {
+		printk("kAFS: %d active inode objects still present\n",
+		       atomic_read(&afs_count_active_inodes));
+		BUG();
+	}
+
+	kmem_cache_destroy(afs_inode_cachep);
+
+} /* end afs_fs_exit() */
+
+/*****************************************************************************/
+/*
+ * check that an argument has a value
+ */
+static int want_arg(char **_value, const char *option)
+{
+	if (!_value || !*_value || !**_value) {
+		printk(KERN_NOTICE "kAFS: %s: argument missing\n", option);
+		return 0;
+	}
+	return 1;
+} /* end want_arg() */
+
+/*****************************************************************************/
+/*
+ * check that there's no subsequent value
+ */
+static int want_no_value(char *const *_value, const char *option)
+{
+	if (*_value && **_value) {
+		printk(KERN_NOTICE "kAFS: %s: Invalid argument: %s\n",
+		       option, *_value);
+		return 0;
+	}
+	return 1;
+} /* end want_no_value() */
+
+/*****************************************************************************/
+/*
+ * parse the mount options
+ * - this function has been shamelessly adapted from the ext3 fs which
+ *   shamelessly adapted it from the msdos fs
+ */
+static int afs_super_parse_options(struct afs_mount_params *params,
+				   char *options,
+				   const char **devname)
+{
+	char *key, *value;
+	int ret;
+
+	_enter("%s", options);
+
+	options[PAGE_SIZE - 1] = 0;
+
+	ret = 0;
+	while ((key = strsep(&options, ",")) != 0)
+	{
+		value = strchr(key, '=');
+		if (value)
+			*value++ = 0;
+
+		printk("kAFS: KEY: %s, VAL:%s\n", key, value ?: "-");
+
+		if (strcmp(key, "rwpath") == 0) {
+			if (!want_no_value(&value, "rwpath"))
+				return -EINVAL;
+			params->rwpath = 1;
+			continue;
+		}
+		else if (strcmp(key, "vol") == 0) {
+			if (!want_arg(&value, "vol"))
+				return -EINVAL;
+			*devname = value;
+			continue;
+		}
+		else if (strcmp(key, "cell") == 0) {
+			if (!want_arg(&value, "cell"))
+				return -EINVAL;
+			afs_put_cell(params->default_cell);
+			ret = afs_cell_lookup(value,
+					      strlen(value),
+					      &params->default_cell);
+			if (ret < 0)
+				return -EINVAL;
+			continue;
+		}
+
+		printk("kAFS: Unknown mount option: '%s'\n",  key);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	ret = 0;
+
+ error:
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_super_parse_options() */
+
+/*****************************************************************************/
+/*
+ * check a superblock to see if it's the one we're looking for
+ */
+static int afs_test_super(struct super_block *sb, void *data)
+{
+	struct afs_mount_params *params = data;
+	struct afs_super_info *as = sb->s_fs_info;
+
+	return as->volume == params->volume;
+} /* end afs_test_super() */
+
+/*****************************************************************************/
+/*
+ * fill in the superblock
+ */
+static int afs_fill_super(struct super_block *sb, void *data, int silent)
+{
+	struct afs_mount_params *params = data;
+	struct afs_super_info *as = NULL;
+	struct afs_fid fid;
+	struct dentry *root = NULL;
+	struct inode *inode = NULL;
+	int ret;
+
+	kenter("");
+
+	/* allocate a superblock info record */
+	as = kmalloc(sizeof(struct afs_super_info), GFP_KERNEL);
+	if (!as) {
+		_leave(" = -ENOMEM");
+		return -ENOMEM;
+	}
+
+	memset(as, 0, sizeof(struct afs_super_info));
+
+	afs_get_volume(params->volume);
+	as->volume = params->volume;
+
+	/* fill in the superblock */
+	sb->s_blocksize		= PAGE_CACHE_SIZE;
+	sb->s_blocksize_bits	= PAGE_CACHE_SHIFT;
+	sb->s_magic		= AFS_FS_MAGIC;
+	sb->s_op		= &afs_super_ops;
+	sb->s_fs_info		= as;
+
+	/* allocate the root inode and dentry */
+	fid.vid		= as->volume->vid;
+	fid.vnode	= 1;
+	fid.unique	= 1;
+	ret = afs_iget(sb, &fid, &inode);
+	if (ret < 0)
+		goto error;
+
+	ret = -ENOMEM;
+	root = d_alloc_root(inode);
+	if (!root)
+		goto error;
+
+	sb->s_root = root;
+
+	kleave(" = 0");
+	return 0;
+
+ error:
+	iput(inode);
+	afs_put_volume(as->volume);
+	kfree(as);
+
+	sb->s_fs_info = NULL;
+
+	kleave(" = %d", ret);
+	return ret;
+} /* end afs_fill_super() */
+
+/*****************************************************************************/
+/*
+ * get an AFS superblock
+ * - TODO: don't use get_sb_nodev(), but rather call sget() directly
+ */
+static struct super_block *afs_get_sb(struct file_system_type *fs_type,
+				      int flags,
+				      const char *dev_name,
+				      void *options)
+{
+	struct afs_mount_params params;
+	struct super_block *sb;
+	int ret;
+
+	_enter(",,%s,%p", dev_name, options);
+
+	memset(&params, 0, sizeof(params));
+
+	/* start the cache manager */
+	ret = afscm_start();
+	if (ret < 0) {
+		_leave(" = %d", ret);
+		return ERR_PTR(ret);
+	}
+
+	/* parse the options */
+	if (options) {
+		ret = afs_super_parse_options(&params, options, &dev_name);
+		if (ret < 0)
+			goto error;
+		if (!dev_name) {
+			printk("kAFS: no volume name specified\n");
+			ret = -EINVAL;
+			goto error;
+		}
+	}
+
+	/* parse the device name */
+	ret = afs_volume_lookup(dev_name,
+				params.default_cell,
+				params.rwpath,
+				&params.volume);
+	if (ret < 0)
+		goto error;
+
+	/* allocate a deviceless superblock */
+	sb = sget(fs_type, afs_test_super, set_anon_super, &params);
+	if (IS_ERR(sb))
+		goto error;
+
+	sb->s_flags = flags;
+
+	ret = afs_fill_super(sb, &params, flags & MS_VERBOSE ? 1 : 0);
+	if (ret < 0) {
+		up_write(&sb->s_umount);
+		deactivate_super(sb);
+		goto error;
+	}
+	sb->s_flags |= MS_ACTIVE;
+
+	afs_put_volume(params.volume);
+	afs_put_cell(params.default_cell);
+	_leave(" = %p", sb);
+	return sb;
+
+ error:
+	afs_put_volume(params.volume);
+	afs_put_cell(params.default_cell);
+	afscm_stop();
+	_leave(" = %d", ret);
+	return ERR_PTR(ret);
+} /* end afs_get_sb() */
+
+/*****************************************************************************/
+/*
+ * finish the unmounting process on the superblock
+ */
+static void afs_put_super(struct super_block *sb)
+{
+	struct afs_super_info *as = sb->s_fs_info;
+
+	_enter("");
+
+	afs_put_volume(as->volume);
+	afscm_stop();
+
+	_leave("");
+} /* end afs_put_super() */
+
+/*****************************************************************************/
+/*
+ * initialise an inode cache slab element prior to any use
+ */
+static void afs_i_init_once(void *_vnode, kmem_cache_t *cachep,
+			    unsigned long flags)
+{
+	struct afs_vnode *vnode = (struct afs_vnode *) _vnode;
+
+	if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+	    SLAB_CTOR_CONSTRUCTOR) {
+		memset(vnode, 0, sizeof(*vnode));
+		inode_init_once(&vnode->vfs_inode);
+		init_waitqueue_head(&vnode->update_waitq);
+		spin_lock_init(&vnode->lock);
+		INIT_LIST_HEAD(&vnode->cb_link);
+		INIT_LIST_HEAD(&vnode->cb_hash_link);
+		afs_timer_init(&vnode->cb_timeout,
+			       &afs_vnode_cb_timed_out_ops);
+	}
+
+} /* end afs_i_init_once() */
+
+/*****************************************************************************/
+/*
+ * allocate an AFS inode struct from our slab cache
+ */
+static struct inode *afs_alloc_inode(struct super_block *sb)
+{
+	struct afs_vnode *vnode;
+
+	vnode = (struct afs_vnode *)
+		kmem_cache_alloc(afs_inode_cachep, SLAB_KERNEL);
+	if (!vnode)
+		return NULL;
+
+	atomic_inc(&afs_count_active_inodes);
+
+	memset(&vnode->fid, 0, sizeof(vnode->fid));
+	memset(&vnode->status, 0, sizeof(vnode->status));
+
+	vnode->volume		= NULL;
+	vnode->update_cnt	= 0;
+	vnode->flags		= 0;
+
+	return &vnode->vfs_inode;
+} /* end afs_alloc_inode() */
+
+/*****************************************************************************/
+/*
+ * destroy an AFS inode struct
+ */
+static void afs_destroy_inode(struct inode *inode)
+{
+	_enter("{%lu}", inode->i_ino);
+
+	kmem_cache_free(afs_inode_cachep, AFS_FS_I(inode));
+
+	atomic_dec(&afs_count_active_inodes);
+
+} /* end afs_destroy_inode() */
diff --git a/fs/afs/super.h b/fs/afs/super.h
new file mode 100644
index 000000000000..ac11362f4e95
--- /dev/null
+++ b/fs/afs/super.h
@@ -0,0 +1,43 @@
+/* super.h: AFS filesystem internal private data
+ *
+ * Copyright (c) 2002 Red Hat, Inc. All rights reserved.
+ *
+ * This software may be freely redistributed under the terms of the
+ * GNU General Public License.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Authors: David Woodhouse <dwmw2@cambridge.redhat.com>
+ *          David Howells <dhowells@redhat.com>
+ *
+ */
+
+#ifndef _LINUX_AFS_SUPER_H
+#define _LINUX_AFS_SUPER_H
+
+#include <linux/fs.h>
+#include "server.h"
+
+#ifdef __KERNEL__
+
+/*****************************************************************************/
+/*
+ * AFS superblock private data
+ * - there's one superblock per volume
+ */
+struct afs_super_info
+{
+	struct afs_volume	*volume;	/* volume record */
+	char			rwparent;	/* T if parent is R/W AFS volume */
+};
+
+static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
+{
+	return sb->s_fs_info;
+}
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_AFS_SUPER_H */
diff --git a/fs/afs/transport.h b/fs/afs/transport.h
new file mode 100644
index 000000000000..7013ae6ccc8c
--- /dev/null
+++ b/fs/afs/transport.h
@@ -0,0 +1,21 @@
+/* transport.h: AFS transport management
+ *
+ * Copyright (C) 2002 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_AFS_TRANSPORT_H
+#define _LINUX_AFS_TRANSPORT_H
+
+#include "types.h"
+#include <rxrpc/transport.h>
+
+/* the cache manager transport endpoint */
+extern struct rxrpc_transport *afs_transport;
+
+#endif /* _LINUX_AFS_TRANSPORT_H */
diff --git a/fs/afs/types.h b/fs/afs/types.h
new file mode 100644
index 000000000000..b1a2367c7587
--- /dev/null
+++ b/fs/afs/types.h
@@ -0,0 +1,125 @@
+/* types.h: AFS types
+ *
+ * Copyright (C) 2002 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_AFS_TYPES_H
+#define _LINUX_AFS_TYPES_H
+
+#ifdef __KERNEL__
+#include <rxrpc/types.h>
+#endif /* __KERNEL__ */
+
+typedef unsigned			afs_volid_t;
+typedef unsigned			afs_vnodeid_t;
+typedef unsigned long long		afs_dataversion_t;
+
+typedef enum {
+	AFSVL_RWVOL,			/* read/write volume */
+	AFSVL_ROVOL,			/* read-only volume */
+	AFSVL_BACKVOL,			/* backup volume */
+} __attribute__((packed)) afs_voltype_t;
+
+typedef enum {
+	AFS_FTYPE_INVALID	= 0,
+	AFS_FTYPE_FILE		= 1,
+	AFS_FTYPE_DIR		= 2,
+	AFS_FTYPE_SYMLINK	= 3,
+} afs_file_type_t;
+
+#ifdef __KERNEL__
+
+struct afs_cell;
+struct afs_vnode;
+
+/*****************************************************************************/
+/*
+ * AFS file identifier
+ */
+struct afs_fid
+{
+	afs_volid_t	vid;		/* volume ID */
+	afs_vnodeid_t	vnode;		/* file index within volume */
+	unsigned	unique;		/* unique ID number (file index version) */
+};
+
+/*****************************************************************************/
+/*
+ * AFS callback notification
+ */
+typedef enum {
+	AFSCM_CB_UNTYPED	= 0,	/* no type set on CB break */
+	AFSCM_CB_EXCLUSIVE	= 1,	/* CB exclusive to CM [not implemented] */
+	AFSCM_CB_SHARED		= 2,	/* CB shared by other CM's */
+	AFSCM_CB_DROPPED	= 3,	/* CB promise cancelled by file server */
+} afs_callback_type_t;
+
+struct afs_callback
+{
+	struct afs_server	*server;	/* server that made the promise */
+	struct afs_fid		fid;		/* file identifier */
+	unsigned		version;	/* callback version */
+	unsigned		expiry;		/* time at which expires */
+	afs_callback_type_t	type;		/* type of callback */
+};
+
+#define AFSCBMAX 50
+
+/*****************************************************************************/
+/*
+ * AFS volume information
+ */
+struct afs_volume_info
+{
+	afs_volid_t		vid;		/* volume ID */
+	afs_voltype_t		type;		/* type of this volume */
+	afs_volid_t		type_vids[5];	/* volume ID's for possible types for this vol */
+	
+	/* list of fileservers serving this volume */
+	size_t			nservers;	/* number of entries used in servers[] */
+	struct {
+		struct in_addr	addr;		/* fileserver address */
+	} servers[8];
+};
+
+/*****************************************************************************/
+/*
+ * AFS file status information
+ */
+struct afs_file_status
+{
+	unsigned		if_version;	/* interface version */
+#define AFS_FSTATUS_VERSION	1
+
+	afs_file_type_t		type;		/* file type */
+	unsigned		nlink;		/* link count */
+	size_t			size;		/* file size */
+	afs_dataversion_t	version;	/* current data version */
+	unsigned		author;		/* author ID */
+	unsigned		owner;		/* owner ID */
+	unsigned		caller_access;	/* access rights for authenticated caller */
+	unsigned		anon_access;	/* access rights for unauthenticated caller */
+	umode_t			mode;		/* UNIX mode */
+	struct afs_fid		parent;		/* parent file ID */
+	time_t			mtime_client;	/* last time client changed data */
+	time_t			mtime_server;	/* last time server changed data */
+};
+
+/*****************************************************************************/
+/*
+ * AFS volume synchronisation information
+ */
+struct afs_volsync
+{
+	time_t			creation;	/* volume creation time */
+};
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_AFS_TYPES_H */
diff --git a/fs/afs/vlclient.c b/fs/afs/vlclient.c
new file mode 100644
index 000000000000..7b0e3192ee39
--- /dev/null
+++ b/fs/afs/vlclient.c
@@ -0,0 +1,695 @@
+/* vlclient.c: AFS Volume Location Service client
+ *
+ * Copyright (C) 2002 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/init.h>
+#include <linux/sched.h>
+#include <rxrpc/rxrpc.h>
+#include <rxrpc/transport.h>
+#include <rxrpc/connection.h>
+#include <rxrpc/call.h>
+#include "server.h"
+#include "volume.h"
+#include "vlclient.h"
+#include "kafsasyncd.h"
+#include "kafstimod.h"
+#include "errors.h"
+#include "internal.h"
+
+#define VLGETENTRYBYID		503	/* AFS Get Cache Entry By ID operation ID */
+#define VLGETENTRYBYNAME	504	/* AFS Get Cache Entry By Name operation ID */
+#define VLPROBE			514	/* AFS Probe Volume Location Service operation ID */
+
+static void afs_rxvl_get_entry_by_id_attn(struct rxrpc_call *call);
+static void afs_rxvl_get_entry_by_id_error(struct rxrpc_call *call);
+
+/*****************************************************************************/
+/*
+ * map afs VL abort codes to/from Linux error codes
+ * - called with call->lock held
+ */
+static void afs_rxvl_aemap(struct rxrpc_call *call)
+{
+	int err;
+
+	_enter("{%u,%u,%d}",
+	       call->app_err_state, call->app_abort_code, call->app_errno);
+
+	switch (call->app_err_state) {
+	case RXRPC_ESTATE_LOCAL_ABORT:
+		call->app_abort_code = -call->app_errno;
+		return;
+
+	case RXRPC_ESTATE_PEER_ABORT:
+		switch (call->app_abort_code) {
+		case AFSVL_IDEXIST:		err = -EEXIST;		break;
+		case AFSVL_IO:			err = -EREMOTEIO;	break;
+		case AFSVL_NAMEEXIST:		err = -EEXIST;		break;
+		case AFSVL_CREATEFAIL:		err = -EREMOTEIO;	break;
+		case AFSVL_NOENT:		err = -ENOMEDIUM;	break;
+		case AFSVL_EMPTY:		err = -ENOMEDIUM;	break;
+		case AFSVL_ENTDELETED:		err = -ENOMEDIUM;	break;
+		case AFSVL_BADNAME:		err = -EINVAL;		break;
+		case AFSVL_BADINDEX:		err = -EINVAL;		break;
+		case AFSVL_BADVOLTYPE:		err = -EINVAL;		break;
+		case AFSVL_BADSERVER:		err = -EINVAL;		break;
+		case AFSVL_BADPARTITION:	err = -EINVAL;		break;
+		case AFSVL_REPSFULL:		err = -EFBIG;		break;
+		case AFSVL_NOREPSERVER:		err = -ENOENT;		break;
+		case AFSVL_DUPREPSERVER:	err = -EEXIST;		break;
+		case AFSVL_RWNOTFOUND:		err = -ENOENT;		break;
+		case AFSVL_BADREFCOUNT:		err = -EINVAL;		break;
+		case AFSVL_SIZEEXCEEDED:	err = -EINVAL;		break;
+		case AFSVL_BADENTRY:		err = -EINVAL;		break;
+		case AFSVL_BADVOLIDBUMP:	err = -EINVAL;		break;
+		case AFSVL_IDALREADYHASHED:	err = -EINVAL;		break;
+		case AFSVL_ENTRYLOCKED:		err = -EBUSY;		break;
+		case AFSVL_BADVOLOPER:		err = -EBADRQC;		break;
+		case AFSVL_BADRELLOCKTYPE:	err = -EINVAL;		break;
+		case AFSVL_RERELEASE:		err = -EREMOTEIO;	break;
+		case AFSVL_BADSERVERFLAG:	err = -EINVAL;		break;
+		case AFSVL_PERM:		err = -EACCES;		break;
+		case AFSVL_NOMEM:		err = -EREMOTEIO;	break;
+		default:
+			err = afs_abort_to_error(call->app_abort_code);
+			break;
+		}
+		call->app_errno = err;
+		return;
+
+	default:
+		return;
+	}
+} /* end afs_rxvl_aemap() */
+
+#if 0
+/*****************************************************************************/
+/*
+ * probe a volume location server to see if it is still alive -- unused
+ */
+static int afs_rxvl_probe(struct afs_server *server, int alloc_flags)
+{
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[1];
+	size_t sent;
+	int ret;
+	__be32 param[1];
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	/* get hold of the vlserver connection */
+	ret = afs_server_get_vlconn(server, &conn);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = VLPROBE;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	param[0] = htonl(VLPROBE);
+	piov[0].iov_len = sizeof(param);
+	piov[0].iov_base = param;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET,
+				    alloc_flags, 0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (call->app_call_state != RXRPC_CSTATE_CLNT_RCV_REPLY ||
+		    signal_pending(current))
+			break;
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+
+	ret = -EINTR;
+	if (signal_pending(current))
+		goto abort;
+
+	switch (call->app_call_state) {
+	case RXRPC_CSTATE_ERROR:
+		ret = call->app_errno;
+		goto out_unwait;
+
+	case RXRPC_CSTATE_CLNT_GOT_REPLY:
+		ret = 0;
+		goto out_unwait;
+
+	default:
+		BUG();
+	}
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	rxrpc_put_connection(conn);
+ out:
+	return ret;
+
+} /* end afs_rxvl_probe() */
+#endif
+
+/*****************************************************************************/
+/*
+ * look up a volume location database entry by name
+ */
+int afs_rxvl_get_entry_by_name(struct afs_server *server,
+			       const char *volname,
+			       unsigned volnamesz,
+			       struct afs_cache_vlocation *entry)
+{
+	DECLARE_WAITQUEUE(myself, current);
+
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[3];
+	unsigned tmp;
+	size_t sent;
+	int ret, loop;
+	__be32 *bp, param[2], zero;
+
+	_enter(",%*.*s,%u,", volnamesz, volnamesz, volname, volnamesz);
+
+	memset(entry, 0, sizeof(*entry));
+
+	/* get hold of the vlserver connection */
+	ret = afs_server_get_vlconn(server, &conn);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = VLGETENTRYBYNAME;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	piov[1].iov_len = volnamesz;
+	piov[1].iov_base = (char *) volname;
+
+	zero = 0;
+	piov[2].iov_len = (4 - (piov[1].iov_len & 3)) & 3;
+	piov[2].iov_base = &zero;
+
+	param[0] = htonl(VLGETENTRYBYNAME);
+	param[1] = htonl(piov[1].iov_len);
+
+	piov[0].iov_len = sizeof(param);
+	piov[0].iov_base = param;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 3, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	bp = rxrpc_call_alloc_scratch(call, 384);
+
+	ret = rxrpc_call_read_data(call, bp, 384,
+				   RXRPC_CALL_READ_BLOCK |
+				   RXRPC_CALL_READ_ALL);
+	if (ret < 0) {
+		if (ret == -ECONNABORTED) {
+			ret = call->app_errno;
+			goto out_unwait;
+		}
+		goto abort;
+	}
+
+	/* unmarshall the reply */
+	for (loop = 0; loop < 64; loop++)
+		entry->name[loop] = ntohl(*bp++);
+	bp++; /* final NUL */
+
+	bp++; /* type */
+	entry->nservers = ntohl(*bp++);
+
+	for (loop = 0; loop < 8; loop++)
+		entry->servers[loop].s_addr = *bp++;
+
+	bp += 8; /* partition IDs */
+
+	for (loop = 0; loop < 8; loop++) {
+		tmp = ntohl(*bp++);
+		if (tmp & AFS_VLSF_RWVOL)
+			entry->srvtmask[loop] |= AFS_VOL_VTM_RW;
+		if (tmp & AFS_VLSF_ROVOL)
+			entry->srvtmask[loop] |= AFS_VOL_VTM_RO;
+		if (tmp & AFS_VLSF_BACKVOL)
+			entry->srvtmask[loop] |= AFS_VOL_VTM_BAK;
+	}
+
+	entry->vid[0] = ntohl(*bp++);
+	entry->vid[1] = ntohl(*bp++);
+	entry->vid[2] = ntohl(*bp++);
+
+	bp++; /* clone ID */
+
+	tmp = ntohl(*bp++); /* flags */
+	if (tmp & AFS_VLF_RWEXISTS)
+		entry->vidmask |= AFS_VOL_VTM_RW;
+	if (tmp & AFS_VLF_ROEXISTS)
+		entry->vidmask |= AFS_VOL_VTM_RO;
+	if (tmp & AFS_VLF_BACKEXISTS)
+		entry->vidmask |= AFS_VOL_VTM_BAK;
+
+	ret = -ENOMEDIUM;
+	if (!entry->vidmask)
+		goto abort;
+
+	/* success */
+	entry->rtime = get_seconds();
+	ret = 0;
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	rxrpc_put_connection(conn);
+ out:
+	_leave(" = %d", ret);
+	return ret;
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+} /* end afs_rxvl_get_entry_by_name() */
+
+/*****************************************************************************/
+/*
+ * look up a volume location database entry by ID
+ */
+int afs_rxvl_get_entry_by_id(struct afs_server *server,
+			     afs_volid_t volid,
+			     afs_voltype_t voltype,
+			     struct afs_cache_vlocation *entry)
+{
+	DECLARE_WAITQUEUE(myself, current);
+
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[1];
+	unsigned tmp;
+	size_t sent;
+	int ret, loop;
+	__be32 *bp, param[3];
+
+	_enter(",%x,%d,", volid, voltype);
+
+	memset(entry, 0, sizeof(*entry));
+
+	/* get hold of the vlserver connection */
+	ret = afs_server_get_vlconn(server, &conn);
+	if (ret < 0)
+		goto out;
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn, NULL, NULL, afs_rxvl_aemap, &call);
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		goto out_put_conn;
+	}
+	call->app_opcode = VLGETENTRYBYID;
+
+	/* we want to get event notifications from the call */
+	add_wait_queue(&call->waitq, &myself);
+
+	/* marshall the parameters */
+	param[0] = htonl(VLGETENTRYBYID);
+	param[1] = htonl(volid);
+	param[2] = htonl(voltype);
+
+	piov[0].iov_len = sizeof(param);
+	piov[0].iov_base = param;
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0)
+		goto abort;
+
+	/* wait for the reply to completely arrive */
+	bp = rxrpc_call_alloc_scratch(call, 384);
+
+	ret = rxrpc_call_read_data(call, bp, 384,
+				   RXRPC_CALL_READ_BLOCK |
+				   RXRPC_CALL_READ_ALL);
+	if (ret < 0) {
+		if (ret == -ECONNABORTED) {
+			ret = call->app_errno;
+			goto out_unwait;
+		}
+		goto abort;
+	}
+
+	/* unmarshall the reply */
+	for (loop = 0; loop < 64; loop++)
+		entry->name[loop] = ntohl(*bp++);
+	bp++; /* final NUL */
+
+	bp++; /* type */
+	entry->nservers = ntohl(*bp++);
+
+	for (loop = 0; loop < 8; loop++)
+		entry->servers[loop].s_addr = *bp++;
+
+	bp += 8; /* partition IDs */
+
+	for (loop = 0; loop < 8; loop++) {
+		tmp = ntohl(*bp++);
+		if (tmp & AFS_VLSF_RWVOL)
+			entry->srvtmask[loop] |= AFS_VOL_VTM_RW;
+		if (tmp & AFS_VLSF_ROVOL)
+			entry->srvtmask[loop] |= AFS_VOL_VTM_RO;
+		if (tmp & AFS_VLSF_BACKVOL)
+			entry->srvtmask[loop] |= AFS_VOL_VTM_BAK;
+	}
+
+	entry->vid[0] = ntohl(*bp++);
+	entry->vid[1] = ntohl(*bp++);
+	entry->vid[2] = ntohl(*bp++);
+
+	bp++; /* clone ID */
+
+	tmp = ntohl(*bp++); /* flags */
+	if (tmp & AFS_VLF_RWEXISTS)
+		entry->vidmask |= AFS_VOL_VTM_RW;
+	if (tmp & AFS_VLF_ROEXISTS)
+		entry->vidmask |= AFS_VOL_VTM_RO;
+	if (tmp & AFS_VLF_BACKEXISTS)
+		entry->vidmask |= AFS_VOL_VTM_BAK;
+
+	ret = -ENOMEDIUM;
+	if (!entry->vidmask)
+		goto abort;
+
+#if 0 /* TODO: remove */
+	entry->nservers = 3;
+	entry->servers[0].s_addr = htonl(0xac101249);
+	entry->servers[1].s_addr = htonl(0xac101243);
+	entry->servers[2].s_addr = htonl(0xac10125b /*0xac10125b*/);
+
+	entry->srvtmask[0] = AFS_VOL_VTM_RO;
+	entry->srvtmask[1] = AFS_VOL_VTM_RO;
+	entry->srvtmask[2] = AFS_VOL_VTM_RO | AFS_VOL_VTM_RW;
+#endif
+
+	/* success */
+	entry->rtime = get_seconds();
+	ret = 0;
+
+ out_unwait:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&call->waitq, &myself);
+	rxrpc_put_call(call);
+ out_put_conn:
+	rxrpc_put_connection(conn);
+ out:
+	_leave(" = %d", ret);
+	return ret;
+
+ abort:
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	rxrpc_call_abort(call, ret);
+	schedule();
+	goto out_unwait;
+} /* end afs_rxvl_get_entry_by_id() */
+
+/*****************************************************************************/
+/*
+ * look up a volume location database entry by ID asynchronously
+ */
+int afs_rxvl_get_entry_by_id_async(struct afs_async_op *op,
+				   afs_volid_t volid,
+				   afs_voltype_t voltype)
+{
+	struct rxrpc_connection *conn;
+	struct rxrpc_call *call;
+	struct kvec piov[1];
+	size_t sent;
+	int ret;
+	__be32 param[3];
+
+	_enter(",%x,%d,", volid, voltype);
+
+	/* get hold of the vlserver connection */
+	ret = afs_server_get_vlconn(op->server, &conn);
+	if (ret < 0) {
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+	/* create a call through that connection */
+	ret = rxrpc_create_call(conn,
+				afs_rxvl_get_entry_by_id_attn,
+				afs_rxvl_get_entry_by_id_error,
+				afs_rxvl_aemap,
+				&op->call);
+	rxrpc_put_connection(conn);
+
+	if (ret < 0) {
+		printk("kAFS: Unable to create call: %d\n", ret);
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+	op->call->app_opcode = VLGETENTRYBYID;
+	op->call->app_user = op;
+
+	call = op->call;
+	rxrpc_get_call(call);
+
+	/* send event notifications from the call to kafsasyncd */
+	afs_kafsasyncd_begin_op(op);
+
+	/* marshall the parameters */
+	param[0] = htonl(VLGETENTRYBYID);
+	param[1] = htonl(volid);
+	param[2] = htonl(voltype);
+
+	piov[0].iov_len = sizeof(param);
+	piov[0].iov_base = param;
+
+	/* allocate result read buffer in scratch space */
+	call->app_scr_ptr = rxrpc_call_alloc_scratch(op->call, 384);
+
+	/* send the parameters to the server */
+	ret = rxrpc_call_write_data(call, 1, piov, RXRPC_LAST_PACKET, GFP_NOFS,
+				    0, &sent);
+	if (ret < 0) {
+		rxrpc_call_abort(call, ret); /* handle from kafsasyncd */
+		ret = 0;
+		goto out;
+	}
+
+	/* wait for the reply to completely arrive */
+	ret = rxrpc_call_read_data(call, call->app_scr_ptr, 384, 0);
+	switch (ret) {
+	case 0:
+	case -EAGAIN:
+	case -ECONNABORTED:
+		ret = 0;
+		break;	/* all handled by kafsasyncd */
+
+	default:
+		rxrpc_call_abort(call, ret); /* make kafsasyncd handle it */
+		ret = 0;
+		break;
+	}
+
+ out:
+	rxrpc_put_call(call);
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_rxvl_get_entry_by_id_async() */
+
+/*****************************************************************************/
+/*
+ * attend to the asynchronous get VLDB entry by ID
+ */
+int afs_rxvl_get_entry_by_id_async2(struct afs_async_op *op,
+				    struct afs_cache_vlocation *entry)
+{
+	__be32 *bp;
+	__u32 tmp;
+	int loop, ret;
+
+	_enter("{op=%p cst=%u}", op, op->call->app_call_state);
+
+	memset(entry, 0, sizeof(*entry));
+
+	if (op->call->app_call_state == RXRPC_CSTATE_COMPLETE) {
+		/* operation finished */
+		afs_kafsasyncd_terminate_op(op);
+
+		bp = op->call->app_scr_ptr;
+
+		/* unmarshall the reply */
+		for (loop = 0; loop < 64; loop++)
+			entry->name[loop] = ntohl(*bp++);
+		bp++; /* final NUL */
+
+		bp++; /* type */
+		entry->nservers = ntohl(*bp++);
+
+		for (loop = 0; loop < 8; loop++)
+			entry->servers[loop].s_addr = *bp++;
+
+		bp += 8; /* partition IDs */
+
+		for (loop = 0; loop < 8; loop++) {
+			tmp = ntohl(*bp++);
+			if (tmp & AFS_VLSF_RWVOL)
+				entry->srvtmask[loop] |= AFS_VOL_VTM_RW;
+			if (tmp & AFS_VLSF_ROVOL)
+				entry->srvtmask[loop] |= AFS_VOL_VTM_RO;
+			if (tmp & AFS_VLSF_BACKVOL)
+				entry->srvtmask[loop] |= AFS_VOL_VTM_BAK;
+		}
+
+		entry->vid[0] = ntohl(*bp++);
+		entry->vid[1] = ntohl(*bp++);
+		entry->vid[2] = ntohl(*bp++);
+
+		bp++; /* clone ID */
+
+		tmp = ntohl(*bp++); /* flags */
+		if (tmp & AFS_VLF_RWEXISTS)
+			entry->vidmask |= AFS_VOL_VTM_RW;
+		if (tmp & AFS_VLF_ROEXISTS)
+			entry->vidmask |= AFS_VOL_VTM_RO;
+		if (tmp & AFS_VLF_BACKEXISTS)
+			entry->vidmask |= AFS_VOL_VTM_BAK;
+
+		ret = -ENOMEDIUM;
+		if (!entry->vidmask) {
+			rxrpc_call_abort(op->call, ret);
+			goto done;
+		}
+
+#if 0 /* TODO: remove */
+		entry->nservers = 3;
+		entry->servers[0].s_addr = htonl(0xac101249);
+		entry->servers[1].s_addr = htonl(0xac101243);
+		entry->servers[2].s_addr = htonl(0xac10125b /*0xac10125b*/);
+
+		entry->srvtmask[0] = AFS_VOL_VTM_RO;
+		entry->srvtmask[1] = AFS_VOL_VTM_RO;
+		entry->srvtmask[2] = AFS_VOL_VTM_RO | AFS_VOL_VTM_RW;
+#endif
+
+		/* success */
+		entry->rtime = get_seconds();
+		ret = 0;
+		goto done;
+	}
+
+	if (op->call->app_call_state == RXRPC_CSTATE_ERROR) {
+		/* operation error */
+		ret = op->call->app_errno;
+		goto done;
+	}
+
+	_leave(" = -EAGAIN");
+	return -EAGAIN;
+
+ done:
+	rxrpc_put_call(op->call);
+	op->call = NULL;
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_rxvl_get_entry_by_id_async2() */
+
+/*****************************************************************************/
+/*
+ * handle attention events on an async get-entry-by-ID op
+ * - called from krxiod
+ */
+static void afs_rxvl_get_entry_by_id_attn(struct rxrpc_call *call)
+{
+	struct afs_async_op *op = call->app_user;
+
+	_enter("{op=%p cst=%u}", op, call->app_call_state);
+
+	switch (call->app_call_state) {
+	case RXRPC_CSTATE_COMPLETE:
+		afs_kafsasyncd_attend_op(op);
+		break;
+	case RXRPC_CSTATE_CLNT_RCV_REPLY:
+		if (call->app_async_read)
+			break;
+	case RXRPC_CSTATE_CLNT_GOT_REPLY:
+		if (call->app_read_count == 0)
+			break;
+		printk("kAFS: Reply bigger than expected"
+		       " {cst=%u asyn=%d mark=%Zu rdy=%Zu pr=%u%s}",
+		       call->app_call_state,
+		       call->app_async_read,
+		       call->app_mark,
+		       call->app_ready_qty,
+		       call->pkt_rcv_count,
+		       call->app_last_rcv ? " last" : "");
+
+		rxrpc_call_abort(call, -EBADMSG);
+		break;
+	default:
+		BUG();
+	}
+
+	_leave("");
+
+} /* end afs_rxvl_get_entry_by_id_attn() */
+
+/*****************************************************************************/
+/*
+ * handle error events on an async get-entry-by-ID op
+ * - called from krxiod
+ */
+static void afs_rxvl_get_entry_by_id_error(struct rxrpc_call *call)
+{
+	struct afs_async_op *op = call->app_user;
+
+	_enter("{op=%p cst=%u}", op, call->app_call_state);
+
+	afs_kafsasyncd_attend_op(op);
+
+	_leave("");
+
+} /* end afs_rxvl_get_entry_by_id_error() */
diff --git a/fs/afs/vlclient.h b/fs/afs/vlclient.h
new file mode 100644
index 000000000000..e3d601179c46
--- /dev/null
+++ b/fs/afs/vlclient.h
@@ -0,0 +1,93 @@
+/* vlclient.h: Volume Location Service client interface
+ *
+ * Copyright (C) 2002 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_AFS_VLCLIENT_H
+#define _LINUX_AFS_VLCLIENT_H
+
+#include "types.h"
+
+enum AFSVL_Errors {
+	AFSVL_IDEXIST 		= 363520,	/* Volume Id entry exists in vl database */
+	AFSVL_IO 		= 363521,	/* I/O related error */
+	AFSVL_NAMEEXIST 	= 363522,	/* Volume name entry exists in vl database */
+	AFSVL_CREATEFAIL 	= 363523,	/* Internal creation failure */
+	AFSVL_NOENT 		= 363524,	/* No such entry */
+	AFSVL_EMPTY 		= 363525,	/* Vl database is empty */
+	AFSVL_ENTDELETED 	= 363526,	/* Entry is deleted (soft delete) */
+	AFSVL_BADNAME 		= 363527,	/* Volume name is illegal */
+	AFSVL_BADINDEX 		= 363528,	/* Index is out of range */
+	AFSVL_BADVOLTYPE 	= 363529,	/* Bad volume type */
+	AFSVL_BADSERVER 	= 363530,	/* Illegal server number (out of range) */
+	AFSVL_BADPARTITION 	= 363531,	/* Bad partition number */
+	AFSVL_REPSFULL 		= 363532,	/* Run out of space for Replication sites */
+	AFSVL_NOREPSERVER 	= 363533,	/* No such Replication server site exists */
+	AFSVL_DUPREPSERVER 	= 363534,	/* Replication site already exists */
+	AFSVL_RWNOTFOUND 	= 363535,	/* Parent R/W entry not found */
+	AFSVL_BADREFCOUNT 	= 363536,	/* Illegal Reference Count number */
+	AFSVL_SIZEEXCEEDED 	= 363537,	/* Vl size for attributes exceeded */
+	AFSVL_BADENTRY 		= 363538,	/* Bad incoming vl entry */
+	AFSVL_BADVOLIDBUMP 	= 363539,	/* Illegal max volid increment */
+	AFSVL_IDALREADYHASHED 	= 363540,	/* RO/BACK id already hashed */
+	AFSVL_ENTRYLOCKED 	= 363541,	/* Vl entry is already locked */
+	AFSVL_BADVOLOPER 	= 363542,	/* Bad volume operation code */
+	AFSVL_BADRELLOCKTYPE 	= 363543,	/* Bad release lock type */
+	AFSVL_RERELEASE 	= 363544,	/* Status report: last release was aborted */
+	AFSVL_BADSERVERFLAG 	= 363545,	/* Invalid replication site server °ag */
+	AFSVL_PERM 		= 363546,	/* No permission access */
+	AFSVL_NOMEM 		= 363547,	/* malloc/realloc failed to alloc enough memory */
+};
+
+/* maps to "struct vldbentry" in vvl-spec.pdf */
+struct afs_vldbentry {
+	char		name[65];		/* name of volume (including NUL char) */
+	afs_voltype_t	type;			/* volume type */
+	unsigned	num_servers;		/* num servers that hold instances of this vol */
+	unsigned	clone_id;		/* cloning ID */
+
+	unsigned	flags;
+#define AFS_VLF_RWEXISTS	0x1000		/* R/W volume exists */
+#define AFS_VLF_ROEXISTS	0x2000		/* R/O volume exists */
+#define AFS_VLF_BACKEXISTS	0x4000		/* backup volume exists */
+
+	afs_volid_t	volume_ids[3];		/* volume IDs */
+
+	struct {
+		struct in_addr	addr;		/* server address */
+		unsigned	partition;	/* partition ID on this server */
+		unsigned	flags;		/* server specific flags */
+#define AFS_VLSF_NEWREPSITE	0x0001	/* unused */
+#define AFS_VLSF_ROVOL		0x0002	/* this server holds a R/O instance of the volume */
+#define AFS_VLSF_RWVOL		0x0004	/* this server holds a R/W instance of the volume */
+#define AFS_VLSF_BACKVOL	0x0008	/* this server holds a backup instance of the volume */
+	} servers[8];
+
+};
+
+/* look up a volume location database entry by name */
+extern int afs_rxvl_get_entry_by_name(struct afs_server *server,
+				      const char *volname,
+				      unsigned volnamesz,
+				      struct afs_cache_vlocation *entry);
+
+/* look up a volume location database entry by ID */
+extern int afs_rxvl_get_entry_by_id(struct afs_server *server,
+				    afs_volid_t	volid,
+				    afs_voltype_t voltype,
+				    struct afs_cache_vlocation *entry);
+
+extern int afs_rxvl_get_entry_by_id_async(struct afs_async_op *op,
+					  afs_volid_t volid,
+					  afs_voltype_t voltype);
+
+extern int afs_rxvl_get_entry_by_id_async2(struct afs_async_op *op,
+					   struct afs_cache_vlocation *entry);
+
+#endif /* _LINUX_AFS_VLCLIENT_H */
diff --git a/fs/afs/vlocation.c b/fs/afs/vlocation.c
new file mode 100644
index 000000000000..eced20618ecc
--- /dev/null
+++ b/fs/afs/vlocation.c
@@ -0,0 +1,954 @@
+/* vlocation.c: volume location management
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include "volume.h"
+#include "cell.h"
+#include "cmservice.h"
+#include "fsclient.h"
+#include "vlclient.h"
+#include "kafstimod.h"
+#include <rxrpc/connection.h>
+#include "internal.h"
+
+#define AFS_VLDB_TIMEOUT HZ*1000
+
+static void afs_vlocation_update_timer(struct afs_timer *timer);
+static void afs_vlocation_update_attend(struct afs_async_op *op);
+static void afs_vlocation_update_discard(struct afs_async_op *op);
+static void __afs_put_vlocation(struct afs_vlocation *vlocation);
+
+static void __afs_vlocation_timeout(struct afs_timer *timer)
+{
+	struct afs_vlocation *vlocation =
+		list_entry(timer, struct afs_vlocation, timeout);
+
+	_debug("VL TIMEOUT [%s{u=%d}]",
+	       vlocation->vldb.name, atomic_read(&vlocation->usage));
+
+	afs_vlocation_do_timeout(vlocation);
+}
+
+static const struct afs_timer_ops afs_vlocation_timer_ops = {
+	.timed_out	= __afs_vlocation_timeout,
+};
+
+static const struct afs_timer_ops afs_vlocation_update_timer_ops = {
+	.timed_out	= afs_vlocation_update_timer,
+};
+
+static const struct afs_async_op_ops afs_vlocation_update_op_ops = {
+	.attend		= afs_vlocation_update_attend,
+	.discard	= afs_vlocation_update_discard,
+};
+
+static LIST_HEAD(afs_vlocation_update_pendq);	/* queue of VLs awaiting update */
+static struct afs_vlocation *afs_vlocation_update;	/* VL currently being updated */
+static DEFINE_SPINLOCK(afs_vlocation_update_lock); /* lock guarding update queue */
+
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_vlocation_cache_match(void *target,
+						     const void *entry);
+static void afs_vlocation_cache_update(void *source, void *entry);
+
+struct cachefs_index_def afs_vlocation_cache_index_def = {
+	.name		= "vldb",
+	.data_size	= sizeof(struct afs_cache_vlocation),
+	.keys[0]	= { CACHEFS_INDEX_KEYS_ASCIIZ, 64 },
+	.match		= afs_vlocation_cache_match,
+	.update		= afs_vlocation_cache_update,
+};
+#endif
+
+/*****************************************************************************/
+/*
+ * iterate through the VL servers in a cell until one of them admits knowing
+ * about the volume in question
+ * - caller must have cell->vl_sem write-locked
+ */
+static int afs_vlocation_access_vl_by_name(struct afs_vlocation *vlocation,
+					   const char *name,
+					   unsigned namesz,
+					   struct afs_cache_vlocation *vldb)
+{
+	struct afs_server *server = NULL;
+	struct afs_cell *cell = vlocation->cell;
+	int count, ret;
+
+	_enter("%s,%*.*s,%u", cell->name, namesz, namesz, name, namesz);
+
+	ret = -ENOMEDIUM;
+	for (count = cell->vl_naddrs; count > 0; count--) {
+		_debug("CellServ[%hu]: %08x",
+		       cell->vl_curr_svix,
+		       cell->vl_addrs[cell->vl_curr_svix].s_addr);
+
+		/* try and create a server */
+		ret = afs_server_lookup(cell,
+					&cell->vl_addrs[cell->vl_curr_svix],
+					&server);
+		switch (ret) {
+		case 0:
+			break;
+		case -ENOMEM:
+		case -ENONET:
+			goto out;
+		default:
+			goto rotate;
+		}
+
+		/* attempt to access the VL server */
+		ret = afs_rxvl_get_entry_by_name(server, name, namesz, vldb);
+		switch (ret) {
+		case 0:
+			afs_put_server(server);
+			goto out;
+		case -ENOMEM:
+		case -ENONET:
+		case -ENETUNREACH:
+		case -EHOSTUNREACH:
+		case -ECONNREFUSED:
+			down_write(&server->sem);
+			if (server->vlserver) {
+				rxrpc_put_connection(server->vlserver);
+				server->vlserver = NULL;
+			}
+			up_write(&server->sem);
+			afs_put_server(server);
+			if (ret == -ENOMEM || ret == -ENONET)
+				goto out;
+			goto rotate;
+		case -ENOMEDIUM:
+			afs_put_server(server);
+			goto out;
+		default:
+			afs_put_server(server);
+			ret = -ENOMEDIUM;
+			goto rotate;
+		}
+
+		/* rotate the server records upon lookup failure */
+	rotate:
+		cell->vl_curr_svix++;
+		cell->vl_curr_svix %= cell->vl_naddrs;
+	}
+
+ out:
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_vlocation_access_vl_by_name() */
+
+/*****************************************************************************/
+/*
+ * iterate through the VL servers in a cell until one of them admits knowing
+ * about the volume in question
+ * - caller must have cell->vl_sem write-locked
+ */
+static int afs_vlocation_access_vl_by_id(struct afs_vlocation *vlocation,
+					 afs_volid_t volid,
+					 afs_voltype_t voltype,
+					 struct afs_cache_vlocation *vldb)
+{
+	struct afs_server *server = NULL;
+	struct afs_cell *cell = vlocation->cell;
+	int count, ret;
+
+	_enter("%s,%x,%d,", cell->name, volid, voltype);
+
+	ret = -ENOMEDIUM;
+	for (count = cell->vl_naddrs; count > 0; count--) {
+		_debug("CellServ[%hu]: %08x",
+		       cell->vl_curr_svix,
+		       cell->vl_addrs[cell->vl_curr_svix].s_addr);
+
+		/* try and create a server */
+		ret = afs_server_lookup(cell,
+					&cell->vl_addrs[cell->vl_curr_svix],
+					&server);
+		switch (ret) {
+		case 0:
+			break;
+		case -ENOMEM:
+		case -ENONET:
+			goto out;
+		default:
+			goto rotate;
+		}
+
+		/* attempt to access the VL server */
+		ret = afs_rxvl_get_entry_by_id(server, volid, voltype, vldb);
+		switch (ret) {
+		case 0:
+			afs_put_server(server);
+			goto out;
+		case -ENOMEM:
+		case -ENONET:
+		case -ENETUNREACH:
+		case -EHOSTUNREACH:
+		case -ECONNREFUSED:
+			down_write(&server->sem);
+			if (server->vlserver) {
+				rxrpc_put_connection(server->vlserver);
+				server->vlserver = NULL;
+			}
+			up_write(&server->sem);
+			afs_put_server(server);
+			if (ret == -ENOMEM || ret == -ENONET)
+				goto out;
+			goto rotate;
+		case -ENOMEDIUM:
+			afs_put_server(server);
+			goto out;
+		default:
+			afs_put_server(server);
+			ret = -ENOMEDIUM;
+			goto rotate;
+		}
+
+		/* rotate the server records upon lookup failure */
+	rotate:
+		cell->vl_curr_svix++;
+		cell->vl_curr_svix %= cell->vl_naddrs;
+	}
+
+ out:
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_vlocation_access_vl_by_id() */
+
+/*****************************************************************************/
+/*
+ * lookup volume location
+ * - caller must have cell->vol_sem write-locked
+ * - iterate through the VL servers in a cell until one of them admits knowing
+ *   about the volume in question
+ * - lookup in the local cache if not able to find on the VL server
+ * - insert/update in the local cache if did get a VL response
+ */
+int afs_vlocation_lookup(struct afs_cell *cell,
+			 const char *name,
+			 unsigned namesz,
+			 struct afs_vlocation **_vlocation)
+{
+	struct afs_cache_vlocation vldb;
+	struct afs_vlocation *vlocation;
+	afs_voltype_t voltype;
+	afs_volid_t vid;
+	int active = 0, ret;
+
+	_enter("{%s},%*.*s,%u,", cell->name, namesz, namesz, name, namesz);
+
+	if (namesz > sizeof(vlocation->vldb.name)) {
+		_leave(" = -ENAMETOOLONG");
+		return -ENAMETOOLONG;
+	}
+
+	/* search the cell's active list first */
+	list_for_each_entry(vlocation, &cell->vl_list, link) {
+		if (namesz < sizeof(vlocation->vldb.name) &&
+		    vlocation->vldb.name[namesz] != '\0')
+			continue;
+
+		if (memcmp(vlocation->vldb.name, name, namesz) == 0)
+			goto found_in_memory;
+	}
+
+	/* search the cell's graveyard list second */
+	spin_lock(&cell->vl_gylock);
+	list_for_each_entry(vlocation, &cell->vl_graveyard, link) {
+		if (namesz < sizeof(vlocation->vldb.name) &&
+		    vlocation->vldb.name[namesz] != '\0')
+			continue;
+
+		if (memcmp(vlocation->vldb.name, name, namesz) == 0)
+			goto found_in_graveyard;
+	}
+	spin_unlock(&cell->vl_gylock);
+
+	/* not in the cell's in-memory lists - create a new record */
+	vlocation = kmalloc(sizeof(struct afs_vlocation), GFP_KERNEL);
+	if (!vlocation)
+		return -ENOMEM;
+
+	memset(vlocation, 0, sizeof(struct afs_vlocation));
+	atomic_set(&vlocation->usage, 1);
+	INIT_LIST_HEAD(&vlocation->link);
+	rwlock_init(&vlocation->lock);
+	memcpy(vlocation->vldb.name, name, namesz);
+
+	afs_timer_init(&vlocation->timeout, &afs_vlocation_timer_ops);
+	afs_timer_init(&vlocation->upd_timer, &afs_vlocation_update_timer_ops);
+	afs_async_op_init(&vlocation->upd_op, &afs_vlocation_update_op_ops);
+
+	afs_get_cell(cell);
+	vlocation->cell = cell;
+
+	list_add_tail(&vlocation->link, &cell->vl_list);
+
+#ifdef AFS_CACHING_SUPPORT
+	/* we want to store it in the cache, plus it might already be
+	 * encached */
+	cachefs_acquire_cookie(cell->cache,
+			       &afs_volume_cache_index_def,
+			       vlocation,
+			       &vlocation->cache);
+
+	if (vlocation->valid)
+		goto found_in_cache;
+#endif
+
+	/* try to look up an unknown volume in the cell VL databases by name */
+	ret = afs_vlocation_access_vl_by_name(vlocation, name, namesz, &vldb);
+	if (ret < 0) {
+		printk("kAFS: failed to locate '%*.*s' in cell '%s'\n",
+		       namesz, namesz, name, cell->name);
+		goto error;
+	}
+
+	goto found_on_vlserver;
+
+ found_in_graveyard:
+	/* found in the graveyard - resurrect */
+	_debug("found in graveyard");
+	atomic_inc(&vlocation->usage);
+	list_del(&vlocation->link);
+	list_add_tail(&vlocation->link, &cell->vl_list);
+	spin_unlock(&cell->vl_gylock);
+
+	afs_kafstimod_del_timer(&vlocation->timeout);
+	goto active;
+
+ found_in_memory:
+	/* found in memory - check to see if it's active */
+	_debug("found in memory");
+	atomic_inc(&vlocation->usage);
+
+ active:
+	active = 1;
+
+#ifdef AFS_CACHING_SUPPORT
+ found_in_cache:
+#endif
+	/* try to look up a cached volume in the cell VL databases by ID */
+	_debug("found in cache");
+
+	_debug("Locally Cached: %s %02x { %08x(%x) %08x(%x) %08x(%x) }",
+	       vlocation->vldb.name,
+	       vlocation->vldb.vidmask,
+	       ntohl(vlocation->vldb.servers[0].s_addr),
+	       vlocation->vldb.srvtmask[0],
+	       ntohl(vlocation->vldb.servers[1].s_addr),
+	       vlocation->vldb.srvtmask[1],
+	       ntohl(vlocation->vldb.servers[2].s_addr),
+	       vlocation->vldb.srvtmask[2]
+	       );
+
+	_debug("Vids: %08x %08x %08x",
+	       vlocation->vldb.vid[0],
+	       vlocation->vldb.vid[1],
+	       vlocation->vldb.vid[2]);
+
+	if (vlocation->vldb.vidmask & AFS_VOL_VTM_RW) {
+		vid = vlocation->vldb.vid[0];
+		voltype = AFSVL_RWVOL;
+	}
+	else if (vlocation->vldb.vidmask & AFS_VOL_VTM_RO) {
+		vid = vlocation->vldb.vid[1];
+		voltype = AFSVL_ROVOL;
+	}
+	else if (vlocation->vldb.vidmask & AFS_VOL_VTM_BAK) {
+		vid = vlocation->vldb.vid[2];
+		voltype = AFSVL_BACKVOL;
+	}
+	else {
+		BUG();
+		vid = 0;
+		voltype = 0;
+	}
+
+	ret = afs_vlocation_access_vl_by_id(vlocation, vid, voltype, &vldb);
+	switch (ret) {
+		/* net error */
+	default:
+		printk("kAFS: failed to volume '%*.*s' (%x) up in '%s': %d\n",
+		       namesz, namesz, name, vid, cell->name, ret);
+		goto error;
+
+		/* pulled from local cache into memory */
+	case 0:
+		goto found_on_vlserver;
+
+		/* uh oh... looks like the volume got deleted */
+	case -ENOMEDIUM:
+		printk("kAFS: volume '%*.*s' (%x) does not exist '%s'\n",
+		       namesz, namesz, name, vid, cell->name);
+
+		/* TODO: make existing record unavailable */
+		goto error;
+	}
+
+ found_on_vlserver:
+	_debug("Done VL Lookup: %*.*s %02x { %08x(%x) %08x(%x) %08x(%x) }",
+	       namesz, namesz, name,
+	       vldb.vidmask,
+	       ntohl(vldb.servers[0].s_addr), vldb.srvtmask[0],
+	       ntohl(vldb.servers[1].s_addr), vldb.srvtmask[1],
+	       ntohl(vldb.servers[2].s_addr), vldb.srvtmask[2]
+	       );
+
+	_debug("Vids: %08x %08x %08x", vldb.vid[0], vldb.vid[1], vldb.vid[2]);
+
+	if ((namesz < sizeof(vlocation->vldb.name) &&
+	     vlocation->vldb.name[namesz] != '\0') ||
+	    memcmp(vldb.name, name, namesz) != 0)
+		printk("kAFS: name of volume '%*.*s' changed to '%s' on server\n",
+		       namesz, namesz, name, vldb.name);
+
+	memcpy(&vlocation->vldb, &vldb, sizeof(vlocation->vldb));
+
+	afs_kafstimod_add_timer(&vlocation->upd_timer, 10 * HZ);
+
+#ifdef AFS_CACHING_SUPPORT
+	/* update volume entry in local cache */
+	cachefs_update_cookie(vlocation->cache);
+#endif
+
+	*_vlocation = vlocation;
+	_leave(" = 0 (%p)",vlocation);
+	return 0;
+
+ error:
+	if (vlocation) {
+		if (active) {
+			__afs_put_vlocation(vlocation);
+		}
+		else {
+			list_del(&vlocation->link);
+#ifdef AFS_CACHING_SUPPORT
+			cachefs_relinquish_cookie(vlocation->cache, 0);
+#endif
+			afs_put_cell(vlocation->cell);
+			kfree(vlocation);
+		}
+	}
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_vlocation_lookup() */
+
+/*****************************************************************************/
+/*
+ * finish using a volume location record
+ * - caller must have cell->vol_sem write-locked
+ */
+static void __afs_put_vlocation(struct afs_vlocation *vlocation)
+{
+	struct afs_cell *cell;
+
+	if (!vlocation)
+		return;
+
+	_enter("%s", vlocation->vldb.name);
+
+	cell = vlocation->cell;
+
+	/* sanity check */
+	BUG_ON(atomic_read(&vlocation->usage) <= 0);
+
+	spin_lock(&cell->vl_gylock);
+	if (likely(!atomic_dec_and_test(&vlocation->usage))) {
+		spin_unlock(&cell->vl_gylock);
+		_leave("");
+		return;
+	}
+
+	/* move to graveyard queue */
+	list_del(&vlocation->link);
+	list_add_tail(&vlocation->link,&cell->vl_graveyard);
+
+	/* remove from pending timeout queue (refcounted if actually being
+	 * updated) */
+	list_del_init(&vlocation->upd_op.link);
+
+	/* time out in 10 secs */
+	afs_kafstimod_del_timer(&vlocation->upd_timer);
+	afs_kafstimod_add_timer(&vlocation->timeout, 10 * HZ);
+
+	spin_unlock(&cell->vl_gylock);
+
+	_leave(" [killed]");
+} /* end __afs_put_vlocation() */
+
+/*****************************************************************************/
+/*
+ * finish using a volume location record
+ */
+void afs_put_vlocation(struct afs_vlocation *vlocation)
+{
+	if (vlocation) {
+		struct afs_cell *cell = vlocation->cell;
+
+		down_write(&cell->vl_sem);
+		__afs_put_vlocation(vlocation);
+		up_write(&cell->vl_sem);
+	}
+} /* end afs_put_vlocation() */
+
+/*****************************************************************************/
+/*
+ * timeout vlocation record
+ * - removes from the cell's graveyard if the usage count is zero
+ */
+void afs_vlocation_do_timeout(struct afs_vlocation *vlocation)
+{
+	struct afs_cell *cell;
+
+	_enter("%s", vlocation->vldb.name);
+
+	cell = vlocation->cell;
+
+	BUG_ON(atomic_read(&vlocation->usage) < 0);
+
+	/* remove from graveyard if still dead */
+	spin_lock(&cell->vl_gylock);
+	if (atomic_read(&vlocation->usage) == 0)
+		list_del_init(&vlocation->link);
+	else
+		vlocation = NULL;
+	spin_unlock(&cell->vl_gylock);
+
+	if (!vlocation) {
+		_leave("");
+		return; /* resurrected */
+	}
+
+	/* we can now destroy it properly */
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_relinquish_cookie(vlocation->cache, 0);
+#endif
+	afs_put_cell(cell);
+
+	kfree(vlocation);
+
+	_leave(" [destroyed]");
+} /* end afs_vlocation_do_timeout() */
+
+/*****************************************************************************/
+/*
+ * send an update operation to the currently selected server
+ */
+static int afs_vlocation_update_begin(struct afs_vlocation *vlocation)
+{
+	afs_voltype_t voltype;
+	afs_volid_t vid;
+	int ret;
+
+	_enter("%s{ufs=%u ucs=%u}",
+	       vlocation->vldb.name,
+	       vlocation->upd_first_svix,
+	       vlocation->upd_curr_svix);
+
+	/* try to look up a cached volume in the cell VL databases by ID */
+	if (vlocation->vldb.vidmask & AFS_VOL_VTM_RW) {
+		vid = vlocation->vldb.vid[0];
+		voltype = AFSVL_RWVOL;
+	}
+	else if (vlocation->vldb.vidmask & AFS_VOL_VTM_RO) {
+		vid = vlocation->vldb.vid[1];
+		voltype = AFSVL_ROVOL;
+	}
+	else if (vlocation->vldb.vidmask & AFS_VOL_VTM_BAK) {
+		vid = vlocation->vldb.vid[2];
+		voltype = AFSVL_BACKVOL;
+	}
+	else {
+		BUG();
+		vid = 0;
+		voltype = 0;
+	}
+
+	/* contact the chosen server */
+	ret = afs_server_lookup(
+		vlocation->cell,
+		&vlocation->cell->vl_addrs[vlocation->upd_curr_svix],
+		&vlocation->upd_op.server);
+
+	switch (ret) {
+	case 0:
+		break;
+	case -ENOMEM:
+	case -ENONET:
+	default:
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+	/* initiate the update operation */
+	ret = afs_rxvl_get_entry_by_id_async(&vlocation->upd_op, vid, voltype);
+	if (ret < 0) {
+		_leave(" = %d", ret);
+		return ret;
+	}
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_vlocation_update_begin() */
+
+/*****************************************************************************/
+/*
+ * abandon updating a VL record
+ * - does not restart the update timer
+ */
+static void afs_vlocation_update_abandon(struct afs_vlocation *vlocation,
+					 afs_vlocation_upd_t state,
+					 int ret)
+{
+	_enter("%s,%u", vlocation->vldb.name, state);
+
+	if (ret < 0)
+		printk("kAFS: Abandoning VL update '%s': %d\n",
+		       vlocation->vldb.name, ret);
+
+	/* discard the server record */
+	afs_put_server(vlocation->upd_op.server);
+	vlocation->upd_op.server = NULL;
+
+	spin_lock(&afs_vlocation_update_lock);
+	afs_vlocation_update = NULL;
+	vlocation->upd_state = state;
+
+	/* TODO: start updating next VL record on pending list */
+
+	spin_unlock(&afs_vlocation_update_lock);
+
+	_leave("");
+} /* end afs_vlocation_update_abandon() */
+
+/*****************************************************************************/
+/*
+ * handle periodic update timeouts and busy retry timeouts
+ * - called from kafstimod
+ */
+static void afs_vlocation_update_timer(struct afs_timer *timer)
+{
+	struct afs_vlocation *vlocation =
+		list_entry(timer, struct afs_vlocation, upd_timer);
+	int ret;
+
+	_enter("%s", vlocation->vldb.name);
+
+	/* only update if not in the graveyard (defend against putting too) */
+	spin_lock(&vlocation->cell->vl_gylock);
+
+	if (!atomic_read(&vlocation->usage))
+		goto out_unlock1;
+
+	spin_lock(&afs_vlocation_update_lock);
+
+	/* if we were woken up due to EBUSY sleep then restart immediately if
+	 * possible or else jump to front of pending queue */
+	if (vlocation->upd_state == AFS_VLUPD_BUSYSLEEP) {
+		if (afs_vlocation_update) {
+			list_add(&vlocation->upd_op.link,
+				 &afs_vlocation_update_pendq);
+		}
+		else {
+			afs_get_vlocation(vlocation);
+			afs_vlocation_update = vlocation;
+			vlocation->upd_state = AFS_VLUPD_INPROGRESS;
+		}
+		goto out_unlock2;
+	}
+
+	/* put on pending queue if there's already another update in progress */
+	if (afs_vlocation_update) {
+		vlocation->upd_state = AFS_VLUPD_PENDING;
+		list_add_tail(&vlocation->upd_op.link,
+			      &afs_vlocation_update_pendq);
+		goto out_unlock2;
+	}
+
+	/* hold a ref on it while actually updating */
+	afs_get_vlocation(vlocation);
+	afs_vlocation_update = vlocation;
+	vlocation->upd_state = AFS_VLUPD_INPROGRESS;
+
+	spin_unlock(&afs_vlocation_update_lock);
+	spin_unlock(&vlocation->cell->vl_gylock);
+
+	/* okay... we can start the update */
+	_debug("BEGIN VL UPDATE [%s]", vlocation->vldb.name);
+	vlocation->upd_first_svix = vlocation->cell->vl_curr_svix;
+	vlocation->upd_curr_svix = vlocation->upd_first_svix;
+	vlocation->upd_rej_cnt = 0;
+	vlocation->upd_busy_cnt = 0;
+
+	ret = afs_vlocation_update_begin(vlocation);
+	if (ret < 0) {
+		afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, ret);
+		afs_kafstimod_add_timer(&vlocation->upd_timer,
+					AFS_VLDB_TIMEOUT);
+		afs_put_vlocation(vlocation);
+	}
+
+	_leave("");
+	return;
+
+ out_unlock2:
+	spin_unlock(&afs_vlocation_update_lock);
+ out_unlock1:
+	spin_unlock(&vlocation->cell->vl_gylock);
+	_leave("");
+	return;
+
+} /* end afs_vlocation_update_timer() */
+
+/*****************************************************************************/
+/*
+ * attend to an update operation upon which an event happened
+ * - called in kafsasyncd context
+ */
+static void afs_vlocation_update_attend(struct afs_async_op *op)
+{
+	struct afs_cache_vlocation vldb;
+	struct afs_vlocation *vlocation =
+		list_entry(op, struct afs_vlocation, upd_op);
+	unsigned tmp;
+	int ret;
+
+	_enter("%s", vlocation->vldb.name);
+
+	ret = afs_rxvl_get_entry_by_id_async2(op, &vldb);
+	switch (ret) {
+	case -EAGAIN:
+		_leave(" [unfinished]");
+		return;
+
+	case 0:
+		_debug("END VL UPDATE: %d\n", ret);
+		vlocation->valid = 1;
+
+		_debug("Done VL Lookup: %02x { %08x(%x) %08x(%x) %08x(%x) }",
+		       vldb.vidmask,
+		       ntohl(vldb.servers[0].s_addr), vldb.srvtmask[0],
+		       ntohl(vldb.servers[1].s_addr), vldb.srvtmask[1],
+		       ntohl(vldb.servers[2].s_addr), vldb.srvtmask[2]
+		       );
+
+		_debug("Vids: %08x %08x %08x",
+		       vldb.vid[0], vldb.vid[1], vldb.vid[2]);
+
+		afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, 0);
+
+		down_write(&vlocation->cell->vl_sem);
+
+		/* actually update the cache */
+		if (strncmp(vldb.name, vlocation->vldb.name,
+			    sizeof(vlocation->vldb.name)) != 0)
+			printk("kAFS: name of volume '%s'"
+			       " changed to '%s' on server\n",
+			       vlocation->vldb.name, vldb.name);
+
+		memcpy(&vlocation->vldb, &vldb, sizeof(vlocation->vldb));
+
+#if 0
+		/* TODO update volume entry in local cache */
+#endif
+
+		up_write(&vlocation->cell->vl_sem);
+
+		if (ret < 0)
+			printk("kAFS: failed to update local cache: %d\n", ret);
+
+		afs_kafstimod_add_timer(&vlocation->upd_timer,
+					AFS_VLDB_TIMEOUT);
+		afs_put_vlocation(vlocation);
+		_leave(" [found]");
+		return;
+
+	case -ENOMEDIUM:
+		vlocation->upd_rej_cnt++;
+		goto try_next;
+
+		/* the server is locked - retry in a very short while */
+	case -EBUSY:
+		vlocation->upd_busy_cnt++;
+		if (vlocation->upd_busy_cnt > 3)
+			goto try_next; /* too many retries */
+
+		afs_vlocation_update_abandon(vlocation,
+					     AFS_VLUPD_BUSYSLEEP, 0);
+		afs_kafstimod_add_timer(&vlocation->upd_timer, HZ / 2);
+		afs_put_vlocation(vlocation);
+		_leave(" [busy]");
+		return;
+
+	case -ENETUNREACH:
+	case -EHOSTUNREACH:
+	case -ECONNREFUSED:
+	case -EREMOTEIO:
+		/* record bad vlserver info in the cell too
+		 * - TODO: use down_write_trylock() if available
+		 */
+		if (vlocation->upd_curr_svix == vlocation->cell->vl_curr_svix)
+			vlocation->cell->vl_curr_svix =
+				vlocation->cell->vl_curr_svix %
+				vlocation->cell->vl_naddrs;
+
+	case -EBADRQC:
+	case -EINVAL:
+	case -EACCES:
+	case -EBADMSG:
+		goto try_next;
+
+	default:
+		goto abandon;
+	}
+
+	/* try contacting the next server */
+ try_next:
+	vlocation->upd_busy_cnt = 0;
+
+	/* discard the server record */
+	afs_put_server(vlocation->upd_op.server);
+	vlocation->upd_op.server = NULL;
+
+	tmp = vlocation->cell->vl_naddrs;
+	if (tmp == 0)
+		goto abandon;
+
+	vlocation->upd_curr_svix++;
+	if (vlocation->upd_curr_svix >= tmp)
+		vlocation->upd_curr_svix = 0;
+	if (vlocation->upd_first_svix >= tmp)
+		vlocation->upd_first_svix = tmp - 1;
+
+	/* move to the next server */
+	if (vlocation->upd_curr_svix != vlocation->upd_first_svix) {
+		afs_vlocation_update_begin(vlocation);
+		_leave(" [next]");
+		return;
+	}
+
+	/* run out of servers to try - was the volume rejected? */
+	if (vlocation->upd_rej_cnt > 0) {
+		printk("kAFS: Active volume no longer valid '%s'\n",
+		       vlocation->vldb.name);
+		vlocation->valid = 0;
+		afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, 0);
+		afs_kafstimod_add_timer(&vlocation->upd_timer,
+					AFS_VLDB_TIMEOUT);
+		afs_put_vlocation(vlocation);
+		_leave(" [invalidated]");
+		return;
+	}
+
+	/* abandon the update */
+ abandon:
+	afs_vlocation_update_abandon(vlocation, AFS_VLUPD_SLEEP, ret);
+	afs_kafstimod_add_timer(&vlocation->upd_timer, HZ * 10);
+	afs_put_vlocation(vlocation);
+	_leave(" [abandoned]");
+
+} /* end afs_vlocation_update_attend() */
+
+/*****************************************************************************/
+/*
+ * deal with an update operation being discarded
+ * - called in kafsasyncd context when it's dying due to rmmod
+ * - the call has already been aborted and put()'d
+ */
+static void afs_vlocation_update_discard(struct afs_async_op *op)
+{
+	struct afs_vlocation *vlocation =
+		list_entry(op, struct afs_vlocation, upd_op);
+
+	_enter("%s", vlocation->vldb.name);
+
+	afs_put_server(op->server);
+	op->server = NULL;
+
+	afs_put_vlocation(vlocation);
+
+	_leave("");
+} /* end afs_vlocation_update_discard() */
+
+/*****************************************************************************/
+/*
+ * match a VLDB record stored in the cache
+ * - may also load target from entry
+ */
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_vlocation_cache_match(void *target,
+						     const void *entry)
+{
+	const struct afs_cache_vlocation *vldb = entry;
+	struct afs_vlocation *vlocation = target;
+
+	_enter("{%s},{%s}", vlocation->vldb.name, vldb->name);
+
+	if (strncmp(vlocation->vldb.name, vldb->name, sizeof(vldb->name)) == 0
+	    ) {
+		if (!vlocation->valid ||
+		    vlocation->vldb.rtime == vldb->rtime
+		    ) {
+			vlocation->vldb = *vldb;
+			vlocation->valid = 1;
+			_leave(" = SUCCESS [c->m]");
+			return CACHEFS_MATCH_SUCCESS;
+		}
+		/* need to update cache if cached info differs */
+		else if (memcmp(&vlocation->vldb, vldb, sizeof(*vldb)) != 0) {
+			/* delete if VIDs for this name differ */
+			if (memcmp(&vlocation->vldb.vid,
+				   &vldb->vid,
+				   sizeof(vldb->vid)) != 0) {
+				_leave(" = DELETE");
+				return CACHEFS_MATCH_SUCCESS_DELETE;
+			}
+
+			_leave(" = UPDATE");
+			return CACHEFS_MATCH_SUCCESS_UPDATE;
+		}
+		else {
+			_leave(" = SUCCESS");
+			return CACHEFS_MATCH_SUCCESS;
+		}
+	}
+
+	_leave(" = FAILED");
+	return CACHEFS_MATCH_FAILED;
+} /* end afs_vlocation_cache_match() */
+#endif
+
+/*****************************************************************************/
+/*
+ * update a VLDB record stored in the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static void afs_vlocation_cache_update(void *source, void *entry)
+{
+	struct afs_cache_vlocation *vldb = entry;
+	struct afs_vlocation *vlocation = source;
+
+	_enter("");
+
+	*vldb = vlocation->vldb;
+
+} /* end afs_vlocation_cache_update() */
+#endif
diff --git a/fs/afs/vnode.c b/fs/afs/vnode.c
new file mode 100644
index 000000000000..9867fef3261d
--- /dev/null
+++ b/fs/afs/vnode.c
@@ -0,0 +1,395 @@
+/* vnode.c: AFS vnode management
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include "volume.h"
+#include "cell.h"
+#include "cmservice.h"
+#include "fsclient.h"
+#include "vlclient.h"
+#include "vnode.h"
+#include "internal.h"
+
+static void afs_vnode_cb_timed_out(struct afs_timer *timer);
+
+struct afs_timer_ops afs_vnode_cb_timed_out_ops = {
+	.timed_out	= afs_vnode_cb_timed_out,
+};
+
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_vnode_cache_match(void *target,
+						 const void *entry);
+static void afs_vnode_cache_update(void *source, void *entry);
+
+struct cachefs_index_def afs_vnode_cache_index_def = {
+	.name		= "vnode",
+	.data_size	= sizeof(struct afs_cache_vnode),
+	.keys[0]	= { CACHEFS_INDEX_KEYS_BIN, 4 },
+	.match		= afs_vnode_cache_match,
+	.update		= afs_vnode_cache_update,
+};
+#endif
+
+/*****************************************************************************/
+/*
+ * handle a callback timing out
+ * TODO: retain a ref to vnode struct for an outstanding callback timeout
+ */
+static void afs_vnode_cb_timed_out(struct afs_timer *timer)
+{
+	struct afs_server *oldserver;
+	struct afs_vnode *vnode;
+
+	vnode = list_entry(timer, struct afs_vnode, cb_timeout);
+
+	_enter("%p", vnode);
+
+	/* set the changed flag in the vnode and release the server */
+	spin_lock(&vnode->lock);
+
+	oldserver = xchg(&vnode->cb_server, NULL);
+	if (oldserver) {
+		vnode->flags |= AFS_VNODE_CHANGED;
+
+		spin_lock(&afs_cb_hash_lock);
+		list_del_init(&vnode->cb_hash_link);
+		spin_unlock(&afs_cb_hash_lock);
+
+		spin_lock(&oldserver->cb_lock);
+		list_del_init(&vnode->cb_link);
+		spin_unlock(&oldserver->cb_lock);
+	}
+
+	spin_unlock(&vnode->lock);
+
+	afs_put_server(oldserver);
+
+	_leave("");
+} /* end afs_vnode_cb_timed_out() */
+
+/*****************************************************************************/
+/*
+ * finish off updating the recorded status of a file
+ * - starts callback expiry timer
+ * - adds to server's callback list
+ */
+static void afs_vnode_finalise_status_update(struct afs_vnode *vnode,
+					     struct afs_server *server,
+					     int ret)
+{
+	struct afs_server *oldserver = NULL;
+
+	_enter("%p,%p,%d", vnode, server, ret);
+
+	spin_lock(&vnode->lock);
+
+	vnode->flags &= ~AFS_VNODE_CHANGED;
+
+	if (ret == 0) {
+		/* adjust the callback timeout appropriately */
+		afs_kafstimod_add_timer(&vnode->cb_timeout,
+					vnode->cb_expiry * HZ);
+
+		spin_lock(&afs_cb_hash_lock);
+		list_del(&vnode->cb_hash_link);
+		list_add_tail(&vnode->cb_hash_link,
+			      &afs_cb_hash(server, &vnode->fid));
+		spin_unlock(&afs_cb_hash_lock);
+
+		/* swap ref to old callback server with that for new callback
+		 * server */
+		oldserver = xchg(&vnode->cb_server, server);
+		if (oldserver != server) {
+			if (oldserver) {
+				spin_lock(&oldserver->cb_lock);
+				list_del_init(&vnode->cb_link);
+				spin_unlock(&oldserver->cb_lock);
+			}
+
+			afs_get_server(server);
+			spin_lock(&server->cb_lock);
+			list_add_tail(&vnode->cb_link, &server->cb_promises);
+			spin_unlock(&server->cb_lock);
+		}
+		else {
+			/* same server */
+			oldserver = NULL;
+		}
+	}
+	else if (ret == -ENOENT) {
+		/* the file was deleted - clear the callback timeout */
+		oldserver = xchg(&vnode->cb_server, NULL);
+		afs_kafstimod_del_timer(&vnode->cb_timeout);
+
+		_debug("got NOENT from server - marking file deleted");
+		vnode->flags |= AFS_VNODE_DELETED;
+	}
+
+	vnode->update_cnt--;
+
+	spin_unlock(&vnode->lock);
+
+	wake_up_all(&vnode->update_waitq);
+
+	afs_put_server(oldserver);
+
+	_leave("");
+
+} /* end afs_vnode_finalise_status_update() */
+
+/*****************************************************************************/
+/*
+ * fetch file status from the volume
+ * - don't issue a fetch if:
+ *   - the changed bit is not set and there's a valid callback
+ *   - there are any outstanding ops that will fetch the status
+ * - TODO implement local caching
+ */
+int afs_vnode_fetch_status(struct afs_vnode *vnode)
+{
+	struct afs_server *server;
+	int ret;
+
+	DECLARE_WAITQUEUE(myself, current);
+
+	_enter("%s,{%u,%u,%u}",
+	       vnode->volume->vlocation->vldb.name,
+	       vnode->fid.vid, vnode->fid.vnode, vnode->fid.unique);
+
+	if (!(vnode->flags & AFS_VNODE_CHANGED) && vnode->cb_server) {
+		_leave(" [unchanged]");
+		return 0;
+	}
+
+	if (vnode->flags & AFS_VNODE_DELETED) {
+		_leave(" [deleted]");
+		return -ENOENT;
+	}
+
+	spin_lock(&vnode->lock);
+
+	if (!(vnode->flags & AFS_VNODE_CHANGED)) {
+		spin_unlock(&vnode->lock);
+		_leave(" [unchanged]");
+		return 0;
+	}
+
+	if (vnode->update_cnt > 0) {
+		/* someone else started a fetch */
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		add_wait_queue(&vnode->update_waitq, &myself);
+
+		/* wait for the status to be updated */
+		for (;;) {
+			if (!(vnode->flags & AFS_VNODE_CHANGED))
+				break;
+			if (vnode->flags & AFS_VNODE_DELETED)
+				break;
+
+			/* it got updated and invalidated all before we saw
+			 * it */
+			if (vnode->update_cnt == 0) {
+				remove_wait_queue(&vnode->update_waitq,
+						  &myself);
+				set_current_state(TASK_RUNNING);
+				goto get_anyway;
+			}
+
+			spin_unlock(&vnode->lock);
+
+			schedule();
+			set_current_state(TASK_UNINTERRUPTIBLE);
+
+			spin_lock(&vnode->lock);
+		}
+
+		remove_wait_queue(&vnode->update_waitq, &myself);
+		spin_unlock(&vnode->lock);
+		set_current_state(TASK_RUNNING);
+
+		return vnode->flags & AFS_VNODE_DELETED ? -ENOENT : 0;
+	}
+
+ get_anyway:
+	/* okay... we're going to have to initiate the op */
+	vnode->update_cnt++;
+
+	spin_unlock(&vnode->lock);
+
+	/* merge AFS status fetches and clear outstanding callback on this
+	 * vnode */
+	do {
+		/* pick a server to query */
+		ret = afs_volume_pick_fileserver(vnode->volume, &server);
+		if (ret<0)
+			return ret;
+
+		_debug("USING SERVER: %08x\n", ntohl(server->addr.s_addr));
+
+		ret = afs_rxfs_fetch_file_status(server, vnode, NULL);
+
+	} while (!afs_volume_release_fileserver(vnode->volume, server, ret));
+
+	/* adjust the flags */
+	afs_vnode_finalise_status_update(vnode, server, ret);
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_vnode_fetch_status() */
+
+/*****************************************************************************/
+/*
+ * fetch file data from the volume
+ * - TODO implement caching and server failover
+ */
+int afs_vnode_fetch_data(struct afs_vnode *vnode,
+			 struct afs_rxfs_fetch_descriptor *desc)
+{
+	struct afs_server *server;
+	int ret;
+
+	_enter("%s,{%u,%u,%u}",
+	       vnode->volume->vlocation->vldb.name,
+	       vnode->fid.vid,
+	       vnode->fid.vnode,
+	       vnode->fid.unique);
+
+	/* this op will fetch the status */
+	spin_lock(&vnode->lock);
+	vnode->update_cnt++;
+	spin_unlock(&vnode->lock);
+
+	/* merge in AFS status fetches and clear outstanding callback on this
+	 * vnode */
+	do {
+		/* pick a server to query */
+		ret = afs_volume_pick_fileserver(vnode->volume, &server);
+		if (ret < 0)
+			return ret;
+
+		_debug("USING SERVER: %08x\n", ntohl(server->addr.s_addr));
+
+		ret = afs_rxfs_fetch_file_data(server, vnode, desc, NULL);
+
+	} while (!afs_volume_release_fileserver(vnode->volume, server, ret));
+
+	/* adjust the flags */
+	afs_vnode_finalise_status_update(vnode, server, ret);
+
+	_leave(" = %d", ret);
+	return ret;
+
+} /* end afs_vnode_fetch_data() */
+
+/*****************************************************************************/
+/*
+ * break any outstanding callback on a vnode
+ * - only relevent to server that issued it
+ */
+int afs_vnode_give_up_callback(struct afs_vnode *vnode)
+{
+	struct afs_server *server;
+	int ret;
+
+	_enter("%s,{%u,%u,%u}",
+	       vnode->volume->vlocation->vldb.name,
+	       vnode->fid.vid,
+	       vnode->fid.vnode,
+	       vnode->fid.unique);
+
+	spin_lock(&afs_cb_hash_lock);
+	list_del_init(&vnode->cb_hash_link);
+	spin_unlock(&afs_cb_hash_lock);
+
+	/* set the changed flag in the vnode and release the server */
+	spin_lock(&vnode->lock);
+
+	afs_kafstimod_del_timer(&vnode->cb_timeout);
+
+	server = xchg(&vnode->cb_server, NULL);
+	if (server) {
+		vnode->flags |= AFS_VNODE_CHANGED;
+
+		spin_lock(&server->cb_lock);
+		list_del_init(&vnode->cb_link);
+		spin_unlock(&server->cb_lock);
+	}
+
+	spin_unlock(&vnode->lock);
+
+	ret = 0;
+	if (server) {
+		ret = afs_rxfs_give_up_callback(server, vnode);
+		afs_put_server(server);
+	}
+
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_vnode_give_up_callback() */
+
+/*****************************************************************************/
+/*
+ * match a vnode record stored in the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_vnode_cache_match(void *target,
+						 const void *entry)
+{
+	const struct afs_cache_vnode *cvnode = entry;
+	struct afs_vnode *vnode = target;
+
+	_enter("{%x,%x,%Lx},{%x,%x,%Lx}",
+	       vnode->fid.vnode,
+	       vnode->fid.unique,
+	       vnode->status.version,
+	       cvnode->vnode_id,
+	       cvnode->vnode_unique,
+	       cvnode->data_version);
+
+	if (vnode->fid.vnode != cvnode->vnode_id) {
+		_leave(" = FAILED");
+		return CACHEFS_MATCH_FAILED;
+	}
+
+	if (vnode->fid.unique != cvnode->vnode_unique ||
+	    vnode->status.version != cvnode->data_version) {
+		_leave(" = DELETE");
+		return CACHEFS_MATCH_SUCCESS_DELETE;
+	}
+
+	_leave(" = SUCCESS");
+	return CACHEFS_MATCH_SUCCESS;
+} /* end afs_vnode_cache_match() */
+#endif
+
+/*****************************************************************************/
+/*
+ * update a vnode record stored in the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static void afs_vnode_cache_update(void *source, void *entry)
+{
+	struct afs_cache_vnode *cvnode = entry;
+	struct afs_vnode *vnode = source;
+
+	_enter("");
+
+	cvnode->vnode_id	= vnode->fid.vnode;
+	cvnode->vnode_unique	= vnode->fid.unique;
+	cvnode->data_version	= vnode->status.version;
+
+} /* end afs_vnode_cache_update() */
+#endif
diff --git a/fs/afs/vnode.h b/fs/afs/vnode.h
new file mode 100644
index 000000000000..b86a97102e8b
--- /dev/null
+++ b/fs/afs/vnode.h
@@ -0,0 +1,94 @@
+/* vnode.h: AFS vnode record
+ *
+ * Copyright (C) 2002 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_AFS_VNODE_H
+#define _LINUX_AFS_VNODE_H
+
+#include <linux/fs.h>
+#include "server.h"
+#include "kafstimod.h"
+#include "cache.h"
+
+#ifdef __KERNEL__
+
+struct afs_rxfs_fetch_descriptor;
+
+/*****************************************************************************/
+/*
+ * vnode catalogue entry
+ */
+struct afs_cache_vnode
+{
+	afs_vnodeid_t		vnode_id;	/* vnode ID */
+	unsigned		vnode_unique;	/* vnode ID uniquifier */
+	afs_dataversion_t	data_version;	/* data version */
+};
+
+#ifdef AFS_CACHING_SUPPORT
+extern struct cachefs_index_def afs_vnode_cache_index_def;
+#endif
+
+/*****************************************************************************/
+/*
+ * AFS inode private data
+ */
+struct afs_vnode
+{
+	struct inode		vfs_inode;	/* the VFS's inode record */
+
+	struct afs_volume	*volume;	/* volume on which vnode resides */
+	struct afs_fid		fid;		/* the file identifier for this inode */
+	struct afs_file_status	status;		/* AFS status info for this file */
+#ifdef AFS_CACHING_SUPPORT
+	struct cachefs_cookie	*cache;		/* caching cookie */
+#endif
+
+	wait_queue_head_t	update_waitq;	/* status fetch waitqueue */
+	unsigned		update_cnt;	/* number of outstanding ops that will update the
+						 * status */
+	spinlock_t		lock;		/* waitqueue/flags lock */
+	unsigned		flags;
+#define AFS_VNODE_CHANGED	0x00000001	/* set if vnode reported changed by callback */
+#define AFS_VNODE_DELETED	0x00000002	/* set if vnode deleted on server */
+#define AFS_VNODE_MOUNTPOINT	0x00000004	/* set if vnode is a mountpoint symlink */
+
+	/* outstanding callback notification on this file */
+	struct afs_server	*cb_server;	/* server that made the current promise */
+	struct list_head	cb_link;	/* link in server's promises list */
+	struct list_head	cb_hash_link;	/* link in master callback hash */
+	struct afs_timer	cb_timeout;	/* timeout on promise */
+	unsigned		cb_version;	/* callback version */
+	unsigned		cb_expiry;	/* callback expiry time */
+	afs_callback_type_t	cb_type;	/* type of callback */
+};
+
+static inline struct afs_vnode *AFS_FS_I(struct inode *inode)
+{
+	return container_of(inode,struct afs_vnode,vfs_inode);
+}
+
+static inline struct inode *AFS_VNODE_TO_I(struct afs_vnode *vnode)
+{
+	return &vnode->vfs_inode;
+}
+
+extern int afs_vnode_fetch_status(struct afs_vnode *vnode);
+
+extern int afs_vnode_fetch_data(struct afs_vnode *vnode,
+				struct afs_rxfs_fetch_descriptor *desc);
+
+extern int afs_vnode_give_up_callback(struct afs_vnode *vnode);
+
+extern struct afs_timer_ops afs_vnode_cb_timed_out_ops;
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_AFS_VNODE_H */
diff --git a/fs/afs/volume.c b/fs/afs/volume.c
new file mode 100644
index 000000000000..0ff4b86476e3
--- /dev/null
+++ b/fs/afs/volume.c
@@ -0,0 +1,520 @@
+/* volume.c: AFS volume management
+ *
+ * Copyright (C) 2002 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include "volume.h"
+#include "vnode.h"
+#include "cell.h"
+#include "cache.h"
+#include "cmservice.h"
+#include "fsclient.h"
+#include "vlclient.h"
+#include "internal.h"
+
+#ifdef __KDEBUG
+static const char *afs_voltypes[] = { "R/W", "R/O", "BAK" };
+#endif
+
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_volume_cache_match(void *target,
+						  const void *entry);
+static void afs_volume_cache_update(void *source, void *entry);
+
+struct cachefs_index_def afs_volume_cache_index_def = {
+	.name		= "volume",
+	.data_size	= sizeof(struct afs_cache_vhash),
+	.keys[0]	= { CACHEFS_INDEX_KEYS_BIN, 1 },
+	.keys[1]	= { CACHEFS_INDEX_KEYS_BIN, 1 },
+	.match		= afs_volume_cache_match,
+	.update		= afs_volume_cache_update,
+};
+#endif
+
+/*****************************************************************************/
+/*
+ * lookup a volume by name
+ * - this can be one of the following:
+ *	"%[cell:]volume[.]"		R/W volume
+ *	"#[cell:]volume[.]"		R/O or R/W volume (rwparent=0),
+ *					 or R/W (rwparent=1) volume
+ *	"%[cell:]volume.readonly"	R/O volume
+ *	"#[cell:]volume.readonly"	R/O volume
+ *	"%[cell:]volume.backup"		Backup volume
+ *	"#[cell:]volume.backup"		Backup volume
+ *
+ * The cell name is optional, and defaults to the current cell.
+ *
+ * See "The Rules of Mount Point Traversal" in Chapter 5 of the AFS SysAdmin
+ * Guide
+ * - Rule 1: Explicit type suffix forces access of that type or nothing
+ *           (no suffix, then use Rule 2 & 3)
+ * - Rule 2: If parent volume is R/O, then mount R/O volume by preference, R/W
+ *           if not available
+ * - Rule 3: If parent volume is R/W, then only mount R/W volume unless
+ *           explicitly told otherwise
+ */
+int afs_volume_lookup(const char *name, struct afs_cell *cell, int rwpath,
+		      struct afs_volume **_volume)
+{
+	struct afs_vlocation *vlocation = NULL;
+	struct afs_volume *volume = NULL;
+	afs_voltype_t type;
+	const char *cellname, *volname, *suffix;
+	char srvtmask;
+	int force, ret, loop, cellnamesz, volnamesz;
+
+	_enter("%s,,%d,", name, rwpath);
+
+	if (!name || (name[0] != '%' && name[0] != '#') || !name[1]) {
+		printk("kAFS: unparsable volume name\n");
+		return -EINVAL;
+	}
+
+	/* determine the type of volume we're looking for */
+	force = 0;
+	type = AFSVL_ROVOL;
+
+	if (rwpath || name[0] == '%') {
+		type = AFSVL_RWVOL;
+		force = 1;
+	}
+
+	suffix = strrchr(name, '.');
+	if (suffix) {
+		if (strcmp(suffix, ".readonly") == 0) {
+			type = AFSVL_ROVOL;
+			force = 1;
+		}
+		else if (strcmp(suffix, ".backup") == 0) {
+			type = AFSVL_BACKVOL;
+			force = 1;
+		}
+		else if (suffix[1] == 0) {
+		}
+		else {
+			suffix = NULL;
+		}
+	}
+
+	/* split the cell and volume names */
+	name++;
+	volname = strchr(name, ':');
+	if (volname) {
+		cellname = name;
+		cellnamesz = volname - name;
+		volname++;
+	}
+	else {
+		volname = name;
+		cellname = NULL;
+		cellnamesz = 0;
+	}
+
+	volnamesz = suffix ? suffix - volname : strlen(volname);
+
+	_debug("CELL:%*.*s [%p] VOLUME:%*.*s SUFFIX:%s TYPE:%d%s",
+	       cellnamesz, cellnamesz, cellname ?: "", cell,
+	       volnamesz, volnamesz, volname, suffix ?: "-",
+	       type,
+	       force ? " FORCE" : "");
+
+	/* lookup the cell record */
+	if (cellname || !cell) {
+		ret = afs_cell_lookup(cellname, cellnamesz, &cell);
+		if (ret<0) {
+			printk("kAFS: unable to lookup cell '%s'\n",
+			       cellname ?: "");
+			goto error;
+		}
+	}
+	else {
+		afs_get_cell(cell);
+	}
+
+	/* lookup the volume location record */
+	ret = afs_vlocation_lookup(cell, volname, volnamesz, &vlocation);
+	if (ret < 0)
+		goto error;
+
+	/* make the final decision on the type we want */
+	ret = -ENOMEDIUM;
+	if (force && !(vlocation->vldb.vidmask & (1 << type)))
+		goto error;
+
+	srvtmask = 0;
+	for (loop = 0; loop < vlocation->vldb.nservers; loop++)
+		srvtmask |= vlocation->vldb.srvtmask[loop];
+
+	if (force) {
+		if (!(srvtmask & (1 << type)))
+			goto error;
+	}
+	else if (srvtmask & AFS_VOL_VTM_RO) {
+		type = AFSVL_ROVOL;
+	}
+	else if (srvtmask & AFS_VOL_VTM_RW) {
+		type = AFSVL_RWVOL;
+	}
+	else {
+		goto error;
+	}
+
+	down_write(&cell->vl_sem);
+
+	/* is the volume already active? */
+	if (vlocation->vols[type]) {
+		/* yes - re-use it */
+		volume = vlocation->vols[type];
+		afs_get_volume(volume);
+		goto success;
+	}
+
+	/* create a new volume record */
+	_debug("creating new volume record");
+
+	ret = -ENOMEM;
+	volume = kmalloc(sizeof(struct afs_volume), GFP_KERNEL);
+	if (!volume)
+		goto error_up;
+
+	memset(volume, 0, sizeof(struct afs_volume));
+	atomic_set(&volume->usage, 1);
+	volume->type		= type;
+	volume->type_force	= force;
+	volume->cell		= cell;
+	volume->vid		= vlocation->vldb.vid[type];
+
+	init_rwsem(&volume->server_sem);
+
+	/* look up all the applicable server records */
+	for (loop = 0; loop < 8; loop++) {
+		if (vlocation->vldb.srvtmask[loop] & (1 << volume->type)) {
+			ret = afs_server_lookup(
+				volume->cell,
+				&vlocation->vldb.servers[loop],
+				&volume->servers[volume->nservers]);
+			if (ret < 0)
+				goto error_discard;
+
+			volume->nservers++;
+		}
+	}
+
+	/* attach the cache and volume location */
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_acquire_cookie(vlocation->cache,
+			       &afs_vnode_cache_index_def,
+			       volume,
+			       &volume->cache);
+#endif
+
+	afs_get_vlocation(vlocation);
+	volume->vlocation = vlocation;
+
+	vlocation->vols[type] = volume;
+
+ success:
+	_debug("kAFS selected %s volume %08x",
+	       afs_voltypes[volume->type], volume->vid);
+	*_volume = volume;
+	ret = 0;
+
+	/* clean up */
+ error_up:
+	up_write(&cell->vl_sem);
+ error:
+	afs_put_vlocation(vlocation);
+	afs_put_cell(cell);
+
+	_leave(" = %d (%p)", ret, volume);
+	return ret;
+
+ error_discard:
+	up_write(&cell->vl_sem);
+
+	for (loop = volume->nservers - 1; loop >= 0; loop--)
+		afs_put_server(volume->servers[loop]);
+
+	kfree(volume);
+	goto error;
+} /* end afs_volume_lookup() */
+
+/*****************************************************************************/
+/*
+ * destroy a volume record
+ */
+void afs_put_volume(struct afs_volume *volume)
+{
+	struct afs_vlocation *vlocation;
+	int loop;
+
+	if (!volume)
+		return;
+
+	_enter("%p", volume);
+
+	vlocation = volume->vlocation;
+
+	/* sanity check */
+	BUG_ON(atomic_read(&volume->usage) <= 0);
+
+	/* to prevent a race, the decrement and the dequeue must be effectively
+	 * atomic */
+	down_write(&vlocation->cell->vl_sem);
+
+	if (likely(!atomic_dec_and_test(&volume->usage))) {
+		up_write(&vlocation->cell->vl_sem);
+		_leave("");
+		return;
+	}
+
+	vlocation->vols[volume->type] = NULL;
+
+	up_write(&vlocation->cell->vl_sem);
+
+	/* finish cleaning up the volume */
+#ifdef AFS_CACHING_SUPPORT
+	cachefs_relinquish_cookie(volume->cache, 0);
+#endif
+	afs_put_vlocation(vlocation);
+
+	for (loop = volume->nservers - 1; loop >= 0; loop--)
+		afs_put_server(volume->servers[loop]);
+
+	kfree(volume);
+
+	_leave(" [destroyed]");
+} /* end afs_put_volume() */
+
+/*****************************************************************************/
+/*
+ * pick a server to use to try accessing this volume
+ * - returns with an elevated usage count on the server chosen
+ */
+int afs_volume_pick_fileserver(struct afs_volume *volume,
+			       struct afs_server **_server)
+{
+	struct afs_server *server;
+	int ret, state, loop;
+
+	_enter("%s", volume->vlocation->vldb.name);
+
+	down_read(&volume->server_sem);
+
+	/* handle the no-server case */
+	if (volume->nservers == 0) {
+		ret = volume->rjservers ? -ENOMEDIUM : -ESTALE;
+		up_read(&volume->server_sem);
+		_leave(" = %d [no servers]", ret);
+		return ret;
+	}
+
+	/* basically, just search the list for the first live server and use
+	 * that */
+	ret = 0;
+	for (loop = 0; loop < volume->nservers; loop++) {
+		server = volume->servers[loop];
+		state = server->fs_state;
+
+		switch (state) {
+			/* found an apparently healthy server */
+		case 0:
+			afs_get_server(server);
+			up_read(&volume->server_sem);
+			*_server = server;
+			_leave(" = 0 (picked %08x)",
+			       ntohl(server->addr.s_addr));
+			return 0;
+
+		case -ENETUNREACH:
+			if (ret == 0)
+				ret = state;
+			break;
+
+		case -EHOSTUNREACH:
+			if (ret == 0 ||
+			    ret == -ENETUNREACH)
+				ret = state;
+			break;
+
+		case -ECONNREFUSED:
+			if (ret == 0 ||
+			    ret == -ENETUNREACH ||
+			    ret == -EHOSTUNREACH)
+				ret = state;
+			break;
+
+		default:
+		case -EREMOTEIO:
+			if (ret == 0 ||
+			    ret == -ENETUNREACH ||
+			    ret == -EHOSTUNREACH ||
+			    ret == -ECONNREFUSED)
+				ret = state;
+			break;
+		}
+	}
+
+	/* no available servers
+	 * - TODO: handle the no active servers case better
+	 */
+	up_read(&volume->server_sem);
+	_leave(" = %d", ret);
+	return ret;
+} /* end afs_volume_pick_fileserver() */
+
+/*****************************************************************************/
+/*
+ * release a server after use
+ * - releases the ref on the server struct that was acquired by picking
+ * - records result of using a particular server to access a volume
+ * - return 0 to try again, 1 if okay or to issue error
+ */
+int afs_volume_release_fileserver(struct afs_volume *volume,
+				  struct afs_server *server,
+				  int result)
+{
+	unsigned loop;
+
+	_enter("%s,%08x,%d",
+	       volume->vlocation->vldb.name, ntohl(server->addr.s_addr),
+	       result);
+
+	switch (result) {
+		/* success */
+	case 0:
+		server->fs_act_jif = jiffies;
+		break;
+
+		/* the fileserver denied all knowledge of the volume */
+	case -ENOMEDIUM:
+		server->fs_act_jif = jiffies;
+		down_write(&volume->server_sem);
+
+		/* first, find where the server is in the active list (if it
+		 * is) */
+		for (loop = 0; loop < volume->nservers; loop++)
+			if (volume->servers[loop] == server)
+				goto present;
+
+		/* no longer there - may have been discarded by another op */
+		goto try_next_server_upw;
+
+	present:
+		volume->nservers--;
+		memmove(&volume->servers[loop],
+			&volume->servers[loop + 1],
+			sizeof(volume->servers[loop]) *
+			(volume->nservers - loop));
+		volume->servers[volume->nservers] = NULL;
+		afs_put_server(server);
+		volume->rjservers++;
+
+		if (volume->nservers > 0)
+			/* another server might acknowledge its existence */
+			goto try_next_server_upw;
+
+		/* handle the case where all the fileservers have rejected the
+		 * volume
+		 * - TODO: try asking the fileservers for volume information
+		 * - TODO: contact the VL server again to see if the volume is
+		 *         no longer registered
+		 */
+		up_write(&volume->server_sem);
+		afs_put_server(server);
+		_leave(" [completely rejected]");
+		return 1;
+
+		/* problem reaching the server */
+	case -ENETUNREACH:
+	case -EHOSTUNREACH:
+	case -ECONNREFUSED:
+	case -ETIMEDOUT:
+	case -EREMOTEIO:
+		/* mark the server as dead
+		 * TODO: vary dead timeout depending on error
+		 */
+		spin_lock(&server->fs_lock);
+		if (!server->fs_state) {
+			server->fs_dead_jif = jiffies + HZ * 10;
+			server->fs_state = result;
+			printk("kAFS: SERVER DEAD state=%d\n", result);
+		}
+		spin_unlock(&server->fs_lock);
+		goto try_next_server;
+
+		/* miscellaneous error */
+	default:
+		server->fs_act_jif = jiffies;
+	case -ENOMEM:
+	case -ENONET:
+		break;
+	}
+
+	/* tell the caller to accept the result */
+	afs_put_server(server);
+	_leave("");
+	return 1;
+
+	/* tell the caller to loop around and try the next server */
+ try_next_server_upw:
+	up_write(&volume->server_sem);
+ try_next_server:
+	afs_put_server(server);
+	_leave(" [try next server]");
+	return 0;
+
+} /* end afs_volume_release_fileserver() */
+
+/*****************************************************************************/
+/*
+ * match a volume hash record stored in the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static cachefs_match_val_t afs_volume_cache_match(void *target,
+						  const void *entry)
+{
+	const struct afs_cache_vhash *vhash = entry;
+	struct afs_volume *volume = target;
+
+	_enter("{%u},{%u}", volume->type, vhash->vtype);
+
+	if (volume->type == vhash->vtype) {
+		_leave(" = SUCCESS");
+		return CACHEFS_MATCH_SUCCESS;
+	}
+
+	_leave(" = FAILED");
+	return CACHEFS_MATCH_FAILED;
+} /* end afs_volume_cache_match() */
+#endif
+
+/*****************************************************************************/
+/*
+ * update a volume hash record stored in the cache
+ */
+#ifdef AFS_CACHING_SUPPORT
+static void afs_volume_cache_update(void *source, void *entry)
+{
+	struct afs_cache_vhash *vhash = entry;
+	struct afs_volume *volume = source;
+
+	_enter("");
+
+	vhash->vtype = volume->type;
+
+} /* end afs_volume_cache_update() */
+#endif
diff --git a/fs/afs/volume.h b/fs/afs/volume.h
new file mode 100644
index 000000000000..1e691889c4c9
--- /dev/null
+++ b/fs/afs/volume.h
@@ -0,0 +1,142 @@
+/* volume.h: AFS volume management
+ *
+ * Copyright (C) 2002 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_AFS_VOLUME_H
+#define _LINUX_AFS_VOLUME_H
+
+#include "types.h"
+#include "fsclient.h"
+#include "kafstimod.h"
+#include "kafsasyncd.h"
+#include "cache.h"
+
+#define __packed __attribute__((packed))
+
+typedef enum {
+	AFS_VLUPD_SLEEP,		/* sleeping waiting for update timer to fire */
+	AFS_VLUPD_PENDING,		/* on pending queue */
+	AFS_VLUPD_INPROGRESS,		/* op in progress */
+	AFS_VLUPD_BUSYSLEEP,		/* sleeping because server returned EBUSY */
+	
+} __attribute__((packed)) afs_vlocation_upd_t;
+
+/*****************************************************************************/
+/*
+ * entry in the cached volume location catalogue
+ */
+struct afs_cache_vlocation
+{
+	uint8_t			name[64];	/* volume name (lowercase, padded with NULs) */
+	uint8_t			nservers;	/* number of entries used in servers[] */
+	uint8_t			vidmask;	/* voltype mask for vid[] */
+	uint8_t			srvtmask[8];	/* voltype masks for servers[] */
+#define AFS_VOL_VTM_RW	0x01 /* R/W version of the volume is available (on this server) */
+#define AFS_VOL_VTM_RO	0x02 /* R/O version of the volume is available (on this server) */
+#define AFS_VOL_VTM_BAK	0x04 /* backup version of the volume is available (on this server) */
+
+	afs_volid_t		vid[3];		/* volume IDs for R/W, R/O and Bak volumes */
+	struct in_addr		servers[8];	/* fileserver addresses */
+	time_t			rtime;		/* last retrieval time */
+};
+
+#ifdef AFS_CACHING_SUPPORT
+extern struct cachefs_index_def afs_vlocation_cache_index_def;
+#endif
+
+/*****************************************************************************/
+/*
+ * volume -> vnode hash table entry
+ */
+struct afs_cache_vhash
+{
+	afs_voltype_t		vtype;		/* which volume variation */
+	uint8_t			hash_bucket;	/* which hash bucket this represents */
+} __attribute__((packed));
+
+#ifdef AFS_CACHING_SUPPORT
+extern struct cachefs_index_def afs_volume_cache_index_def;
+#endif
+
+/*****************************************************************************/
+/*
+ * AFS volume location record
+ */
+struct afs_vlocation
+{
+	atomic_t		usage;
+	struct list_head	link;		/* link in cell volume location list */
+	struct afs_timer	timeout;	/* decaching timer */
+	struct afs_cell		*cell;		/* cell to which volume belongs */
+#ifdef AFS_CACHING_SUPPORT
+	struct cachefs_cookie	*cache;		/* caching cookie */
+#endif
+	struct afs_cache_vlocation vldb;	/* volume information DB record */
+	struct afs_volume	*vols[3];	/* volume access record pointer (index by type) */
+	rwlock_t		lock;		/* access lock */
+	unsigned long		read_jif;	/* time at which last read from vlserver */
+	struct afs_timer	upd_timer;	/* update timer */
+	struct afs_async_op	upd_op;		/* update operation */
+	afs_vlocation_upd_t	upd_state;	/* update state */
+	unsigned short		upd_first_svix;	/* first server index during update */
+	unsigned short		upd_curr_svix;	/* current server index during update */
+	unsigned short		upd_rej_cnt;	/* ENOMEDIUM count during update */
+	unsigned short		upd_busy_cnt;	/* EBUSY count during update */
+	unsigned short		valid;		/* T if valid */
+};
+
+extern int afs_vlocation_lookup(struct afs_cell *cell,
+				const char *name,
+				unsigned namesz,
+				struct afs_vlocation **_vlocation);
+
+#define afs_get_vlocation(V) do { atomic_inc(&(V)->usage); } while(0)
+
+extern void afs_put_vlocation(struct afs_vlocation *vlocation);
+extern void afs_vlocation_do_timeout(struct afs_vlocation *vlocation);
+
+/*****************************************************************************/
+/*
+ * AFS volume access record
+ */
+struct afs_volume
+{
+	atomic_t		usage;
+	struct afs_cell		*cell;		/* cell to which belongs (unrefd ptr) */
+	struct afs_vlocation	*vlocation;	/* volume location */
+#ifdef AFS_CACHING_SUPPORT
+	struct cachefs_cookie	*cache;		/* caching cookie */
+#endif
+	afs_volid_t		vid;		/* volume ID */
+	afs_voltype_t __packed	type;		/* type of volume */
+	char			type_force;	/* force volume type (suppress R/O -> R/W) */
+	unsigned short		nservers;	/* number of server slots filled */
+	unsigned short		rjservers;	/* number of servers discarded due to -ENOMEDIUM */
+	struct afs_server	*servers[8];	/* servers on which volume resides (ordered) */
+	struct rw_semaphore	server_sem;	/* lock for accessing current server */
+};
+
+extern int afs_volume_lookup(const char *name,
+			     struct afs_cell *cell,
+			     int rwpath,
+			     struct afs_volume **_volume);
+
+#define afs_get_volume(V) do { atomic_inc(&(V)->usage); } while(0)
+
+extern void afs_put_volume(struct afs_volume *volume);
+
+extern int afs_volume_pick_fileserver(struct afs_volume *volume,
+				      struct afs_server **_server);
+
+extern int afs_volume_release_fileserver(struct afs_volume *volume,
+					 struct afs_server *server,
+					 int result);
+
+#endif /* _LINUX_AFS_VOLUME_H */