summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--fs/afs/Makefile1
-rw-r--r--fs/afs/addr_list.c308
-rw-r--r--fs/afs/cell.c169
-rw-r--r--fs/afs/fsclient.c121
-rw-r--r--fs/afs/internal.h120
-rw-r--r--fs/afs/proc.c23
-rw-r--r--fs/afs/rxrpc.c3
-rw-r--r--fs/afs/server.c70
-rw-r--r--fs/afs/vlclient.c8
-rw-r--r--fs/afs/vlocation.c75
-rw-r--r--fs/afs/vnode.c392
-rw-r--r--fs/afs/volume.c115
12 files changed, 844 insertions, 561 deletions
diff --git a/fs/afs/Makefile b/fs/afs/Makefile
index 641148208e90..849383986d3b 100644
--- a/fs/afs/Makefile
+++ b/fs/afs/Makefile
@@ -7,6 +7,7 @@ afs-cache-$(CONFIG_AFS_FSCACHE) := cache.o
 
 kafs-objs := \
 	$(afs-cache-y) \
+	addr_list.o \
 	callback.o \
 	cell.o \
 	cmservice.o \
diff --git a/fs/afs/addr_list.c b/fs/afs/addr_list.c
new file mode 100644
index 000000000000..ecb9c72aebd2
--- /dev/null
+++ b/fs/afs/addr_list.c
@@ -0,0 +1,308 @@
+/* Server address list management
+ *
+ * Copyright (C) 2017 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 Licence
+ * as published by the Free Software Foundation; either version
+ * 2 of the Licence, or (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/dns_resolver.h>
+#include <linux/inet.h>
+#include <keys/rxrpc-type.h>
+#include "internal.h"
+#include "afs_fs.h"
+
+#define AFS_MAX_ADDRESSES						\
+	((unsigned int)((PAGE_SIZE - sizeof(struct afs_addr_list)) /	\
+			sizeof(struct sockaddr_rxrpc)))
+
+/*
+ * Release an address list.
+ */
+void afs_put_addrlist(struct afs_addr_list *alist)
+{
+	if (alist && refcount_dec_and_test(&alist->usage))
+		call_rcu(&alist->rcu, (rcu_callback_t)kfree);
+}
+
+/*
+ * Allocate an address list.
+ */
+struct afs_addr_list *afs_alloc_addrlist(unsigned int nr,
+					 unsigned short service,
+					 unsigned short port)
+{
+	struct afs_addr_list *alist;
+	unsigned int i;
+
+	_enter("%u,%u,%u", nr, service, port);
+
+	alist = kzalloc(sizeof(*alist) + sizeof(alist->addrs[0]) * nr,
+			GFP_KERNEL);
+	if (!alist)
+		return NULL;
+
+	refcount_set(&alist->usage, 1);
+
+	for (i = 0; i < nr; i++) {
+		struct sockaddr_rxrpc *srx = &alist->addrs[i];
+		srx->srx_family			= AF_RXRPC;
+		srx->srx_service		= service;
+		srx->transport_type		= SOCK_DGRAM;
+		srx->transport_len		= sizeof(srx->transport.sin6);
+		srx->transport.sin6.sin6_family	= AF_INET6;
+		srx->transport.sin6.sin6_port	= htons(port);
+	}
+
+	return alist;
+}
+
+/*
+ * Parse a text string consisting of delimited addresses.
+ */
+struct afs_addr_list *afs_parse_text_addrs(const char *text, size_t len,
+					   char delim,
+					   unsigned short service,
+					   unsigned short port)
+{
+	struct afs_addr_list *alist;
+	const char *p, *end = text + len;
+	unsigned int nr = 0;
+
+	_enter("%*.*s,%c", (int)len, (int)len, text, delim);
+
+	if (!len)
+		return ERR_PTR(-EDESTADDRREQ);
+
+	if (delim == ':' && (memchr(text, ',', len) || !memchr(text, '.', len)))
+		delim = ',';
+
+	/* Count the addresses */
+	p = text;
+	do {
+		if (!*p)
+			return ERR_PTR(-EINVAL);
+		if (*p == delim)
+			continue;
+		nr++;
+		if (*p == '[') {
+			p++;
+			if (p == end)
+				return ERR_PTR(-EINVAL);
+			p = memchr(p, ']', end - p);
+			if (!p)
+				return ERR_PTR(-EINVAL);
+			p++;
+			if (p >= end)
+				break;
+		}
+
+		p = memchr(p, delim, end - p);
+		if (!p)
+			break;
+		p++;
+	} while (p < end);
+
+	_debug("%u/%u addresses", nr, AFS_MAX_ADDRESSES);
+	if (nr > AFS_MAX_ADDRESSES)
+		nr = AFS_MAX_ADDRESSES;
+
+	alist = afs_alloc_addrlist(nr, service, port);
+	if (!alist)
+		return ERR_PTR(-ENOMEM);
+
+	/* Extract the addresses */
+	p = text;
+	do {
+		struct sockaddr_rxrpc *srx = &alist->addrs[alist->nr_addrs];
+		char tdelim = delim;
+
+		if (*p == delim) {
+			p++;
+			continue;
+		}
+
+		if (*p == '[') {
+			p++;
+			tdelim = ']';
+		}
+
+		if (in4_pton(p, end - p,
+			     (u8 *)&srx->transport.sin6.sin6_addr.s6_addr32[3],
+			     tdelim, &p)) {
+			srx->transport.sin6.sin6_addr.s6_addr32[0] = 0;
+			srx->transport.sin6.sin6_addr.s6_addr32[1] = 0;
+			srx->transport.sin6.sin6_addr.s6_addr32[2] = htonl(0xffff);
+		} else if (in6_pton(p, end - p,
+				    srx->transport.sin6.sin6_addr.s6_addr,
+				    tdelim, &p)) {
+			/* Nothing to do */
+		} else {
+			goto bad_address;
+		}
+
+		if (tdelim == ']') {
+			if (p == end || *p != ']')
+				goto bad_address;
+			p++;
+		}
+
+		if (p < end) {
+			if (*p == '+') {
+				/* Port number specification "+1234" */
+				unsigned int xport = 0;
+				p++;
+				if (p >= end || !isdigit(*p))
+					goto bad_address;
+				do {
+					xport *= 10;
+					xport += *p - '0';
+					if (xport > 65535)
+						goto bad_address;
+					p++;
+				} while (p < end && isdigit(*p));
+				srx->transport.sin6.sin6_port = htons(xport);
+			} else if (*p == delim) {
+				p++;
+			} else {
+				goto bad_address;
+			}
+		}
+
+		alist->nr_addrs++;
+	} while (p < end && alist->nr_addrs < AFS_MAX_ADDRESSES);
+
+	_leave(" = [nr %u]", alist->nr_addrs);
+	return alist;
+
+bad_address:
+	kfree(alist);
+	return ERR_PTR(-EINVAL);
+}
+
+/*
+ * Compare old and new address lists to see if there's been any change.
+ * - How to do this in better than O(Nlog(N)) time?
+ *   - We don't really want to sort the address list, but would rather take the
+ *     list as we got it so as not to undo record rotation by the DNS server.
+ */
+#if 0
+static int afs_cmp_addr_list(const struct afs_addr_list *a1,
+			     const struct afs_addr_list *a2)
+{
+}
+#endif
+
+/*
+ * Perform a DNS query for VL servers and build a up an address list.
+ */
+struct afs_addr_list *afs_dns_query(struct afs_cell *cell, time64_t *_expiry)
+{
+	struct afs_addr_list *alist;
+	char *vllist = NULL;
+	int ret;
+
+	_enter("%s", cell->name);
+
+	ret = dns_query("afsdb", cell->name, cell->name_len,
+			"ipv4", &vllist, _expiry);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	alist = afs_parse_text_addrs(vllist, strlen(vllist), ',',
+				     VL_SERVICE, AFS_VL_PORT);
+	if (IS_ERR(alist)) {
+		kfree(vllist);
+		if (alist != ERR_PTR(-ENOMEM))
+			pr_err("Failed to parse DNS data\n");
+		return alist;
+	}
+
+	kfree(vllist);
+	return alist;
+}
+
+/*
+ * Get an address to try.
+ */
+bool afs_iterate_addresses(struct afs_addr_cursor *ac)
+{
+	_enter("%hu+%hd", ac->start, (short)ac->index);
+
+	if (!ac->alist)
+		return false;
+
+	if (ac->begun) {
+		ac->index++;
+		if (ac->index == ac->alist->nr_addrs)
+			ac->index = 0;
+
+		if (ac->index == ac->start) {
+			ac->error = -EDESTADDRREQ;
+			return false;
+		}
+	}
+
+	ac->begun = true;
+	ac->responded = false;
+	ac->addr = &ac->alist->addrs[ac->index];
+	return true;
+}
+
+/*
+ * Release an address list cursor.
+ */
+int afs_end_cursor(struct afs_addr_cursor *ac)
+{
+	if (ac->responded && ac->index != ac->start)
+		WRITE_ONCE(ac->alist->index, ac->index);
+
+	afs_put_addrlist(ac->alist);
+	ac->alist = NULL;
+	return ac->error;
+}
+
+/*
+ * Set the address cursor for iterating over VL servers.
+ */
+int afs_set_vl_cursor(struct afs_addr_cursor *ac, struct afs_cell *cell)
+{
+	struct afs_addr_list *alist;
+	int ret;
+
+	if (!rcu_access_pointer(cell->vl_addrs)) {
+		ret = wait_on_bit(&cell->flags, AFS_CELL_FL_NO_LOOKUP_YET,
+				  TASK_INTERRUPTIBLE);
+		if (ret < 0)
+			return ret;
+
+		if (!rcu_access_pointer(cell->vl_addrs) &&
+		    ktime_get_real_seconds() < cell->dns_expiry)
+			return cell->error;
+	}
+
+	read_lock(&cell->vl_addrs_lock);
+	alist = rcu_dereference_protected(cell->vl_addrs,
+					  lockdep_is_held(&cell->vl_addrs_lock));
+	if (alist->nr_addrs > 0)
+		afs_get_addrlist(alist);
+	else
+		alist = NULL;
+	read_unlock(&cell->vl_addrs_lock);
+
+	if (!alist)
+		return -EDESTADDRREQ;
+
+	ac->alist = alist;
+	ac->addr = NULL;
+	ac->start = READ_ONCE(alist->index);
+	ac->index = ac->start;
+	ac->error = 0;
+	ac->begun = false;
+	return 0;
+}
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index e83103e8a6fb..a0e08d3a108c 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -9,7 +9,6 @@
  * 2 of the License, or (at your option) any later version.
  */
 
-#include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/key.h>
 #include <linux/ctype.h>
@@ -152,68 +151,33 @@ static struct afs_cell *afs_alloc_cell(struct afs_net *net,
 	init_rwsem(&cell->vl_sem);
 	INIT_LIST_HEAD(&cell->vl_list);
 	spin_lock_init(&cell->vl_lock);
-	seqlock_init(&cell->vl_addrs_lock);
-	cell->flags = (1 << AFS_CELL_FL_NOT_READY);
-
-	for (i = 0; i < AFS_CELL_MAX_ADDRS; i++) {
-		struct sockaddr_rxrpc *srx = &cell->vl_addrs[i];
-		srx->srx_family			= AF_RXRPC;
-		srx->srx_service		= VL_SERVICE;
-		srx->transport_type		= SOCK_DGRAM;
-		srx->transport.sin6.sin6_family	= AF_INET6;
-		srx->transport.sin6.sin6_port	= htons(AFS_VL_PORT);
-	}
+	cell->flags = ((1 << AFS_CELL_FL_NOT_READY) |
+		       (1 << AFS_CELL_FL_NO_LOOKUP_YET));
+	rwlock_init(&cell->vl_addrs_lock);
 
 	/* Fill in the VL server list if we were given a list of addresses to
 	 * use.
 	 */
 	if (vllist) {
-		char delim = ':';
-
-		if (strchr(vllist, ',') || !strchr(vllist, '.'))
-			delim = ',';
-
-		do {
-			struct sockaddr_rxrpc *srx = &cell->vl_addrs[cell->vl_naddrs];
-
-			if (in4_pton(vllist, -1,
-				     (u8 *)&srx->transport.sin6.sin6_addr.s6_addr32[3],
-				     delim, &vllist)) {
-				srx->transport_len = sizeof(struct sockaddr_in6);
-				srx->transport.sin6.sin6_addr.s6_addr32[0] = 0;
-				srx->transport.sin6.sin6_addr.s6_addr32[1] = 0;
-				srx->transport.sin6.sin6_addr.s6_addr32[2] = htonl(0xffff);
-			} else if (in6_pton(vllist, -1,
-					    srx->transport.sin6.sin6_addr.s6_addr,
-					    delim, &vllist)) {
-				srx->transport_len = sizeof(struct sockaddr_in6);
-				srx->transport.sin6.sin6_family	= AF_INET6;
-			} else {
-				goto bad_address;
-			}
+		struct afs_addr_list *alist;
 
-			cell->vl_naddrs++;
-			if (!*vllist)
-				break;
-			vllist++;
-
-		} while (cell->vl_naddrs < AFS_CELL_MAX_ADDRS && vllist);
+		alist = afs_parse_text_addrs(vllist, strlen(vllist), ':',
+					     VL_SERVICE, AFS_VL_PORT);
+		if (IS_ERR(alist)) {
+			ret = PTR_ERR(alist);
+			goto parse_failed;
+		}
 
-		/* Disable DNS refresh for manually-specified cells */
+		rcu_assign_pointer(cell->vl_addrs, alist);
 		cell->dns_expiry = TIME64_MAX;
-	} else {
-		/* We're going to need to 'refresh' this cell's VL server list
-		 * from the DNS before we can use it.
-		 */
-		cell->dns_expiry = S64_MIN;
 	}
 
 	_leave(" = %p", cell);
 	return cell;
 
-bad_address:
-	printk(KERN_ERR "kAFS: bad VL server IP address\n");
-	ret = -EINVAL;
+parse_failed:
+	if (ret == -EINVAL)
+		printk(KERN_ERR "kAFS: bad VL server IP address\n");
 	kfree(cell);
 	_leave(" = %d", ret);
 	return ERR_PTR(ret);
@@ -325,7 +289,6 @@ cell_already_exists:
 	if (excl) {
 		ret = -EEXIST;
 	} else {
-		ASSERTCMP(atomic_read(&cursor->usage), >=, 1);
 		afs_get_cell(cursor);
 		ret = 0;
 	}
@@ -333,8 +296,10 @@ cell_already_exists:
 	kfree(candidate);
 	if (ret == 0)
 		goto wait_for_cell;
+	goto error_noput;
 error:
 	afs_put_cell(net, cell);
+error_noput:
 	_leave(" = %d [error]", ret);
 	return ERR_PTR(ret);
 }
@@ -396,78 +361,50 @@ int afs_cell_init(struct afs_net *net, const char *rootcell)
  */
 static void afs_update_cell(struct afs_cell *cell)
 {
+	struct afs_addr_list *alist, *old;
 	time64_t now, expiry;
-	char *vllist = NULL;
-	int ret;
 
 	_enter("%s", cell->name);
 
-	ret = dns_query("afsdb", cell->name, cell->name_len,
-			"ipv4", &vllist, &expiry);
-	_debug("query %d", ret);
-	switch (ret) {
-	case 0 ... INT_MAX:
-		clear_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags);
-		clear_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags);
-		goto parse_dns_data;
+	alist = afs_dns_query(cell, &expiry);
+	if (IS_ERR(alist)) {
+		switch (PTR_ERR(alist)) {
+		case -ENODATA:
+			/* The DNS said that the cell does not exist */
+			set_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags);
+			clear_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags);
+			cell->dns_expiry = ktime_get_real_seconds() + 61;
+			break;
 
-	case -ENODATA:
-		clear_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags);
-		set_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags);
-		cell->dns_expiry = ktime_get_real_seconds() + 61;
-		cell->error = -EDESTADDRREQ;
-		goto out;
+		case -EAGAIN:
+		case -ECONNREFUSED:
+		default:
+			set_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags);
+			cell->dns_expiry = ktime_get_real_seconds() + 10;
+			break;
+		}
 
-	case -EAGAIN:
-	case -ECONNREFUSED:
-	default:
-		/* Unable to query DNS. */
-		set_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags);
-		cell->dns_expiry = ktime_get_real_seconds() + 10;
 		cell->error = -EDESTADDRREQ;
-		goto out;
-	}
-
-parse_dns_data:
-	write_seqlock(&cell->vl_addrs_lock);
-
-	ret = -EINVAL;
-	do {
-		struct sockaddr_rxrpc *srx = &cell->vl_addrs[cell->vl_naddrs];
-
-		if (in4_pton(vllist, -1,
-			     (u8 *)&srx->transport.sin6.sin6_addr.s6_addr32[3],
-			     ',', (const char **)&vllist)) {
-			srx->transport_len = sizeof(struct sockaddr_in6);
-			srx->transport.sin6.sin6_addr.s6_addr32[0] = 0;
-			srx->transport.sin6.sin6_addr.s6_addr32[1] = 0;
-			srx->transport.sin6.sin6_addr.s6_addr32[2] = htonl(0xffff);
-		} else if (in6_pton(vllist, -1,
-				    srx->transport.sin6.sin6_addr.s6_addr,
-				    ',', (const char **)&vllist)) {
-			srx->transport_len = sizeof(struct sockaddr_in6);
-			srx->transport.sin6.sin6_family	= AF_INET6;
-		} else {
-			goto bad_address;
-		}
+	} else {
+		clear_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags);
+		clear_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags);
 
-		cell->vl_naddrs++;
-		if (!*vllist)
-			break;
-		vllist++;
+		/* Exclusion on changing vl_addrs is achieved by a
+		 * non-reentrant work item.
+		 */
+		old = rcu_dereference_protected(cell->vl_addrs, true);
+		rcu_assign_pointer(cell->vl_addrs, alist);
+		cell->dns_expiry = expiry;
 
-	} while (cell->vl_naddrs < AFS_CELL_MAX_ADDRS);
+		if (old)
+			afs_put_addrlist(old);
+	}
 
-	if (cell->vl_naddrs < AFS_CELL_MAX_ADDRS)
-		memset(cell->vl_addrs + cell->vl_naddrs, 0,
-		       (AFS_CELL_MAX_ADDRS - cell->vl_naddrs) * sizeof(cell->vl_addrs[0]));
+	if (test_and_clear_bit(AFS_CELL_FL_NO_LOOKUP_YET, &cell->flags))
+		wake_up_bit(&cell->flags, AFS_CELL_FL_NO_LOOKUP_YET);
 
 	now = ktime_get_real_seconds();
-	cell->dns_expiry = expiry;
-	afs_set_cell_timer(cell->net, expiry - now);
-bad_address:
-	write_sequnlock(&cell->vl_addrs_lock);
-out:
+	afs_set_cell_timer(cell->net, cell->dns_expiry - now);
 	_leave("");
 }
 
@@ -482,6 +419,7 @@ static void afs_cell_destroy(struct rcu_head *rcu)
 
 	ASSERTCMP(atomic_read(&cell->usage), ==, 0);
 
+	afs_put_addrlist(cell->vl_addrs);
 	key_put(cell->anonymous_key);
 	kfree(cell);
 
@@ -515,6 +453,15 @@ void afs_cells_timer(struct timer_list *timer)
 }
 
 /*
+ * Get a reference on a cell record.
+ */
+struct afs_cell *afs_get_cell(struct afs_cell *cell)
+{
+	atomic_inc(&cell->usage);
+	return cell;
+}
+
+/*
  * Drop a reference on a cell record.
  */
 void afs_put_cell(struct afs_net *net, struct afs_cell *cell)
diff --git a/fs/afs/fsclient.c b/fs/afs/fsclient.c
index 680c02d510f7..6614d0a78daa 100644
--- a/fs/afs/fsclient.c
+++ b/fs/afs/fsclient.c
@@ -297,7 +297,7 @@ static const struct afs_call_type afs_RXFSFetchStatus = {
 /*
  * fetch the status information for a file
  */
-int afs_fs_fetch_file_status(struct afs_server *server,
+int afs_fs_fetch_file_status(struct afs_fs_cursor *fc,
 			     struct key *key,
 			     struct afs_vnode *vnode,
 			     struct afs_volsync *volsync,
@@ -325,9 +325,9 @@ int afs_fs_fetch_file_status(struct afs_server *server,
 	bp[2] = htonl(vnode->fid.vnode);
 	bp[3] = htonl(vnode->fid.unique);
 
-	call->cb_break = vnode->cb_break + server->cb_s_break;
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	call->cb_break = vnode->cb_break + fc->server->cb_s_break;
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -502,7 +502,7 @@ static const struct afs_call_type afs_RXFSFetchData64 = {
 /*
  * fetch data from a very large file
  */
-static int afs_fs_fetch_data64(struct afs_server *server,
+static int afs_fs_fetch_data64(struct afs_fs_cursor *fc,
 			       struct key *key,
 			       struct afs_vnode *vnode,
 			       struct afs_read *req,
@@ -536,15 +536,15 @@ static int afs_fs_fetch_data64(struct afs_server *server,
 	bp[7] = htonl(lower_32_bits(req->len));
 
 	atomic_inc(&req->usage);
-	call->cb_break = vnode->cb_break + server->cb_s_break;
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	call->cb_break = vnode->cb_break + fc->server->cb_s_break;
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
  * fetch data from a file
  */
-int afs_fs_fetch_data(struct afs_server *server,
+int afs_fs_fetch_data(struct afs_fs_cursor *fc,
 		      struct key *key,
 		      struct afs_vnode *vnode,
 		      struct afs_read *req,
@@ -557,7 +557,7 @@ int afs_fs_fetch_data(struct afs_server *server,
 	if (upper_32_bits(req->pos) ||
 	    upper_32_bits(req->len) ||
 	    upper_32_bits(req->pos + req->len))
-		return afs_fs_fetch_data64(server, key, vnode, req, async);
+		return afs_fs_fetch_data64(fc, key, vnode, req, async);
 
 	_enter("");
 
@@ -581,9 +581,9 @@ int afs_fs_fetch_data(struct afs_server *server,
 	bp[5] = htonl(lower_32_bits(req->len));
 
 	atomic_inc(&req->usage);
-	call->cb_break = vnode->cb_break + server->cb_s_break;
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	call->cb_break = vnode->cb_break + fc->server->cb_s_break;
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -625,7 +625,7 @@ static const struct afs_call_type afs_RXFSCreateXXXX = {
 /*
  * create a file or make a directory
  */
-int afs_fs_create(struct afs_server *server,
+int afs_fs_create(struct afs_fs_cursor *fc,
 		  struct key *key,
 		  struct afs_vnode *vnode,
 		  const char *name,
@@ -677,8 +677,8 @@ int afs_fs_create(struct afs_server *server,
 	*bp++ = htonl(mode & S_IALLUGO); /* unix mode */
 	*bp++ = 0; /* segment size */
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -717,7 +717,7 @@ static const struct afs_call_type afs_RXFSRemoveXXXX = {
 /*
  * remove a file or directory
  */
-int afs_fs_remove(struct afs_server *server,
+int afs_fs_remove(struct afs_fs_cursor *fc,
 		  struct key *key,
 		  struct afs_vnode *vnode,
 		  const char *name,
@@ -756,8 +756,8 @@ int afs_fs_remove(struct afs_server *server,
 		bp = (void *) bp + padsz;
 	}
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -797,7 +797,7 @@ static const struct afs_call_type afs_RXFSLink = {
 /*
  * make a hard link
  */
-int afs_fs_link(struct afs_server *server,
+int afs_fs_link(struct afs_fs_cursor *fc,
 		struct key *key,
 		struct afs_vnode *dvnode,
 		struct afs_vnode *vnode,
@@ -840,8 +840,8 @@ int afs_fs_link(struct afs_server *server,
 	*bp++ = htonl(vnode->fid.vnode);
 	*bp++ = htonl(vnode->fid.unique);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -882,7 +882,7 @@ static const struct afs_call_type afs_RXFSSymlink = {
 /*
  * create a symbolic link
  */
-int afs_fs_symlink(struct afs_server *server,
+int afs_fs_symlink(struct afs_fs_cursor *fc,
 		   struct key *key,
 		   struct afs_vnode *vnode,
 		   const char *name,
@@ -943,8 +943,8 @@ int afs_fs_symlink(struct afs_server *server,
 	*bp++ = htonl(S_IRWXUGO); /* unix mode */
 	*bp++ = 0; /* segment size */
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -986,7 +986,7 @@ static const struct afs_call_type afs_RXFSRename = {
 /*
  * create a symbolic link
  */
-int afs_fs_rename(struct afs_server *server,
+int afs_fs_rename(struct afs_fs_cursor *fc,
 		  struct key *key,
 		  struct afs_vnode *orig_dvnode,
 		  const char *orig_name,
@@ -1045,8 +1045,8 @@ int afs_fs_rename(struct afs_server *server,
 		bp = (void *) bp + n_padsz;
 	}
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -1094,7 +1094,7 @@ static const struct afs_call_type afs_RXFSStoreData64 = {
 /*
  * store a set of pages to a very large file
  */
-static int afs_fs_store_data64(struct afs_server *server,
+static int afs_fs_store_data64(struct afs_fs_cursor *fc,
 			       struct afs_writeback *wb,
 			       pgoff_t first, pgoff_t last,
 			       unsigned offset, unsigned to,
@@ -1147,14 +1147,14 @@ static int afs_fs_store_data64(struct afs_server *server,
 	*bp++ = htonl(i_size >> 32);
 	*bp++ = htonl((u32) i_size);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
  * store a set of pages
  */
-int afs_fs_store_data(struct afs_server *server, struct afs_writeback *wb,
+int afs_fs_store_data(struct afs_fs_cursor *fc, struct afs_writeback *wb,
 		      pgoff_t first, pgoff_t last,
 		      unsigned offset, unsigned to,
 		      bool async)
@@ -1183,7 +1183,7 @@ int afs_fs_store_data(struct afs_server *server, struct afs_writeback *wb,
 	       (unsigned long long) i_size);
 
 	if (pos >> 32 || i_size >> 32 || size >> 32 || (pos + size) >> 32)
-		return afs_fs_store_data64(server, wb, first, last, offset, to,
+		return afs_fs_store_data64(fc, wb, first, last, offset, to,
 					   size, pos, i_size, async);
 
 	call = afs_alloc_flat_call(net, &afs_RXFSStoreData,
@@ -1221,8 +1221,8 @@ int afs_fs_store_data(struct afs_server *server, struct afs_writeback *wb,
 	*bp++ = htonl(size);
 	*bp++ = htonl(i_size);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -1279,7 +1279,7 @@ static const struct afs_call_type afs_RXFSStoreData64_as_Status = {
  * set the attributes on a very large file, using FS.StoreData rather than
  * FS.StoreStatus so as to alter the file size also
  */
-static int afs_fs_setattr_size64(struct afs_server *server, struct key *key,
+static int afs_fs_setattr_size64(struct afs_fs_cursor *fc, struct key *key,
 				 struct afs_vnode *vnode, struct iattr *attr,
 				 bool async)
 {
@@ -1319,15 +1319,15 @@ static int afs_fs_setattr_size64(struct afs_server *server, struct key *key,
 	*bp++ = htonl(attr->ia_size >> 32);	/* new file length */
 	*bp++ = htonl((u32) attr->ia_size);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
  * set the attributes on a file, using FS.StoreData rather than FS.StoreStatus
  * so as to alter the file size also
  */
-static int afs_fs_setattr_size(struct afs_server *server, struct key *key,
+static int afs_fs_setattr_size(struct afs_fs_cursor *fc, struct key *key,
 			       struct afs_vnode *vnode, struct iattr *attr,
 			       bool async)
 {
@@ -1340,8 +1340,7 @@ static int afs_fs_setattr_size(struct afs_server *server, struct key *key,
 
 	ASSERT(attr->ia_valid & ATTR_SIZE);
 	if (attr->ia_size >> 32)
-		return afs_fs_setattr_size64(server, key, vnode, attr,
-					     async);
+		return afs_fs_setattr_size64(fc, key, vnode, attr, async);
 
 	call = afs_alloc_flat_call(net, &afs_RXFSStoreData_as_Status,
 				   (4 + 6 + 3) * 4,
@@ -1367,15 +1366,15 @@ static int afs_fs_setattr_size(struct afs_server *server, struct key *key,
 	*bp++ = 0;				/* size of write */
 	*bp++ = htonl(attr->ia_size);		/* new file length */
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
  * set the attributes on a file, using FS.StoreData if there's a change in file
  * size, and FS.StoreStatus otherwise
  */
-int afs_fs_setattr(struct afs_server *server, struct key *key,
+int afs_fs_setattr(struct afs_fs_cursor *fc, struct key *key,
 		   struct afs_vnode *vnode, struct iattr *attr,
 		   bool async)
 {
@@ -1384,8 +1383,7 @@ int afs_fs_setattr(struct afs_server *server, struct key *key,
 	__be32 *bp;
 
 	if (attr->ia_valid & ATTR_SIZE)
-		return afs_fs_setattr_size(server, key, vnode, attr,
-					   async);
+		return afs_fs_setattr_size(fc, key, vnode, attr, async);
 
 	_enter(",%x,{%x:%u},,",
 	       key_serial(key), vnode->fid.vid, vnode->fid.vnode);
@@ -1409,8 +1407,8 @@ int afs_fs_setattr(struct afs_server *server, struct key *key,
 
 	xdr_encode_AFS_StoreStatus(&bp, attr);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -1607,7 +1605,7 @@ static const struct afs_call_type afs_RXFSGetVolumeStatus = {
 /*
  * fetch the status of a volume
  */
-int afs_fs_get_volume_status(struct afs_server *server,
+int afs_fs_get_volume_status(struct afs_fs_cursor *fc,
 			     struct key *key,
 			     struct afs_vnode *vnode,
 			     struct afs_volume_status *vs,
@@ -1640,8 +1638,8 @@ int afs_fs_get_volume_status(struct afs_server *server,
 	bp[0] = htonl(FSGETVOLUMESTATUS);
 	bp[1] = htonl(vnode->fid.vid);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -1696,7 +1694,7 @@ static const struct afs_call_type afs_RXFSReleaseLock = {
 /*
  * get a lock on a file
  */
-int afs_fs_set_lock(struct afs_server *server,
+int afs_fs_set_lock(struct afs_fs_cursor *fc,
 		    struct key *key,
 		    struct afs_vnode *vnode,
 		    afs_lock_type_t type,
@@ -1723,14 +1721,14 @@ int afs_fs_set_lock(struct afs_server *server,
 	*bp++ = htonl(vnode->fid.unique);
 	*bp++ = htonl(type);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
  * extend a lock on a file
  */
-int afs_fs_extend_lock(struct afs_server *server,
+int afs_fs_extend_lock(struct afs_fs_cursor *fc,
 		       struct key *key,
 		       struct afs_vnode *vnode,
 		       bool async)
@@ -1755,14 +1753,14 @@ int afs_fs_extend_lock(struct afs_server *server,
 	*bp++ = htonl(vnode->fid.vnode);
 	*bp++ = htonl(vnode->fid.unique);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
  * release a lock on a file
  */
-int afs_fs_release_lock(struct afs_server *server,
+int afs_fs_release_lock(struct afs_fs_cursor *fc,
 			struct key *key,
 			struct afs_vnode *vnode,
 			bool async)
@@ -1787,8 +1785,8 @@ int afs_fs_release_lock(struct afs_server *server,
 	*bp++ = htonl(vnode->fid.vnode);
 	*bp++ = htonl(vnode->fid.unique);
 
-	afs_use_fs_server(call, server);
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	afs_use_fs_server(call, fc->server);
+	return afs_make_call(&fc->ac, call, GFP_NOFS, async);
 }
 
 /*
@@ -1812,6 +1810,7 @@ static const struct afs_call_type afs_RXFSGiveUpAllCallBacks = {
  * Flush all the callbacks we have on a server.
  */
 int afs_fs_give_up_all_callbacks(struct afs_server *server,
+				 struct afs_addr_cursor *ac,
 				 struct key *key,
 				 bool async)
 {
@@ -1831,5 +1830,5 @@ int afs_fs_give_up_all_callbacks(struct afs_server *server,
 	*bp++ = htonl(FSGIVEUPALLCALLBACKS);
 
 	/* Can't take a ref on server */
-	return afs_make_call(&server->addr, call, GFP_NOFS, async);
+	return afs_make_call(ac, call, GFP_NOFS, async);
 }
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 51e3825b5ffb..df52bf18a263 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -71,6 +71,17 @@ enum afs_call_state {
 };
 
 /*
+ * List of server addresses.
+ */
+struct afs_addr_list {
+	struct rcu_head		rcu;		/* Must be first */
+	refcount_t		usage;
+	unsigned short		nr_addrs;
+	unsigned short		index;		/* Address currently in use */
+	struct sockaddr_rxrpc	addrs[];
+};
+
+/*
  * a record of an in-progress RxRPC call
  */
 struct afs_call {
@@ -283,16 +294,15 @@ struct afs_cell {
 #define AFS_CELL_FL_NO_GC	1		/* The cell was added manually, don't auto-gc */
 #define AFS_CELL_FL_NOT_FOUND	2		/* Permanent DNS error */
 #define AFS_CELL_FL_DNS_FAIL	3		/* Failed to access DNS */
+#define AFS_CELL_FL_NO_LOOKUP_YET 4		/* Not completed first DNS lookup yet */
 	enum afs_cell_state	state;
 	short			error;
 
 	spinlock_t		vl_lock;	/* vl_list lock */
 
 	/* VLDB server list. */
-	seqlock_t		vl_addrs_lock;
-	unsigned short		vl_naddrs;	/* number of VL servers in addr list */
-	unsigned short		vl_curr_svix;	/* current server index */
-	struct sockaddr_rxrpc	vl_addrs[AFS_CELL_MAX_ADDRS];	/* cell VL server addresses */
+	rwlock_t		vl_addrs_lock;	/* Lock on vl_addrs */
+	struct afs_addr_list	__rcu *vl_addrs; /* List of VL servers */
 	u8			name_len;	/* Length of name */
 	char			name[64 + 1];	/* Cell name, case-flattened and NUL-padded */
 };
@@ -343,7 +353,7 @@ struct afs_vlocation {
 struct afs_server {
 	atomic_t		usage;
 	time64_t		time_of_death;	/* time at which put reduced usage to 0 */
-	struct sockaddr_rxrpc	addr;		/* server address */
+	struct afs_addr_list	__rcu *addrs;	/* List of addresses for this server */
 	struct afs_net		*net;		/* Network namespace in which the server resides */
 	struct afs_cell		*cell;		/* cell in which server resides */
 	struct list_head	link;		/* link in cell's server list */
@@ -485,8 +495,49 @@ struct afs_interface {
 	unsigned	mtu;		/* MTU of interface */
 };
 
+/*
+ * Cursor for iterating over a server's address list.
+ */
+struct afs_addr_cursor {
+	struct afs_addr_list	*alist;		/* Current address list (pins ref) */
+	struct sockaddr_rxrpc	*addr;
+	unsigned short		start;		/* Starting point in alist->addrs[] */
+	unsigned short		index;		/* Wrapping offset from start to current addr */
+	short			error;
+	bool			begun;		/* T if we've begun iteration */
+	bool			responded;	/* T if the current address responded */
+};
+
+/*
+ * Cursor for iterating over a set of fileservers.
+ */
+struct afs_fs_cursor {
+	struct afs_addr_cursor	ac;
+	struct afs_server	*server;	/* Current server (pins ref) */
+};
+
 /*****************************************************************************/
 /*
+ * addr_list.c
+ */
+static inline struct afs_addr_list *afs_get_addrlist(struct afs_addr_list *alist)
+{
+	if (alist)
+		refcount_inc(&alist->usage);
+	return alist;
+}
+extern struct afs_addr_list *afs_alloc_addrlist(unsigned int,
+						unsigned short,
+						unsigned short);
+extern void afs_put_addrlist(struct afs_addr_list *);
+extern struct afs_addr_list *afs_parse_text_addrs(const char *, size_t, char,
+						  unsigned short, unsigned short);
+extern struct afs_addr_list *afs_dns_query(struct afs_cell *, time64_t *);
+extern bool afs_iterate_addresses(struct afs_addr_cursor *);
+extern int afs_end_cursor(struct afs_addr_cursor *);
+extern int afs_set_vl_cursor(struct afs_addr_cursor *, struct afs_cell *);
+
+/*
  * cache.c
  */
 #ifdef CONFIG_AFS_FSCACHE
@@ -521,17 +572,11 @@ static inline struct afs_cb_interest *afs_get_cb_interest(struct afs_cb_interest
 /*
  * cell.c
  */
- static inline struct afs_cell *afs_get_cell(struct afs_cell *cell)
-{
-	if (cell)
-		atomic_inc(&cell->usage);
-	return cell;
-}
-
 extern int afs_cell_init(struct afs_net *, const char *);
 extern struct afs_cell *afs_lookup_cell_rcu(struct afs_net *, const char *, unsigned);
 extern struct afs_cell *afs_lookup_cell(struct afs_net *, const char *, unsigned,
 					const char *, bool);
+extern struct afs_cell *afs_get_cell(struct afs_cell *);
 extern void afs_put_cell(struct afs_net *, struct afs_cell *);
 extern void afs_manage_cells(struct work_struct *);
 extern void afs_cells_timer(struct timer_list *);
@@ -574,40 +619,41 @@ extern int afs_flock(struct file *, int, struct file_lock *);
 /*
  * fsclient.c
  */
-extern int afs_fs_fetch_file_status(struct afs_server *, struct key *,
+extern int afs_fs_fetch_file_status(struct afs_fs_cursor *, struct key *,
 				    struct afs_vnode *, struct afs_volsync *,
 				    bool);
 extern int afs_fs_give_up_callbacks(struct afs_net *, struct afs_server *, bool);
-extern int afs_fs_fetch_data(struct afs_server *, struct key *,
+extern int afs_fs_fetch_data(struct afs_fs_cursor *, struct key *,
 			     struct afs_vnode *, struct afs_read *, bool);
-extern int afs_fs_create(struct afs_server *, struct key *,
+extern int afs_fs_create(struct afs_fs_cursor *, struct key *,
 			 struct afs_vnode *, const char *, umode_t,
 			 struct afs_fid *, struct afs_file_status *,
 			 struct afs_callback *, bool);
-extern int afs_fs_remove(struct afs_server *, struct key *,
+extern int afs_fs_remove(struct afs_fs_cursor *, struct key *,
 			 struct afs_vnode *, const char *, bool, bool);
-extern int afs_fs_link(struct afs_server *, struct key *, struct afs_vnode *,
+extern int afs_fs_link(struct afs_fs_cursor *, struct key *, struct afs_vnode *,
 		       struct afs_vnode *, const char *, bool);
-extern int afs_fs_symlink(struct afs_server *, struct key *,
+extern int afs_fs_symlink(struct afs_fs_cursor *, struct key *,
 			  struct afs_vnode *, const char *, const char *,
 			  struct afs_fid *, struct afs_file_status *, bool);
-extern int afs_fs_rename(struct afs_server *, struct key *,
+extern int afs_fs_rename(struct afs_fs_cursor *, struct key *,
 			 struct afs_vnode *, const char *,
 			 struct afs_vnode *, const char *, bool);
-extern int afs_fs_store_data(struct afs_server *, struct afs_writeback *,
+extern int afs_fs_store_data(struct afs_fs_cursor *, struct afs_writeback *,
 			     pgoff_t, pgoff_t, unsigned, unsigned, bool);
-extern int afs_fs_setattr(struct afs_server *, struct key *,
+extern int afs_fs_setattr(struct afs_fs_cursor *, struct key *,
 			  struct afs_vnode *, struct iattr *, bool);
-extern int afs_fs_get_volume_status(struct afs_server *, struct key *,
+extern int afs_fs_get_volume_status(struct afs_fs_cursor *, struct key *,
 				    struct afs_vnode *,
 				    struct afs_volume_status *, bool);
-extern int afs_fs_set_lock(struct afs_server *, struct key *,
+extern int afs_fs_set_lock(struct afs_fs_cursor *, struct key *,
 			   struct afs_vnode *, afs_lock_type_t, bool);
-extern int afs_fs_extend_lock(struct afs_server *, struct key *,
+extern int afs_fs_extend_lock(struct afs_fs_cursor *, struct key *,
 			      struct afs_vnode *, bool);
-extern int afs_fs_release_lock(struct afs_server *, struct key *,
+extern int afs_fs_release_lock(struct afs_fs_cursor *, struct key *,
 			       struct afs_vnode *, bool);
-extern int afs_fs_give_up_all_callbacks(struct afs_server *, struct key *, bool);
+extern int afs_fs_give_up_all_callbacks(struct afs_server *, struct afs_addr_cursor *,
+					struct key *, bool);
 
 /*
  * inode.c
@@ -697,7 +743,7 @@ extern void __net_exit afs_close_socket(struct afs_net *);
 extern void afs_charge_preallocation(struct work_struct *);
 extern void afs_put_call(struct afs_call *);
 extern int afs_queue_call_work(struct afs_call *);
-extern long afs_make_call(struct sockaddr_rxrpc *, struct afs_call *, gfp_t, bool);
+extern long afs_make_call(struct afs_addr_cursor *, struct afs_call *, gfp_t, bool);
 extern struct afs_call *afs_alloc_flat_call(struct afs_net *,
 					    const struct afs_call_type *,
 					    size_t, size_t);
@@ -751,13 +797,11 @@ extern void __exit afs_fs_exit(void);
 /*
  * vlclient.c
  */
-extern int afs_vl_get_entry_by_name(struct afs_net *,
-				    struct sockaddr_rxrpc *, struct key *,
-				    const char *, struct afs_cache_vlocation *,
-				    bool);
-extern int afs_vl_get_entry_by_id(struct afs_net *,
-				  struct sockaddr_rxrpc *, struct key *,
-				  afs_volid_t, afs_voltype_t,
+extern int afs_vl_get_entry_by_name(struct afs_net *, struct afs_addr_cursor *,
+				    struct key *, const char *,
+				    struct afs_cache_vlocation *, bool);
+extern int afs_vl_get_entry_by_id(struct afs_net *, struct afs_addr_cursor *,
+				  struct key *, afs_volid_t, afs_voltype_t,
 				  struct afs_cache_vlocation *, bool);
 
 /*
@@ -828,9 +872,11 @@ static inline struct afs_volume *afs_get_volume(struct afs_volume *volume)
 
 extern void afs_put_volume(struct afs_cell *, struct afs_volume *);
 extern struct afs_volume *afs_volume_lookup(struct afs_mount_params *);
-extern struct afs_server *afs_volume_pick_fileserver(struct afs_vnode *);
-extern int afs_volume_release_fileserver(struct afs_vnode *,
-					 struct afs_server *, int);
+extern void afs_init_fs_cursor(struct afs_fs_cursor *, struct afs_vnode *);
+extern int afs_set_fs_cursor(struct afs_fs_cursor *, struct afs_vnode *);
+extern bool afs_volume_pick_fileserver(struct afs_fs_cursor *, struct afs_vnode *);
+extern bool afs_iterate_fs_cursor(struct afs_fs_cursor *, struct afs_vnode *);
+extern int afs_end_fs_cursor(struct afs_fs_cursor *, struct afs_net *);
 
 /*
  * write.c
diff --git a/fs/afs/proc.c b/fs/afs/proc.c
index 08565429615d..9cf9ce88a8dd 100644
--- a/fs/afs/proc.c
+++ b/fs/afs/proc.c
@@ -514,23 +514,23 @@ static int afs_proc_cell_vlservers_open(struct inode *inode, struct file *file)
  */
 static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos)
 {
+	struct afs_addr_list *alist;
 	struct afs_cell *cell = m->private;
 	loff_t pos = *_pos;
 
-	_enter("cell=%p pos=%Ld", cell, *_pos);
+	rcu_read_lock();
 
-	/* lock the list against modification */
-	down_read(&cell->vl_sem);
+	alist = rcu_dereference(cell->vl_addrs);
 
 	/* allow for the header line */
 	if (!pos)
 		return (void *) 1;
 	pos--;
 
-	if (pos >= cell->vl_naddrs)
+	if (!alist || pos >= alist->nr_addrs)
 		return NULL;
 
-	return &cell->vl_addrs[pos];
+	return alist->addrs + pos;
 }
 
 /*
@@ -539,17 +539,18 @@ static void *afs_proc_cell_vlservers_start(struct seq_file *m, loff_t *_pos)
 static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v,
 					  loff_t *_pos)
 {
+	struct afs_addr_list *alist;
 	struct afs_cell *cell = p->private;
 	loff_t pos;
 
-	_enter("cell=%p{nad=%u} pos=%Ld", cell, cell->vl_naddrs, *_pos);
+	alist = rcu_dereference(cell->vl_addrs);
 
 	pos = *_pos;
 	(*_pos)++;
-	if (pos >= cell->vl_naddrs)
+	if (!alist || pos >= alist->nr_addrs)
 		return NULL;
 
-	return &cell->vl_addrs[pos];
+	return alist->addrs + pos;
 }
 
 /*
@@ -557,9 +558,7 @@ static void *afs_proc_cell_vlservers_next(struct seq_file *p, void *v,
  */
 static void afs_proc_cell_vlservers_stop(struct seq_file *p, void *v)
 {
-	struct afs_cell *cell = p->private;
-
-	up_read(&cell->vl_sem);
+	rcu_read_unlock();
 }
 
 /*
@@ -658,7 +657,7 @@ static int afs_proc_cell_servers_show(struct seq_file *m, void *v)
 	}
 
 	/* display one cell per line on subsequent lines */
-	sprintf(ipaddr, "%pISp", &server->addr.transport);
+	sprintf(ipaddr, "%pISp", &server->addrs->addrs[0].transport);
 	seq_printf(m, "%3d %-15s %5d\n",
 		   atomic_read(&server->usage), ipaddr, server->fs_state);
 
diff --git a/fs/afs/rxrpc.c b/fs/afs/rxrpc.c
index ac1e25f957b1..5ddfb7c4cf78 100644
--- a/fs/afs/rxrpc.c
+++ b/fs/afs/rxrpc.c
@@ -321,9 +321,10 @@ static int afs_send_pages(struct afs_call *call, struct msghdr *msg)
 /*
  * initiate a call
  */
-long afs_make_call(struct sockaddr_rxrpc *srx, struct afs_call *call,
+long afs_make_call(struct afs_addr_cursor *ac, struct afs_call *call,
 		   gfp_t gfp, bool async)
 {
+	struct sockaddr_rxrpc *srx = ac->addr;
 	struct rxrpc_call *rxcall;
 	struct msghdr msg;
 	struct kvec iov[1];
diff --git a/fs/afs/server.c b/fs/afs/server.c
index 4e66608fc805..9ca174b24f5b 100644
--- a/fs/afs/server.c
+++ b/fs/afs/server.c
@@ -56,7 +56,9 @@ static int afs_install_server(struct afs_server *server)
 		p = *pp;
 		_debug("- consider %p", p);
 		xserver = rb_entry(p, struct afs_server, master_rb);
-		diff = memcmp(&server->addr, &xserver->addr, sizeof(server->addr));
+		diff = memcmp(&server->addrs->addrs[0],
+			      &xserver->addrs->addrs[0],
+			      sizeof(sizeof(server->addrs->addrs[0])));
 		if (diff < 0)
 			pp = &(*pp)->rb_left;
 		else if (diff > 0)
@@ -85,25 +87,38 @@ static struct afs_server *afs_alloc_server(struct afs_cell *cell,
 	_enter("");
 
 	server = kzalloc(sizeof(struct afs_server), GFP_KERNEL);
-	if (server) {
-		atomic_set(&server->usage, 1);
-		server->net = cell->net;
-		server->cell = cell;
-
-		INIT_LIST_HEAD(&server->link);
-		INIT_LIST_HEAD(&server->grave);
-		init_rwsem(&server->sem);
-		spin_lock_init(&server->fs_lock);
-		INIT_LIST_HEAD(&server->cb_interests);
-		rwlock_init(&server->cb_break_lock);
-
-		server->addr = *addr;
-		afs_inc_servers_outstanding(cell->net);
-		_leave(" = %p{%d}", server, atomic_read(&server->usage));
-	} else {
-		_leave(" = NULL [nomem]");
-	}
+	if (!server)
+		goto enomem;
+	server->addrs = kzalloc(sizeof(struct afs_addr_list) +
+				sizeof(struct sockaddr_rxrpc),
+				GFP_KERNEL);
+	if (!server->addrs)
+		goto enomem_server;
+
+	atomic_set(&server->usage, 1);
+	server->net = cell->net;
+	server->cell = cell;
+
+	INIT_LIST_HEAD(&server->link);
+	INIT_LIST_HEAD(&server->grave);
+	init_rwsem(&server->sem);
+	spin_lock_init(&server->fs_lock);
+	INIT_LIST_HEAD(&server->cb_interests);
+	rwlock_init(&server->cb_break_lock);
+
+	refcount_set(&server->addrs->usage, 1);
+	server->addrs->nr_addrs = 1;
+	server->addrs->addrs[0] = *addr;
+	afs_inc_servers_outstanding(cell->net);
+
+	_leave(" = %p{%d}", server, atomic_read(&server->usage));
 	return server;
+
+enomem_server:
+	kfree(server);
+enomem:
+	_leave(" = NULL [nomem]");
+	return NULL;
 }
 
 /*
@@ -120,7 +135,7 @@ struct afs_server *afs_lookup_server(struct afs_cell *cell,
 	read_lock(&cell->servers_lock);
 
 	list_for_each_entry(server, &cell->servers, link) {
-		if (memcmp(&server->addr, addr, sizeof(*addr)) == 0)
+		if (memcmp(&server->addrs->addrs[0], addr, sizeof(*addr)) == 0)
 			goto found_server_quickly;
 	}
 	read_unlock(&cell->servers_lock);
@@ -135,7 +150,7 @@ struct afs_server *afs_lookup_server(struct afs_cell *cell,
 
 	/* check the cell's server list again */
 	list_for_each_entry(server, &cell->servers, link) {
-		if (memcmp(&server->addr, addr, sizeof(*addr)) == 0)
+		if (memcmp(&server->addrs->addrs[0], addr, sizeof(*addr)) == 0)
 			goto found_server;
 	}
 
@@ -204,7 +219,7 @@ struct afs_server *afs_find_server(struct afs_net *net,
 
 		_debug("- consider %p", p);
 
-		diff = memcmp(srx, &server->addr, sizeof(*srx));
+		diff = memcmp(srx, &server->addrs->addrs[0], sizeof(*srx));
 		if (diff < 0) {
 			p = p->rb_left;
 		} else if (diff > 0) {
@@ -269,10 +284,19 @@ void afs_put_server(struct afs_net *net, struct afs_server *server)
  */
 static void afs_destroy_server(struct afs_net *net, struct afs_server *server)
 {
+	struct afs_addr_list *alist = server->addrs;
+	struct afs_addr_cursor ac = {
+		.alist	= alist,
+		.addr	= &alist->addrs[0],
+		.start	= alist->index,
+		.index	= alist->index,
+		.error	= 0,
+	};
 	_enter("%p", server);
 
-	afs_fs_give_up_all_callbacks(server, NULL, false);
+	afs_fs_give_up_all_callbacks(server, &ac, NULL, false);
 	afs_put_cell(net, server->cell);
+	afs_put_addrlist(server->addrs);
 	kfree(server);
 	afs_dec_servers_outstanding(net);
 }
diff --git a/fs/afs/vlclient.c b/fs/afs/vlclient.c
index aa79fe3f168b..1d1e7df77dd5 100644
--- a/fs/afs/vlclient.c
+++ b/fs/afs/vlclient.c
@@ -114,7 +114,7 @@ static const struct afs_call_type afs_RXVLGetEntryById = {
  * dispatch a get volume entry by name operation
  */
 int afs_vl_get_entry_by_name(struct afs_net *net,
-			     struct sockaddr_rxrpc *addr,
+			     struct afs_addr_cursor *ac,
 			     struct key *key,
 			     const char *volname,
 			     struct afs_cache_vlocation *entry,
@@ -146,14 +146,14 @@ int afs_vl_get_entry_by_name(struct afs_net *net,
 		memset((void *) bp + volnamesz, 0, padsz);
 
 	/* initiate the call */
-	return afs_make_call(addr, call, GFP_KERNEL, async);
+	return afs_make_call(ac, call, GFP_KERNEL, async);
 }
 
 /*
  * dispatch a get volume entry by ID operation
  */
 int afs_vl_get_entry_by_id(struct afs_net *net,
-			   struct sockaddr_rxrpc *addr,
+			   struct afs_addr_cursor *ac,
 			   struct key *key,
 			   afs_volid_t volid,
 			   afs_voltype_t voltype,
@@ -179,5 +179,5 @@ int afs_vl_get_entry_by_id(struct afs_net *net,
 	*bp   = htonl(voltype);
 
 	/* initiate the call */
-	return afs_make_call(addr, call, GFP_KERNEL, async);
+	return afs_make_call(ac, call, GFP_KERNEL, async);
 }
diff --git a/fs/afs/vlocation.c b/fs/afs/vlocation.c
index ec5ab8dc9bc8..52c31ad0ef60 100644
--- a/fs/afs/vlocation.c
+++ b/fs/afs/vlocation.c
@@ -29,22 +29,25 @@ static int afs_vlocation_access_vl_by_name(struct afs_vlocation *vl,
 					   struct key *key,
 					   struct afs_cache_vlocation *vldb)
 {
-	struct afs_cell *cell = vl->cell;
-	int count, ret;
+	struct afs_addr_cursor ac;
+	int ret;
 
-	_enter("%s,%s", cell->name, vl->vldb.name);
+	_enter("%s,%s", vl->cell->name, vl->vldb.name);
+
+	ret = afs_set_vl_cursor(&ac, vl->cell);
+	if (ret < 0)
+		return ret;
 
 	down_write(&vl->cell->vl_sem);
+	
 	ret = -ENOMEDIUM;
-	for (count = cell->vl_naddrs; count > 0; count--) {
-		struct sockaddr_rxrpc *addr = &cell->vl_addrs[cell->vl_curr_svix];
-
-		_debug("CellServ[%hu]: %pIS", cell->vl_curr_svix, &addr->transport);
+	while (afs_iterate_addresses(&ac)) {
+		_debug("CellServ[%hu]: %pIS", ac.index, &ac.addr->transport);
 
 		/* attempt to access the VL server */
-		ret = afs_vl_get_entry_by_name(cell->net, addr, key,
-					       vl->vldb.name, vldb, false);
-		switch (ret) {
+		ac.error = afs_vl_get_entry_by_name(vl->cell->net, &ac, key,
+						    vl->vldb.name, vldb, false);
+		switch (ac.error) {
 		case 0:
 			goto out;
 		case -ENOMEM:
@@ -52,26 +55,24 @@ static int afs_vlocation_access_vl_by_name(struct afs_vlocation *vl,
 		case -ENETUNREACH:
 		case -EHOSTUNREACH:
 		case -ECONNREFUSED:
-			if (ret == -ENOMEM || ret == -ENONET)
+			if (ac.error == -ENOMEM || ac.error == -ENONET)
 				goto out;
-			goto rotate;
+			break;
 		case -ENOMEDIUM:
 		case -EKEYREJECTED:
 		case -EKEYEXPIRED:
+			ac.responded = true;
 			goto out;
 		default:
-			ret = -EIO;
-			goto rotate;
+			ac.responded = true;
+			ac.error = -EIO;
+			break;
 		}
-
-		/* rotate the server records upon lookup failure */
-	rotate:
-		cell->vl_curr_svix++;
-		cell->vl_curr_svix %= cell->vl_naddrs;
 	}
 
 out:
 	up_write(&vl->cell->vl_sem);
+	ret = afs_end_cursor(&ac);
 	_leave(" = %d", ret);
 	return ret;
 }
@@ -86,22 +87,24 @@ static int afs_vlocation_access_vl_by_id(struct afs_vlocation *vl,
 					 afs_voltype_t voltype,
 					 struct afs_cache_vlocation *vldb)
 {
-	struct afs_cell *cell = vl->cell;
-	int count, ret;
+	struct afs_addr_cursor ac;
+	int ret;
 
-	_enter("%s,%x,%d,", cell->name, volid, voltype);
+	_enter("%s,%x,%d,", vl->cell->name, volid, voltype);
+
+	ret = afs_set_vl_cursor(&ac, vl->cell);
+	if (ret < 0)
+		return ret;
 
 	down_write(&vl->cell->vl_sem);
 	ret = -ENOMEDIUM;
-	for (count = cell->vl_naddrs; count > 0; count--) {
-		struct sockaddr_rxrpc *addr = &cell->vl_addrs[cell->vl_curr_svix];
-
-		_debug("CellServ[%hu]: %pIS", cell->vl_curr_svix, &addr->transport);
+	while (afs_iterate_addresses(&ac)) {
+		_debug("CellServ[%hu]: %pIS", ac.index, &ac.addr->transport);
 
 		/* attempt to access the VL server */
-		ret = afs_vl_get_entry_by_id(cell->net, addr, key, volid,
-					     voltype, vldb, false);
-		switch (ret) {
+		ac.error = afs_vl_get_entry_by_id(vl->cell->net, &ac, key, volid,
+						  voltype, vldb, false);
+		switch (ac.error) {
 		case 0:
 			goto out;
 		case -ENOMEM:
@@ -109,10 +112,11 @@ static int afs_vlocation_access_vl_by_id(struct afs_vlocation *vl,
 		case -ENETUNREACH:
 		case -EHOSTUNREACH:
 		case -ECONNREFUSED:
-			if (ret == -ENOMEM || ret == -ENONET)
+			if (ac.error == -ENOMEM || ac.error == -ENONET)
 				goto out;
 			goto rotate;
 		case -EBUSY:
+			ac.responded = true;
 			vl->upd_busy_cnt++;
 			if (vl->upd_busy_cnt <= 3) {
 				if (vl->upd_busy_cnt > 1) {
@@ -124,30 +128,31 @@ static int afs_vlocation_access_vl_by_id(struct afs_vlocation *vl,
 			}
 			break;
 		case -ENOMEDIUM:
+			ac.responded = true;
 			vl->upd_rej_cnt++;
 			goto rotate;
 		default:
-			ret = -EIO;
+			ac.responded = true;
+			ac.error = -EIO;
 			goto rotate;
 		}
 
 		/* rotate the server records upon lookup failure */
 	rotate:
-		cell->vl_curr_svix++;
-		cell->vl_curr_svix %= cell->vl_naddrs;
 		vl->upd_busy_cnt = 0;
 	}
 
 out:
-	if (ret < 0 && vl->upd_rej_cnt > 0) {
+	if (ac.error < 0 && vl->upd_rej_cnt > 0) {
 		printk(KERN_NOTICE "kAFS:"
 		       " Active volume no longer valid '%s'\n",
 		       vl->vldb.name);
 		vl->valid = 0;
-		ret = -ENOMEDIUM;
+		ac.error = -ENOMEDIUM;
 	}
 
 	up_write(&vl->cell->vl_sem);
+	ret = afs_end_cursor(&ac);
 	_leave(" = %d", ret);
 	return ret;
 }
diff --git a/fs/afs/vnode.c b/fs/afs/vnode.c
index 622e1100099b..9c7333eb01c2 100644
--- a/fs/afs/vnode.c
+++ b/fs/afs/vnode.c
@@ -44,30 +44,26 @@ static void afs_vnode_deleted_remotely(struct afs_vnode *vnode)
 void afs_vnode_finalise_status_update(struct afs_vnode *vnode,
 				      struct afs_server *server)
 {
-	struct afs_server *oldserver = NULL;
-
-	_enter("%p,%p", vnode, server);
-
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
 
 	wake_up_all(&vnode->update_waitq);
-	afs_put_server(afs_v2net(vnode), oldserver);
 	_leave("");
 }
 
 /*
  * finish off updating the recorded status of a file after an operation failed
  */
-static void afs_vnode_status_update_failed(struct afs_vnode *vnode, int ret)
+static void afs_vnode_status_update_failed(struct afs_fs_cursor *fc,
+					   struct afs_vnode *vnode)
 {
-	_enter("{%x:%u},%d", vnode->fid.vid, vnode->fid.vnode, ret);
+	_enter("{%x:%u},%d", vnode->fid.vid, vnode->fid.vnode, fc->ac.error);
 
 	spin_lock(&vnode->lock);
 
-	if (ret == -ENOENT) {
+	if (fc->ac.error == -ENOENT) {
 		/* the file was deleted on the server */
 		_debug("got NOENT from server - marking file deleted");
 		afs_vnode_deleted_remotely(vnode);
@@ -90,9 +86,8 @@ static void afs_vnode_status_update_failed(struct afs_vnode *vnode, int ret)
  */
 int afs_vnode_fetch_status(struct afs_vnode *vnode, struct key *key, bool force)
 {
-	struct afs_server *server;
+	struct afs_fs_cursor fc;
 	unsigned int cb_break = 0;
-	int ret;
 
 	DECLARE_WAITQUEUE(myself, current);
 
@@ -172,43 +167,37 @@ get_anyway:
 
 	/* merge AFS status fetches and clear outstanding callback on this
 	 * vnode */
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %p{%pIS}",
-		       server, &server->addr.transport);
-
-		ret = afs_fs_fetch_file_status(server, key, vnode, NULL,
-					       false);
+		fc.ac.error = afs_fs_fetch_file_status(&fc, key, vnode, NULL, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
+	if (fc.ac.error == 0) {
 		_debug("adjust");
 		afs_cache_permit(vnode, key, cb_break);
-		afs_vnode_finalise_status_update(vnode, server);
-		afs_put_server(afs_v2net(vnode), server);
+		afs_vnode_finalise_status_update(vnode, fc.server);
 	} else {
-		_debug("failed [%d]", ret);
-		afs_vnode_status_update_failed(vnode, ret);
+		_debug("failed [%d]", fc.ac.error);
+		afs_vnode_status_update_failed(&fc, vnode);
 	}
 
+out:
+	afs_end_fs_cursor(&fc, afs_v2net(vnode));
 	ASSERTCMP(vnode->update_cnt, >=, 0);
-
-	_leave(" = %d [cnt %d]", ret, vnode->update_cnt);
-	return ret;
+	_leave(" = %d [cnt %d]", fc.ac.error, vnode->update_cnt);
+	return fc.ac.error;
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
-	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	_leave(" = %ld [cnt %d]", PTR_ERR(server), vnode->update_cnt);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -218,8 +207,7 @@ no_server:
 int afs_vnode_fetch_data(struct afs_vnode *vnode, struct key *key,
 			 struct afs_read *desc)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x,,,",
 	       vnode->volume->vlocation->vldb.name,
@@ -235,36 +223,31 @@ int afs_vnode_fetch_data(struct afs_vnode *vnode, struct key *key,
 
 	/* merge in AFS status fetches and clear outstanding callback on this
 	 * vnode */
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
-
-		ret = afs_fs_fetch_data(server, key, vnode, desc,
-					false);
+		fc.ac.error = afs_fs_fetch_data(&fc, key, vnode, desc, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		afs_put_server(afs_v2net(vnode), server);
-	} else {
-		afs_vnode_status_update_failed(vnode, ret);
-	}
+	if (fc.ac.error == 0)
+		afs_vnode_finalise_status_update(vnode, fc.server);
+	else
+		afs_vnode_status_update_failed(&fc, vnode);
 
-	_leave(" = %d", ret);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -275,8 +258,7 @@ int afs_vnode_create(struct afs_vnode *vnode, struct key *key,
 		     struct afs_file_status *newstatus,
 		     struct afs_callback *newcb, struct afs_server **_server)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x,%s,,",
 	       vnode->volume->vlocation->vldb.name,
@@ -291,38 +273,36 @@ int afs_vnode_create(struct afs_vnode *vnode, struct key *key,
 	vnode->update_cnt++;
 	spin_unlock(&vnode->lock);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
-
-		ret = afs_fs_create(server, key, vnode, name, mode, newfid,
-				    newstatus, newcb, false);
+		fc.ac.error = afs_fs_create(&fc, key, vnode, name, mode, newfid,
+					    newstatus, newcb, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		*_server = server;
+	if (fc.ac.error == 0) {
+		afs_vnode_finalise_status_update(vnode, fc.server);
+		*_server = fc.server;
+		fc.server = NULL;
 	} else {
-		afs_vnode_status_update_failed(vnode, ret);
+		afs_vnode_status_update_failed(&fc, vnode);
 		*_server = NULL;
 	}
 
-	_leave(" = %d [cnt %d]", ret, vnode->update_cnt);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	_leave(" = %ld [cnt %d]", PTR_ERR(server), vnode->update_cnt);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -331,8 +311,7 @@ no_server:
 int afs_vnode_remove(struct afs_vnode *vnode, struct key *key, const char *name,
 		     bool isdir)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x,%s",
 	       vnode->volume->vlocation->vldb.name,
@@ -347,37 +326,31 @@ int afs_vnode_remove(struct afs_vnode *vnode, struct key *key, const char *name,
 	vnode->update_cnt++;
 	spin_unlock(&vnode->lock);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
-
-		ret = afs_fs_remove(server, key, vnode, name, isdir,
-				    false);
+		fc.ac.error = afs_fs_remove(&fc, key, vnode, name, isdir, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		afs_put_server(afs_v2net(vnode), server);
-	} else {
-		afs_vnode_status_update_failed(vnode, ret);
-	}
+	if (fc.ac.error == 0)
+		afs_vnode_finalise_status_update(vnode, fc.server);
+	else
+		afs_vnode_status_update_failed(&fc, vnode);
 
-	_leave(" = %d [cnt %d]", ret, vnode->update_cnt);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	_leave(" = %ld [cnt %d]", PTR_ERR(server), vnode->update_cnt);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -386,8 +359,7 @@ no_server:
 int afs_vnode_link(struct afs_vnode *dvnode, struct afs_vnode *vnode,
 			  struct key *key, const char *name)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%s{%x:%u.%u},%x,%s",
 	       dvnode->volume->vlocation->vldb.name,
@@ -409,31 +381,27 @@ int afs_vnode_link(struct afs_vnode *dvnode, struct afs_vnode *vnode,
 	dvnode->update_cnt++;
 	spin_unlock(&dvnode->lock);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(dvnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, dvnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
+		fc.ac.error = afs_fs_link(&fc, key, dvnode, vnode, name, false);
 
-		ret = afs_fs_link(server, key, dvnode, vnode, name,
-				  false);
-
-	} while (!afs_volume_release_fileserver(dvnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, dvnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		afs_vnode_finalise_status_update(dvnode, server);
-		afs_put_server(afs_v2net(dvnode), server);
+	if (fc.ac.error == 0) {
+		afs_vnode_finalise_status_update(vnode, fc.server);
+		afs_vnode_finalise_status_update(dvnode, fc.server);
 	} else {
-		afs_vnode_status_update_failed(vnode, ret);
-		afs_vnode_status_update_failed(dvnode, ret);
+		afs_vnode_status_update_failed(&fc, vnode);
+		afs_vnode_status_update_failed(&fc, dvnode);
 	}
 
-	_leave(" = %d [cnt %d]", ret, vnode->update_cnt);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
@@ -444,8 +412,7 @@ no_server:
 	dvnode->update_cnt--;
 	ASSERTCMP(dvnode->update_cnt, >=, 0);
 	spin_unlock(&dvnode->lock);
-	_leave(" = %ld [cnt %d]", PTR_ERR(server), vnode->update_cnt);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -457,8 +424,7 @@ int afs_vnode_symlink(struct afs_vnode *vnode, struct key *key,
 		      struct afs_file_status *newstatus,
 		      struct afs_server **_server)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x,%s,%s,,,",
 	       vnode->volume->vlocation->vldb.name,
@@ -473,38 +439,37 @@ int afs_vnode_symlink(struct afs_vnode *vnode, struct key *key,
 	vnode->update_cnt++;
 	spin_unlock(&vnode->lock);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
+		fc.ac.error = afs_fs_symlink(&fc, key, vnode, name, content,
+					     newfid, newstatus, false);
 
-		ret = afs_fs_symlink(server, key, vnode, name, content,
-				     newfid, newstatus, false);
-
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		*_server = server;
+	if (fc.ac.error == 0) {
+		afs_vnode_finalise_status_update(vnode, fc.server);
+		*_server = fc.server;
+		fc.server = NULL;
 	} else {
-		afs_vnode_status_update_failed(vnode, ret);
+		afs_vnode_status_update_failed(&fc, vnode);
 		*_server = NULL;
 	}
 
-	_leave(" = %d [cnt %d]", ret, vnode->update_cnt);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	_leave(" = %ld [cnt %d]", PTR_ERR(server), vnode->update_cnt);
-	return PTR_ERR(server);
+	*_server = NULL;
+	goto out;
 }
 
 /*
@@ -516,8 +481,7 @@ int afs_vnode_rename(struct afs_vnode *orig_dvnode,
 		     const char *orig_name,
 		     const char *new_name)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%s{%u,%u,%u},%x,%s,%s",
 	       orig_dvnode->volume->vlocation->vldb.name,
@@ -543,33 +507,30 @@ int afs_vnode_rename(struct afs_vnode *orig_dvnode,
 		spin_unlock(&new_dvnode->lock);
 	}
 
+	afs_init_fs_cursor(&fc, orig_dvnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(orig_dvnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, orig_dvnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
-
-		ret = afs_fs_rename(server, key, orig_dvnode, orig_name,
-				    new_dvnode, new_name, false);
+		fc.ac.error = afs_fs_rename(&fc, key, orig_dvnode, orig_name,
+					    new_dvnode, new_name, false);
 
-	} while (!afs_volume_release_fileserver(orig_dvnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, orig_dvnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(orig_dvnode, server);
+	if (fc.ac.error == 0) {
+		afs_vnode_finalise_status_update(orig_dvnode, fc.server);
 		if (new_dvnode != orig_dvnode)
-			afs_vnode_finalise_status_update(new_dvnode, server);
-		afs_put_server(afs_v2net(orig_dvnode), server);
+			afs_vnode_finalise_status_update(new_dvnode, fc.server);
 	} else {
-		afs_vnode_status_update_failed(orig_dvnode, ret);
+		afs_vnode_status_update_failed(&fc, orig_dvnode);
 		if (new_dvnode != orig_dvnode)
-			afs_vnode_status_update_failed(new_dvnode, ret);
+			afs_vnode_status_update_failed(&fc, new_dvnode);
 	}
 
-	_leave(" = %d [cnt %d]", ret, orig_dvnode->update_cnt);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(orig_dvnode));
 
 no_server:
 	spin_lock(&orig_dvnode->lock);
@@ -582,8 +543,7 @@ no_server:
 		ASSERTCMP(new_dvnode->update_cnt, >=, 0);
 		spin_unlock(&new_dvnode->lock);
 	}
-	_leave(" = %ld [cnt %d]", PTR_ERR(server), orig_dvnode->update_cnt);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -592,9 +552,8 @@ no_server:
 int afs_vnode_store_data(struct afs_writeback *wb, pgoff_t first, pgoff_t last,
 			 unsigned offset, unsigned to)
 {
-	struct afs_server *server;
+	struct afs_fs_cursor fc;
 	struct afs_vnode *vnode = wb->vnode;
-	int ret;
 
 	_enter("%s{%x:%u.%u},%x,%lx,%lx,%x,%x",
 	       vnode->volume->vlocation->vldb.name,
@@ -609,36 +568,33 @@ int afs_vnode_store_data(struct afs_writeback *wb, pgoff_t first, pgoff_t last,
 	vnode->update_cnt++;
 	spin_unlock(&vnode->lock);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
-
-		ret = afs_fs_store_data(server, wb, first, last, offset, to,
-					false);
+		fc.ac.error = afs_fs_store_data(&fc, wb, first, last, offset, to,
+						false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		afs_put_server(afs_v2net(vnode), server);
+	if (fc.ac.error == 0) {
+		afs_vnode_finalise_status_update(vnode, fc.server);
 	} else {
-		afs_vnode_status_update_failed(vnode, ret);
+		afs_vnode_status_update_failed(&fc, vnode);
 	}
 
-	_leave(" = %d", ret);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -647,8 +603,7 @@ no_server:
 int afs_vnode_setattr(struct afs_vnode *vnode, struct key *key,
 		      struct iattr *attr)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x",
 	       vnode->volume->vlocation->vldb.name,
@@ -662,35 +617,32 @@ int afs_vnode_setattr(struct afs_vnode *vnode, struct key *key,
 	vnode->update_cnt++;
 	spin_unlock(&vnode->lock);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
+		if (!afs_volume_pick_fileserver(&fc, vnode))
 			goto no_server;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
-
-		ret = afs_fs_setattr(server, key, vnode, attr, false);
+		fc.ac.error = afs_fs_setattr(&fc, key, vnode, attr, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
 	/* adjust the flags */
-	if (ret == 0) {
-		afs_vnode_finalise_status_update(vnode, server);
-		afs_put_server(afs_v2net(vnode), server);
+	if (fc.ac.error == 0) {
+		afs_vnode_finalise_status_update(vnode, fc.server);
 	} else {
-		afs_vnode_status_update_failed(vnode, ret);
+		afs_vnode_status_update_failed(&fc, vnode);
 	}
 
-	_leave(" = %d", ret);
-	return ret;
+out:
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 
 no_server:
 	spin_lock(&vnode->lock);
 	vnode->update_cnt--;
 	ASSERTCMP(vnode->update_cnt, >=, 0);
 	spin_unlock(&vnode->lock);
-	return PTR_ERR(server);
+	goto out;
 }
 
 /*
@@ -699,8 +651,7 @@ no_server:
 int afs_vnode_get_volume_status(struct afs_vnode *vnode, struct key *key,
 				struct afs_volume_status *vs)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x,",
 	       vnode->volume->vlocation->vldb.name,
@@ -709,27 +660,17 @@ int afs_vnode_get_volume_status(struct afs_vnode *vnode, struct key *key,
 	       vnode->fid.unique,
 	       key_serial(key));
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
-			goto no_server;
-
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
+		if (!afs_volume_pick_fileserver(&fc, vnode))
+			break;
 
-		ret = afs_fs_get_volume_status(server, key, vnode, vs, false);
+		fc.ac.error = afs_fs_get_volume_status(&fc, key, vnode, vs, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
-	/* adjust the flags */
-	if (ret == 0)
-		afs_put_server(afs_v2net(vnode), server);
-
-	_leave(" = %d", ret);
-	return ret;
-
-no_server:
-	return PTR_ERR(server);
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 }
 
 /*
@@ -738,8 +679,7 @@ no_server:
 int afs_vnode_set_lock(struct afs_vnode *vnode, struct key *key,
 		       afs_lock_type_t type)
 {
-	struct afs_server *server;
-	int ret;
+	struct afs_fs_cursor fc;
 
 	_enter("%s{%x:%u.%u},%x,%u",
 	       vnode->volume->vlocation->vldb.name,
@@ -748,27 +688,17 @@ int afs_vnode_set_lock(struct afs_vnode *vnode, struct key *key,
 	       vnode->fid.unique,
 	       key_serial(key), type);
 
+	afs_init_fs_cursor(&fc, vnode);
 	do {
 		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
-			goto no_server;
+		if (!afs_volume_pick_fileserver(&fc, vnode))
+			break;
 
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
+		fc.ac.error = afs_fs_set_lock(&fc, key, vnode, type, false);
 
-		ret = afs_fs_set_lock(server, key, vnode, type, false);
+	} while (afs_iterate_fs_cursor(&fc, vnode));
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
-
-	/* adjust the flags */
-	if (ret == 0)
-		afs_put_server(afs_v2net(vnode), server);
-
-	_leave(" = %d", ret);
-	return ret;
-
-no_server:
-	return PTR_ERR(server);
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 }
 
 /*
@@ -776,7 +706,7 @@ no_server:
  */
 int afs_vnode_extend_lock(struct afs_vnode *vnode, struct key *key)
 {
-	struct afs_server *server;
+	struct afs_fs_cursor fc;
 	int ret;
 
 	_enter("%s{%x:%u.%u},%x",
@@ -786,27 +716,13 @@ int afs_vnode_extend_lock(struct afs_vnode *vnode, struct key *key)
 	       vnode->fid.unique,
 	       key_serial(key));
 
-	do {
-		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
-			goto no_server;
-
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
+	ret = afs_set_fs_cursor(&fc, vnode);
+	if (ret < 0)
+		return ret;
 
-		ret = afs_fs_extend_lock(server, key, vnode, false);
+	fc.ac.error = afs_fs_extend_lock(&fc, key, vnode, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
-
-	/* adjust the flags */
-	if (ret == 0)
-		afs_put_server(afs_v2net(vnode), server);
-
-	_leave(" = %d", ret);
-	return ret;
-
-no_server:
-	return PTR_ERR(server);
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 }
 
 /*
@@ -814,7 +730,7 @@ no_server:
  */
 int afs_vnode_release_lock(struct afs_vnode *vnode, struct key *key)
 {
-	struct afs_server *server;
+	struct afs_fs_cursor fc;
 	int ret;
 
 	_enter("%s{%x:%u.%u},%x",
@@ -824,25 +740,11 @@ int afs_vnode_release_lock(struct afs_vnode *vnode, struct key *key)
 	       vnode->fid.unique,
 	       key_serial(key));
 
-	do {
-		/* pick a server to query */
-		server = afs_volume_pick_fileserver(vnode);
-		if (IS_ERR(server))
-			goto no_server;
-
-		_debug("USING SERVER: %pIS\n", &server->addr.transport);
+	ret = afs_set_fs_cursor(&fc, vnode);
+	if (ret < 0)
+		return ret;
 
-		ret = afs_fs_release_lock(server, key, vnode, false);
+	fc.ac.error = afs_fs_release_lock(&fc, key, vnode, false);
 
-	} while (!afs_volume_release_fileserver(vnode, server, ret));
-
-	/* adjust the flags */
-	if (ret == 0)
-		afs_put_server(afs_v2net(vnode), server);
-
-	_leave(" = %d", ret);
-	return ret;
-
-no_server:
-	return PTR_ERR(server);
+	return afs_end_fs_cursor(&fc, afs_v2net(vnode));
 }
diff --git a/fs/afs/volume.c b/fs/afs/volume.c
index 4f6fd10094c6..d282cd0ff268 100644
--- a/fs/afs/volume.c
+++ b/fs/afs/volume.c
@@ -210,10 +210,44 @@ void afs_put_volume(struct afs_cell *cell, struct afs_volume *volume)
 }
 
 /*
+ * Initialise a filesystem server cursor for iterating over FS servers.
+ */
+void afs_init_fs_cursor(struct afs_fs_cursor *fc, struct afs_vnode *vnode)
+{
+	fc->ac.alist = NULL;
+	fc->ac.addr = NULL;
+	fc->ac.start = 0;
+	fc->ac.index = 0;
+	fc->ac.error = 0;
+	fc->server = NULL;
+}
+
+/*
+ * Set a filesystem server cursor for using a specific FS server.
+ */
+int afs_set_fs_cursor(struct afs_fs_cursor *fc, struct afs_vnode *vnode)
+{
+	afs_init_fs_cursor(fc, vnode);
+
+	read_seqlock_excl(&vnode->cb_lock);
+	if (vnode->cb_interest) {
+		if (vnode->cb_interest->server->fs_state == 0)
+			fc->server = afs_get_server(vnode->cb_interest->server);
+		else
+			fc->ac.error = vnode->cb_interest->server->fs_state;
+	} else {
+		fc->ac.error = -ESTALE;
+	}
+	read_sequnlock_excl(&vnode->cb_lock);
+
+	return fc->ac.error;
+}
+
+/*
  * pick a server to use to try accessing this volume
  * - returns with an elevated usage count on the server chosen
  */
-struct afs_server *afs_volume_pick_fileserver(struct afs_vnode *vnode)
+bool afs_volume_pick_fileserver(struct afs_fs_cursor *fc, struct afs_vnode *vnode)
 {
 	struct afs_volume *volume = vnode->volume;
 	struct afs_server *server;
@@ -223,19 +257,18 @@ struct afs_server *afs_volume_pick_fileserver(struct afs_vnode *vnode)
 
 	/* stick with the server we're already using if we can */
 	if (vnode->cb_interest && vnode->cb_interest->server->fs_state == 0) {
-		afs_get_server(vnode->cb_interest->server);
-		_leave(" = %p [current]", vnode->cb_interest->server);
-		return vnode->cb_interest->server;
+		fc->server = afs_get_server(vnode->cb_interest->server);
+		goto set_server;
 	}
 
 	down_read(&volume->server_sem);
 
 	/* handle the no-server case */
 	if (volume->nservers == 0) {
-		ret = volume->rjservers ? -ENOMEDIUM : -ESTALE;
+		fc->ac.error = volume->rjservers ? -ENOMEDIUM : -ESTALE;
 		up_read(&volume->server_sem);
-		_leave(" = %d [no servers]", ret);
-		return ERR_PTR(ret);
+		_leave(" = f [no servers %d]", fc->ac.error);
+		return false;
 	}
 
 	/* basically, just search the list for the first live server and use
@@ -280,13 +313,15 @@ struct afs_server *afs_volume_pick_fileserver(struct afs_vnode *vnode)
 		}
 	}
 
+error:
+	fc->ac.error = ret;
+
 	/* no available servers
 	 * - TODO: handle the no active servers case better
 	 */
-error:
 	up_read(&volume->server_sem);
-	_leave(" = %d", ret);
-	return ERR_PTR(ret);
+	_leave(" = f [%d]", fc->ac.error);
+	return false;
 
 picked_server:
 	/* Found an apparently healthy server.  We need to register an interest
@@ -296,37 +331,41 @@ picked_server:
 					      &volume->cb_interests[loop], server);
 	if (ret < 0)
 		goto error;
-	
-	afs_get_server(server);
+
+	fc->server = afs_get_server(server);
 	up_read(&volume->server_sem);
-	_leave(" = %p (picked %pIS)",
-	       server, &server->addr.transport);
-	return server;
+set_server:
+	fc->ac.alist = afs_get_addrlist(fc->server->addrs);
+	fc->ac.addr = &fc->ac.alist->addrs[0];
+	_debug("USING SERVER: %pIS\n", &fc->ac.addr->transport);
+	_leave(" = t (picked %pIS)", &fc->ac.addr->transport);
+	return true;
 }
 
 /*
  * 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
- * - the caller must release the server struct if result was 0
+ * - return true to try again, false if okay or to issue error
+ * - the caller must release the server struct if result was false
  */
-int afs_volume_release_fileserver(struct afs_vnode *vnode,
-				  struct afs_server *server,
-				  int result)
+bool afs_iterate_fs_cursor(struct afs_fs_cursor *fc,
+			   struct afs_vnode *vnode)
 {
 	struct afs_volume *volume = vnode->volume;
+	struct afs_server *server = fc->server;
 	unsigned loop;
 
 	_enter("%s,%pIS,%d",
-	       volume->vlocation->vldb.name, &server->addr.transport, result);
+	       volume->vlocation->vldb.name, &fc->ac.addr->transport,
+	       fc->ac.error);
 
-	switch (result) {
+	switch (fc->ac.error) {
 		/* success */
 	case 0:
 		server->fs_state = 0;
-		_leave("");
-		return 1;
+		_leave(" = f");
+		return false;
 
 		/* the fileserver denied all knowledge of the volume */
 	case -ENOMEDIUM:
@@ -363,8 +402,9 @@ int afs_volume_release_fileserver(struct afs_vnode *vnode,
 		 */
 		up_write(&volume->server_sem);
 		afs_put_server(afs_v2net(vnode), server);
-		_leave(" [completely rejected]");
-		return 1;
+		fc->server = NULL;
+		_leave(" = f [completely rejected]");
+		return false;
 
 		/* problem reaching the server */
 	case -ENETUNREACH:
@@ -378,8 +418,8 @@ int afs_volume_release_fileserver(struct afs_vnode *vnode,
 		 */
 		spin_lock(&server->fs_lock);
 		if (!server->fs_state) {
-			server->fs_state = result;
-			printk("kAFS: SERVER DEAD state=%d\n", result);
+			server->fs_state = fc->ac.error;
+			printk("kAFS: SERVER DEAD state=%d\n", fc->ac.error);
 		}
 		spin_unlock(&server->fs_lock);
 		goto try_next_server;
@@ -390,8 +430,9 @@ int afs_volume_release_fileserver(struct afs_vnode *vnode,
 	case -ENONET:
 		/* tell the caller to accept the result */
 		afs_put_server(afs_v2net(vnode), server);
-		_leave(" [local failure]");
-		return 1;
+		fc->server = NULL;
+		_leave(" = f [local failure]");
+		return false;
 	}
 
 	/* tell the caller to loop around and try the next server */
@@ -399,6 +440,16 @@ try_next_server_upw:
 	up_write(&volume->server_sem);
 try_next_server:
 	afs_put_server(afs_v2net(vnode), server);
-	_leave(" [try next server]");
-	return 0;
+	_leave(" = t [try next server]");
+	return true;
+}
+
+/*
+ * Clean up a fileserver cursor.
+ */
+int afs_end_fs_cursor(struct afs_fs_cursor *fc, struct afs_net *net)
+{
+	afs_end_cursor(&fc->ac);
+	afs_put_server(net, fc->server);
+	return fc->ac.error;
 }