Commit 3c4c4075 authored by David Howells's avatar David Howells

afs: Fix the by-UUID server tree to allow servers with the same UUID

Whilst it shouldn't happen, it is possible for multiple fileservers to
share a UUID, particularly if an entire cell has been duplicated, UUIDs and
all.  In such a case, it's not necessarily possible to map the effect of
the CB.InitCallBackState3 incoming RPC to a specific server unambiguously
by UUID and thus to a specific cell.

Indeed, there's a problem whereby multiple server records may need to
occupy the same spot in the rb_tree rooted in the afs_net struct.

Fix this by allowing servers to form a list, with the head of the list in
the tree.  When the front entry in the list is removed, the second in the
list just replaces it.  afs_init_callback_state() then just goes down the
line, poking each server in the list.

This means that some servers will be unnecessarily poked, unfortunately.
An alternative would be to route by call parameters.
Reported-by: default avatarJeffrey Altman <jaltman@auristor.com>
Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
Fixes: d2ddc776 ("afs: Overhaul volume and server record caching and fileserver rotation")
parent 20325960
...@@ -21,11 +21,17 @@ ...@@ -21,11 +21,17 @@
#include "internal.h" #include "internal.h"
/* /*
* allow the fileserver to request callback state (re-)initialisation * Allow the fileserver to request callback state (re-)initialisation.
* Unfortunately, UUIDs are not guaranteed unique.
*/ */
void afs_init_callback_state(struct afs_server *server) void afs_init_callback_state(struct afs_server *server)
{ {
rcu_read_lock();
do {
server->cb_s_break++; server->cb_s_break++;
server = rcu_dereference(server->uuid_next);
} while (0);
rcu_read_unlock();
} }
/* /*
......
...@@ -486,7 +486,9 @@ struct afs_server { ...@@ -486,7 +486,9 @@ struct afs_server {
struct afs_addr_list __rcu *addresses; struct afs_addr_list __rcu *addresses;
struct afs_cell *cell; /* Cell to which belongs (pins ref) */ struct afs_cell *cell; /* Cell to which belongs (pins ref) */
struct rb_node uuid_rb; /* Link in cell->fs_servers */ struct rb_node uuid_rb; /* Link in net->fs_servers */
struct afs_server __rcu *uuid_next; /* Next server with same UUID */
struct afs_server *uuid_prev; /* Previous server with same UUID */
struct list_head probe_link; /* Link in net->fs_probe_list */ struct list_head probe_link; /* Link in net->fs_probe_list */
struct hlist_node addr4_link; /* Link in net->fs_addresses4 */ struct hlist_node addr4_link; /* Link in net->fs_addresses4 */
struct hlist_node addr6_link; /* Link in net->fs_addresses6 */ struct hlist_node addr6_link; /* Link in net->fs_addresses6 */
......
...@@ -130,13 +130,15 @@ struct afs_server *afs_find_server_by_uuid(struct afs_net *net, const uuid_t *uu ...@@ -130,13 +130,15 @@ struct afs_server *afs_find_server_by_uuid(struct afs_net *net, const uuid_t *uu
} }
/* /*
* Install a server record in the namespace tree * Install a server record in the namespace tree. If there's a clash, we stick
* it into a list anchored on whichever afs_server struct is actually in the
* tree.
*/ */
static struct afs_server *afs_install_server(struct afs_cell *cell, static struct afs_server *afs_install_server(struct afs_cell *cell,
struct afs_server *candidate) struct afs_server *candidate)
{ {
const struct afs_addr_list *alist; const struct afs_addr_list *alist;
struct afs_server *server; struct afs_server *server, *next;
struct afs_net *net = cell->net; struct afs_net *net = cell->net;
struct rb_node **pp, *p; struct rb_node **pp, *p;
int diff; int diff;
...@@ -153,12 +155,30 @@ static struct afs_server *afs_install_server(struct afs_cell *cell, ...@@ -153,12 +155,30 @@ static struct afs_server *afs_install_server(struct afs_cell *cell,
_debug("- consider %p", p); _debug("- consider %p", p);
server = rb_entry(p, struct afs_server, uuid_rb); server = rb_entry(p, struct afs_server, uuid_rb);
diff = memcmp(&candidate->uuid, &server->uuid, sizeof(uuid_t)); diff = memcmp(&candidate->uuid, &server->uuid, sizeof(uuid_t));
if (diff < 0) if (diff < 0) {
pp = &(*pp)->rb_left; pp = &(*pp)->rb_left;
else if (diff > 0) } else if (diff > 0) {
pp = &(*pp)->rb_right; pp = &(*pp)->rb_right;
else } else {
if (server->cell == cell)
goto exists; goto exists;
/* We have the same UUID representing servers in
* different cells. Append the new server to the list.
*/
for (;;) {
next = rcu_dereference_protected(
server->uuid_next,
lockdep_is_held(&net->fs_lock.lock));
if (!next)
break;
server = next;
}
rcu_assign_pointer(server->uuid_next, candidate);
candidate->uuid_prev = server;
server = candidate;
goto added_dup;
}
} }
server = candidate; server = candidate;
...@@ -166,6 +186,7 @@ static struct afs_server *afs_install_server(struct afs_cell *cell, ...@@ -166,6 +186,7 @@ static struct afs_server *afs_install_server(struct afs_cell *cell,
rb_insert_color(&server->uuid_rb, &net->fs_servers); rb_insert_color(&server->uuid_rb, &net->fs_servers);
hlist_add_head_rcu(&server->proc_link, &net->fs_proc); hlist_add_head_rcu(&server->proc_link, &net->fs_proc);
added_dup:
write_seqlock(&net->fs_addr_lock); write_seqlock(&net->fs_addr_lock);
alist = rcu_dereference_protected(server->addresses, alist = rcu_dereference_protected(server->addresses,
lockdep_is_held(&net->fs_addr_lock.lock)); lockdep_is_held(&net->fs_addr_lock.lock));
...@@ -453,7 +474,7 @@ static void afs_destroy_server(struct afs_net *net, struct afs_server *server) ...@@ -453,7 +474,7 @@ static void afs_destroy_server(struct afs_net *net, struct afs_server *server)
*/ */
static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list) static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list)
{ {
struct afs_server *server; struct afs_server *server, *next, *prev;
int active; int active;
while ((server = gc_list)) { while ((server = gc_list)) {
...@@ -465,7 +486,26 @@ static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list) ...@@ -465,7 +486,26 @@ static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list)
if (active == 0) { if (active == 0) {
trace_afs_server(server, atomic_read(&server->ref), trace_afs_server(server, atomic_read(&server->ref),
active, afs_server_trace_gc); active, afs_server_trace_gc);
next = rcu_dereference_protected(
server->uuid_next, lockdep_is_held(&net->fs_lock.lock));
prev = server->uuid_prev;
if (!prev) {
/* The one at the front is in the tree */
if (!next) {
rb_erase(&server->uuid_rb, &net->fs_servers); rb_erase(&server->uuid_rb, &net->fs_servers);
} else {
rb_replace_node_rcu(&server->uuid_rb,
&next->uuid_rb,
&net->fs_servers);
next->uuid_prev = NULL;
}
} else {
/* This server is not at the front */
rcu_assign_pointer(prev->uuid_next, next);
if (next)
next->uuid_prev = prev;
}
list_del(&server->probe_link); list_del(&server->probe_link);
hlist_del_rcu(&server->proc_link); hlist_del_rcu(&server->proc_link);
if (!hlist_unhashed(&server->addr4_link)) if (!hlist_unhashed(&server->addr4_link))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment