Overhaul the way that the in-kernel AFS client keeps track of cells in the
following manner:

 (1) Cells are now held in an rbtree to make walking them quicker and RCU
     managed (though this is probably overkill).

 (2) Cells now have a manager work item that:

     (A) Looks after fetching and refreshing the VL server list.

     (B) Manages cell record lifetime, including initialising and
         destruction.

     (B) Manages cell record caching whereby threads are kept around for a
         certain time after last use and then destroyed.

     (C) Manages the FS-Cache index cookie for a cell.  It is not permitted
         for a cookie to be in use twice, so we have to be careful to not
         allow a new cell record to exist at the same time as an old record
         of the same name.

 (3) Each AFS network namespace is given a manager work item that manages
     the cells within it, maintaining a single timer to prod cells into
     updating their DNS records.

     This uses the reduce_timer() facility to make the timer expire at the
     soonest timed event that needs happening.

 (4) When a module is being unloaded, cells and cell managers are now
     counted out using dec_after_work() to make sure the module text is
     pinned until after the data structures have been cleaned up.

 (5) Each cell's VL server list is now protected by a seqlock rather than a
     semaphore.

Signed-off-by: David Howells <dhowe...@redhat.com>
---

 fs/afs/callback.c  |    2 
 fs/afs/cell.c      |  913 ++++++++++++++++++++++++++++++++++++----------------
 fs/afs/internal.h  |   62 +++-
 fs/afs/main.c      |   16 +
 fs/afs/proc.c      |   15 -
 fs/afs/server.c    |    3 
 fs/afs/super.c     |   20 +
 fs/afs/vlocation.c |    6 
 fs/afs/xattr.c     |    2 
 9 files changed, 710 insertions(+), 329 deletions(-)

diff --git a/fs/afs/callback.c b/fs/afs/callback.c
index d12dffb76b67..bdccc4e72c0e 100644
--- a/fs/afs/callback.c
+++ b/fs/afs/callback.c
@@ -341,7 +341,7 @@ void afs_dispatch_give_up_callbacks(struct work_struct 
*work)
         *   had callbacks entirely, and the server will call us later to break
         *   them
         */
-       afs_fs_give_up_callbacks(server->cell->net, server, true);
+       afs_fs_give_up_callbacks(server->net, server, true);
 }
 
 /*
diff --git a/fs/afs/cell.c b/fs/afs/cell.c
index 6c49532a79de..078ffd90e5f4 100644
--- a/fs/afs/cell.c
+++ b/fs/afs/cell.c
@@ -1,6 +1,6 @@
 /* AFS cell and server record management
  *
- * Copyright (C) 2002 Red Hat, Inc. All Rights Reserved.
+ * Copyright (C) 2002, 2017 Red Hat, Inc. All Rights Reserved.
  * Written by David Howells (dhowe...@redhat.com)
  *
  * This program is free software; you can redistribute it and/or
@@ -19,128 +19,192 @@
 #include <keys/rxrpc-type.h>
 #include "internal.h"
 
+unsigned __read_mostly afs_cell_gc_delay = 10;
+
+static void afs_manage_cell(struct work_struct *);
+
+static void afs_dec_cells_outstanding(struct afs_net *net)
+{
+       if (atomic_dec_and_test(&net->cells_outstanding))
+               wake_up_atomic_t(&net->cells_outstanding);
+}
+
 /*
- * allocate a cell record and fill in its name, VL server address list and
- * allocate an anonymous key
+ * Set the cell timer to fire after a given delay, assuming it's not already
+ * set for an earlier time.
  */
-static struct afs_cell *afs_cell_alloc(struct afs_net *net,
-                                      const char *name, unsigned namelen,
-                                      char *vllist)
+static void afs_set_cell_timer(struct afs_net *net, time64_t delay)
 {
-       struct afs_cell *cell;
-       struct key *key;
-       char keyname[4 + AFS_MAXCELLNAME + 1], *cp, *dp, *next;
-       char  *dvllist = NULL, *_vllist = NULL;
-       char  delimiter = ':';
-       int ret, i;
+       if (net->live) {
+               atomic_inc(&net->cells_outstanding);
+               if (reduce_timer(&net->cells_timer, jiffies + delay * HZ))
+                       afs_dec_cells_outstanding(net);
+       }
+}
+
+/*
+ * Look up and get an activation reference on a cell record under RCU
+ * conditions.  The caller must hold the RCU read lock.
+ */
+struct afs_cell *afs_lookup_cell_rcu(struct afs_net *net,
+                                    const char *name, unsigned int namesz)
+{
+       struct afs_cell *cell = NULL;
+       struct rb_node *p;
+       unsigned int seq = 0, n;
+       int ret = 0;
+
+       _enter("%*.*s", namesz, namesz, name);
+
+       if (name && namesz == 0)
+               return ERR_PTR(-EINVAL);
+       if (namesz > AFS_MAXCELLNAME)
+               return ERR_PTR(-ENAMETOOLONG);
+
+       do {
+               /* Unfortunately, rbtree walking doesn't give reliable results
+                * under just the RCU read lock, so we have to check for
+                * changes.
+                */
+               read_seqbegin_or_lock(&net->cells_lock, &seq);
+
+               if (!name) {
+                       ret = -EDESTADDRREQ;
+                       cell = rcu_dereference_raw(net->ws_cell);
+                       if (!cell)
+                               goto done;
+
+                       afs_get_cell(cell);
+                       goto done;
+               }
+
+               p = rcu_dereference_raw(net->cells.rb_node);
+               while (p) {
+                       cell = rb_entry(p, struct afs_cell, net_node);
+
+                       n = strncasecmp(cell->name, name,
+                                       min_t(size_t, cell->name_len, namesz));
+                       if (n == 0)
+                               n = cell->name_len - namesz;
+                       if (n < 0) {
+                               p = rcu_dereference_raw(p->rb_left);
+                       } else if (n > 0) {
+                               p = rcu_dereference_raw(p->rb_right);
+                       } else {
+                               if (refcount_inc_not_zero(&cell->usage))
+                                       goto done;
+                               /* We want to repeat the search, this time with
+                                * the lock properly locked.
+                                */
+                       }
+                       cell = NULL;
+               }
+       } while (need_seqretry(&net->cells_lock, seq));
+
+       ret = -ENOENT;
 
-       _enter("%*.*s,%s", namelen, namelen, name ?: "", vllist);
+done:
+       done_seqretry(&net->cells_lock, seq);
 
-       BUG_ON(!name); /* TODO: want to look up "this cell" in the cache */
+       return ret == 0 ? cell : ERR_PTR(ret);
+}
+
+/*
+ * Set up a cell record and fill in its name, VL server address list and
+ * allocate an anonymous key
+ */
+static struct afs_cell *afs_alloc_cell(struct afs_net *net,
+                                      const char *name, unsigned int namelen,
+                                      const char *vllist)
+{
+       struct afs_cell *cell;
+       int i, ret;
 
+       ASSERT(name);
+       if (namelen == 0)
+               return ERR_PTR(-EINVAL);
        if (namelen > AFS_MAXCELLNAME) {
                _leave(" = -ENAMETOOLONG");
                return ERR_PTR(-ENAMETOOLONG);
        }
 
-       /* allocate and initialise a cell record */
-       cell = kzalloc(sizeof(struct afs_cell) + namelen + 1, GFP_KERNEL);
+       _enter("%*.*s,%s", namelen, namelen, name, vllist);
+
+       cell = kzalloc(sizeof(struct afs_cell), GFP_KERNEL);
        if (!cell) {
                _leave(" = -ENOMEM");
                return ERR_PTR(-ENOMEM);
        }
 
-       memcpy(cell->name, name, namelen);
-       cell->name[namelen] = 0;
-
-       atomic_set(&cell->usage, 1);
-       INIT_LIST_HEAD(&cell->link);
        cell->net = net;
+       cell->name_len = namelen;
+       for (i = 0; i < namelen; i++)
+               cell->name[i] = tolower(name[i]);
+
+       refcount_set(&cell->usage, 2);
+       INIT_WORK(&cell->manager, afs_manage_cell);
        rwlock_init(&cell->servers_lock);
        INIT_LIST_HEAD(&cell->servers);
        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.sin.sin_port     = htons(AFS_VL_PORT);
+               srx->transport.sin6.sin6_family = AF_INET6;
+               srx->transport.sin6.sin6_port   = htons(AFS_VL_PORT);
        }
 
-       /* if the ip address is invalid, try dns query */
-       if (!vllist || strlen(vllist) < 7) {
-               ret = dns_query("afsdb", name, namelen, "ipv4", &dvllist, NULL);
-               if (ret < 0) {
-                       if (ret == -ENODATA || ret == -EAGAIN || ret == -ENOKEY)
-                               /* translate these errors into something
-                                * userspace might understand */
-                               ret = -EDESTADDRREQ;
-                       _leave(" = %d", ret);
-                       return ERR_PTR(ret);
-               }
-               _vllist = dvllist;
+       /* Fill in the VL server list if we were given a list of addresses to
+        * use.
+        */
+       if (vllist) {
+               char delim = ':';
 
-               /* change the delimiter for user-space reply */
-               delimiter = ',';
-
-       } else {
                if (strchr(vllist, ',') || !strchr(vllist, '.'))
-                       delimiter = ',';
-               _vllist = vllist;
-       }
-
-       /* fill in the VL server list from the rest of the string */
-       do {
-               struct sockaddr_rxrpc *srx = &cell->vl_addrs[cell->vl_naddrs];
-               const char *end;
-
-               next = strchr(_vllist, delimiter);
-               if (next)
-                       *next++ = 0;
+                       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;
+                       }
 
-               if (in4_pton(_vllist, -1, (u8 
*)&srx->transport.sin6.sin6_addr.s6_addr32[3],
-                            -1, &end)) {
-                       srx->transport_len              = sizeof(struct 
sockaddr_in6);
-                       srx->transport.sin6.sin6_family = AF_INET6;
-                       srx->transport.sin6.sin6_flowinfo = 0;
-                       srx->transport.sin6.sin6_scope_id = 0;
-                       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,
-                                   -1, &end)) {
-                       srx->transport_len              = sizeof(struct 
sockaddr_in6);
-                       srx->transport.sin6.sin6_family = AF_INET6;
-                       srx->transport.sin6.sin6_flowinfo = 0;
-                       srx->transport.sin6.sin6_scope_id = 0;
-               } else {
-                       goto bad_address;
-               }
+                       cell->vl_naddrs++;
+                       if (!*vllist)
+                               break;
+                       vllist++;
 
-       } while (cell->vl_naddrs++,
-                cell->vl_naddrs < AFS_CELL_MAX_ADDRS && (_vllist = next));
+               } while (cell->vl_naddrs < AFS_CELL_MAX_ADDRS && vllist);
 
-       /* create a key to represent an anonymous user */
-       memcpy(keyname, "afs@", 4);
-       dp = keyname + 4;
-       cp = cell->name;
-       do {
-               *dp++ = toupper(*cp);
-       } while (*cp++);
-
-       key = rxrpc_get_null_key(keyname);
-       if (IS_ERR(key)) {
-               _debug("no key");
-               ret = PTR_ERR(key);
-               goto error;
+               /* Disable DNS refresh for manually-specified cells and set the
+                * no-garbage collect flag (which pins the active count).
+                */
+               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;
        }
-       cell->anonymous_key = key;
-
-       _debug("anon key %p{%x}",
-              cell->anonymous_key, key_serial(cell->anonymous_key));
 
        _leave(" = %p", cell);
        return cell;
@@ -148,92 +212,128 @@ static struct afs_cell *afs_cell_alloc(struct afs_net 
*net,
 bad_address:
        printk(KERN_ERR "kAFS: bad VL server IP address\n");
        ret = -EINVAL;
-error:
-       key_put(cell->anonymous_key);
-       kfree(dvllist);
        kfree(cell);
        _leave(" = %d", ret);
        return ERR_PTR(ret);
 }
 
 /*
- * afs_cell_crate() - create a cell record
+ * afs_lookup_cell - Look up or create a cell record.
  * @net:       The network namespace
- * @name:      is the name of the cell.
- * @namsesz:   is the strlen of the cell name.
- * @vllist:    is a colon separated list of IP addresses in "a.b.c.d" format.
- * @retref:    is T to return the cell reference when the cell exists.
+ * @name:      The name of the cell.
+ * @namesz:    The strlen of the cell name.
+ * @vllist:    A colon/comma separated list of numeric IP addresses or NULL.
+ * @excl:      T if an error should be given if the cell name already exists.
+ *
+ * Look up a cell record by name and query the DNS for VL server addresses if
+ * needed.  Note that that actual DNS query is punted off to the manager thread
+ * so that this function can return immediately if interrupted whilst allowing
+ * cell records to be shared even if not yet fully constructed.
  */
-struct afs_cell *afs_cell_create(struct afs_net *net,
-                                const char *name, unsigned namesz,
-                                char *vllist, bool retref)
+struct afs_cell *afs_lookup_cell(struct afs_net *net,
+                                const char *name, unsigned int namesz,
+                                const char *vllist, bool excl)
 {
-       struct afs_cell *cell;
-       int ret;
-
-       _enter("%*.*s,%s", namesz, namesz, name ?: "", vllist);
+       struct afs_cell *cell, *candidate, *cursor;
+       struct rb_node *parent, **pp;
+       int ret, n;
+
+       _enter("%s,%s", name, vllist);
+
+       if (!excl) {
+               rcu_read_lock();
+               cell = afs_lookup_cell_rcu(net, name, namesz);
+               rcu_read_unlock();
+               if (!IS_ERR(cell)) {
+                       if (excl) {
+                               afs_put_cell(net, cell);
+                               return ERR_PTR(-EEXIST);
+                       }
+                       goto wait_for_cell;
+               }
+       }
 
-       down_write(&net->cells_sem);
-       read_lock(&net->cells_lock);
-       list_for_each_entry(cell, &net->cells, link) {
-               if (strncasecmp(cell->name, name, namesz) == 0)
-                       goto duplicate_name;
+       /* Assume we're probably going to create a cell and preallocate and
+        * mostly set up a candidate record.  We can then use this to stash the
+        * name, the net namespace and VL server addresses.
+        *
+        * We also want to do this before we hold any locks as it may involve
+        * upcalling to userspace to make DNS queries.
+        */
+       candidate = afs_alloc_cell(net, name, namesz, vllist);
+       if (IS_ERR(candidate)) {
+               _leave(" = %ld", PTR_ERR(candidate));
+               return candidate;
        }
-       read_unlock(&net->cells_lock);
 
-       cell = afs_cell_alloc(net, name, namesz, vllist);
-       if (IS_ERR(cell)) {
-               _leave(" = %ld", PTR_ERR(cell));
-               up_write(&net->cells_sem);
-               return cell;
+       /* Find the insertion point and check to see if someone else added a
+        * cell whilst we were allocating.
+        */
+       write_seqlock(&net->cells_lock);
+
+       pp = &net->cells.rb_node;
+       parent = NULL;
+       while (*pp) {
+               parent = *pp;
+               cursor = rb_entry(parent, struct afs_cell, net_node);
+
+               n = strncasecmp(cursor->name, name,
+                               min_t(size_t, cursor->name_len, namesz));
+               if (n == 0)
+                       n = cursor->name_len - namesz;
+               if (n < 0)
+                       pp = &(*pp)->rb_left;
+               else if (n > 0)
+                       pp = &(*pp)->rb_right;
+               else
+                       goto cell_already_exists;
        }
 
-       /* add a proc directory for this cell */
-       ret = afs_proc_cell_setup(net, cell);
-       if (ret < 0)
+       cell = candidate;
+       candidate = NULL;
+       rb_link_node_rcu(&cell->net_node, parent, pp);
+       rb_insert_color(&cell->net_node, &net->cells);
+       atomic_inc(&net->cells_outstanding);
+       write_sequnlock(&net->cells_lock);
+
+       schedule_work(&cell->manager);
+
+wait_for_cell:
+       _debug("wait_for_cell");
+       ret = wait_on_bit(&cell->flags, AFS_CELL_FL_NOT_READY, 
TASK_INTERRUPTIBLE);
+       smp_rmb();
+
+       switch (READ_ONCE(cell->state)) {
+       case AFS_CELL_FAILED:
+               ret = cell->error;
+       default:
+               _debug("weird %u %d", cell->state, cell->error);
                goto error;
+       case AFS_CELL_ACTIVE:
+               break;
+       }
 
-#ifdef CONFIG_AFS_FSCACHE
-       /* put it up for caching (this never returns an error) */
-       cell->cache = fscache_acquire_cookie(afs_cache_netfs.primary_index,
-                                            &afs_cell_cache_index_def,
-                                            cell, true);
-#endif
-
-       /* add to the cell lists */
-       write_lock(&net->cells_lock);
-       list_add_tail(&cell->link, &net->cells);
-       write_unlock(&net->cells_lock);
-
-       down_write(&net->proc_cells_sem);
-       list_add_tail(&cell->proc_link, &net->proc_cells);
-       up_write(&net->proc_cells_sem);
-       up_write(&net->cells_sem);
-
-       _leave(" = %p", cell);
+       _leave(" = %p [cell]", cell);
        return cell;
 
+cell_already_exists:
+       _debug("cell exists");
+       cell = cursor;
+       if (excl) {
+               ret = -EEXIST;
+       } else {
+               ASSERTCMP(refcount_read(&cursor->usage), >=, 1);
+               refcount_inc(&cursor->usage);
+               ret = 0;
+       }
+       write_sequnlock(&net->cells_lock);
+       kfree(candidate);
+       if (ret == 0)
+               goto wait_for_cell;
 error:
-       up_write(&net->cells_sem);
-       key_put(cell->anonymous_key);
-       kfree(cell);
-       _leave(" = %d", ret);
+       afs_put_cell(net, cell);
+       _leave(" = %d [error]", ret);
        return ERR_PTR(ret);
-
-duplicate_name:
-       if (retref && !IS_ERR(cell))
-               afs_get_cell(cell);
-
-       read_unlock(&net->cells_lock);
-       up_write(&net->cells_sem);
-
-       if (retref) {
-               _leave(" = %p", cell);
-               return cell;
-       }
-
-       _leave(" = -EEXIST");
-       return ERR_PTR(-EEXIST);
 }
 
 /*
@@ -241,10 +341,11 @@ struct afs_cell *afs_cell_create(struct afs_net *net,
  * - can be called with a module parameter string
  * - can be called from a write to /proc/fs/afs/rootcell
  */
-int afs_cell_init(struct afs_net *net, char *rootcell)
+int afs_cell_init(struct afs_net *net, const char *rootcell)
 {
        struct afs_cell *old_root, *new_root;
-       char *cp;
+       const char *cp, *vllist;
+       size_t len;
 
        _enter("");
 
@@ -257,223 +358,467 @@ int afs_cell_init(struct afs_net *net, char *rootcell)
        }
 
        cp = strchr(rootcell, ':');
-       if (!cp)
+       if (!cp) {
                _debug("kAFS: no VL server IP addresses specified");
-       else
-               *cp++ = 0;
+               vllist = NULL;
+               len = strlen(rootcell);
+       } else {
+               vllist = cp + 1;
+               len = cp - rootcell;
+       }
 
        /* allocate a cell record for the root cell */
-       new_root = afs_cell_create(net, rootcell, strlen(rootcell), cp, false);
+       new_root = afs_lookup_cell(net, rootcell, len, vllist, false);
        if (IS_ERR(new_root)) {
                _leave(" = %ld", PTR_ERR(new_root));
                return PTR_ERR(new_root);
        }
 
+       set_bit(AFS_CELL_FL_NO_GC, &new_root->flags);
+       afs_get_cell(new_root);
+
        /* install the new cell */
-       write_lock(&net->cells_lock);
+       write_seqlock(&net->cells_lock);
        old_root = net->ws_cell;
        net->ws_cell = new_root;
-       write_unlock(&net->cells_lock);
-       afs_put_cell(old_root);
+       write_sequnlock(&net->cells_lock);
 
+       afs_put_cell(net, old_root);
        _leave(" = 0");
        return 0;
 }
 
 /*
- * lookup a cell record
+ * Update a cell's VL server address list from the DNS.
  */
-struct afs_cell *afs_cell_lookup(struct afs_net *net,
-                                const char *name, unsigned namesz,
-                                bool dns_cell)
+static void afs_update_cell(struct afs_cell *cell)
 {
-       struct afs_cell *cell;
+       time64_t now, expiry;
+       char *vllist = NULL;
+       int ret;
 
-       _enter("\"%*.*s\",", namesz, namesz, name ?: "");
+       _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;
+
+       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:
+               /* 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;
+       }
 
-       down_read(&net->cells_sem);
-       read_lock(&net->cells_lock);
+parse_dns_data:
+       write_seqlock(&cell->vl_addrs_lock);
 
-       if (name) {
-               /* if the cell was named, look for it in the cell record list */
-               list_for_each_entry(cell, &net->cells, link) {
-                       if (strncmp(cell->name, name, namesz) == 0) {
-                               afs_get_cell(cell);
-                               goto found;
-                       }
-               }
-               cell = ERR_PTR(-ENOENT);
-               if (dns_cell)
-                       goto create_cell;
-       found:
-               ;
-       } else {
-               cell = net->ws_cell;
-               if (!cell) {
-                       /* this should not happen unless user tries to mount
-                        * when root cell is not set. Return an impossibly
-                        * bizarre errno to alert the user. Things like
-                        * ENOENT might be "more appropriate" but they happen
-                        * for other reasons.
-                        */
-                       cell = ERR_PTR(-EDESTADDRREQ);
+       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 {
-                       afs_get_cell(cell);
+                       goto bad_address;
                }
 
-       }
-
-       read_unlock(&net->cells_lock);
-       up_read(&net->cells_sem);
-       _leave(" = %p", cell);
-       return cell;
+               cell->vl_naddrs++;
+               if (!*vllist)
+                       break;
+               vllist++;
 
-create_cell:
-       read_unlock(&net->cells_lock);
-       up_read(&net->cells_sem);
+       } while (cell->vl_naddrs < AFS_CELL_MAX_ADDRS);
 
-       cell = afs_cell_create(net, name, namesz, NULL, true);
+       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]));
 
-       _leave(" = %p", cell);
-       return cell;
+       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:
+       _leave("");
 }
 
-#if 0
 /*
- * try and get a cell record
+ * Destroy a cell record
  */
-struct afs_cell *afs_get_cell_maybe(struct afs_cell *cell)
+static void afs_cell_destroy(struct rcu_head *rcu)
 {
-       write_lock(&net->cells_lock);
+       struct afs_cell *cell = container_of(rcu, struct afs_cell, rcu);
 
-       if (cell && !list_empty(&cell->link))
-               afs_get_cell(cell);
-       else
-               cell = NULL;
+       _enter("%p{%s}", cell, cell->name);
 
-       write_unlock(&net->cells_lock);
-       return cell;
+       ASSERTCMP(refcount_read(&cell->usage), ==, 0);
+
+       key_put(cell->anonymous_key);
+       kfree(cell);
+
+       _leave(" [destroyed]");
 }
-#endif  /*  0  */
 
 /*
- * destroy a cell record
+ * Queue the cell manager.
  */
-void afs_put_cell(struct afs_cell *cell)
+static void afs_queue_cell_manager(struct afs_net *net)
 {
-       if (!cell)
-               return;
+       int outstanding = atomic_inc_return(&net->cells_outstanding);
+
+       _enter("%d", outstanding);
 
-       _enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name);
+       if (!schedule_work(&net->cells_manager))
+               afs_dec_cells_outstanding(net);
+}
 
-       ASSERTCMP(atomic_read(&cell->usage), >, 0);
+/*
+ * Cell management timer.  We have an increment on cells_outstanding that we
+ * need to pass along to the work item.
+ */
+void afs_cells_timer(unsigned long data)
+{
+       struct afs_net *net = (struct afs_net *)data;
+
+       _enter("");
+       if (!schedule_work(&net->cells_manager))
+               afs_dec_cells_outstanding(net);
+}
 
-       /* to prevent a race, the decrement and the dequeue must be effectively
-        * atomic */
-       write_lock(&cell->net->cells_lock);
+/*
+ * Drop a reference on a cell record.
+ */
+void afs_put_cell(struct afs_net *net, struct afs_cell *cell)
+{
+       time64_t now, expire_delay;
 
-       if (likely(!atomic_dec_and_test(&cell->usage))) {
-               write_unlock(&cell->net->cells_lock);
-               _leave("");
+       if (!cell)
                return;
-       }
 
-       ASSERT(list_empty(&cell->servers));
-       ASSERT(list_empty(&cell->vl_list));
+       _enter("%s", cell->name);
 
-       wake_up(&cell->net->cells_freeable_wq);
+       now = ktime_get_real_seconds();
+       cell->last_inactive = now;
+       expire_delay = 0;
+       if (!test_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags) &&
+           !test_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags))
+               expire_delay = afs_cell_gc_delay;
 
-       write_unlock(&cell->net->cells_lock);
+       if (refcount_dec_return(&cell->usage) > 1)
+               return;
 
-       _leave(" [unused]");
+       /* 'cell' may now be garbage collected. */
+       afs_set_cell_timer(net, expire_delay);
 }
 
 /*
- * destroy a cell record
- * - must be called with the net->cells_sem write-locked
- * - cell->link should have been broken by the caller
+ * Allocate a key to use as a placeholder for anonymous user security.
  */
-static void afs_cell_destroy(struct afs_net *net, struct afs_cell *cell)
+static int afs_alloc_anon_key(struct afs_cell *cell)
 {
-       _enter("%p{%d,%s}", cell, atomic_read(&cell->usage), cell->name);
+       struct key *key;
+       char keyname[4 + AFS_MAXCELLNAME + 1], *cp, *dp;
 
-       ASSERTCMP(atomic_read(&cell->usage), >=, 0);
-       ASSERT(list_empty(&cell->link));
+       /* Create a key to represent an anonymous user. */
+       memcpy(keyname, "afs@", 4);
+       dp = keyname + 4;
+       cp = cell->name;
+       do {
+               *dp++ = toupper(*cp);
+       } while (*cp++);
 
-       /* wait for everyone to stop using the cell */
-       if (atomic_read(&cell->usage) > 0) {
-               DECLARE_WAITQUEUE(myself, current);
+       key = rxrpc_get_null_key(keyname);
+       if (IS_ERR(key))
+               return PTR_ERR(key);
 
-               _debug("wait for cell %s", cell->name);
-               set_current_state(TASK_UNINTERRUPTIBLE);
-               add_wait_queue(&net->cells_freeable_wq, &myself);
+       cell->anonymous_key = key;
 
-               while (atomic_read(&cell->usage) > 0) {
-                       schedule();
-                       set_current_state(TASK_UNINTERRUPTIBLE);
-               }
+       _debug("anon key %p{%x}",
+              cell->anonymous_key, key_serial(cell->anonymous_key));
+       return 0;
+}
 
-               remove_wait_queue(&net->cells_freeable_wq, &myself);
-               set_current_state(TASK_RUNNING);
+/*
+ * Activate a cell.
+ */
+static int afs_activate_cell(struct afs_net *net, struct afs_cell *cell)
+{
+       int ret;
+
+       if (!cell->anonymous_key) {
+               ret = afs_alloc_anon_key(cell);
+               if (ret < 0)
+                       return ret;
        }
 
-       _debug("cell dead");
-       ASSERTCMP(atomic_read(&cell->usage), ==, 0);
-       ASSERT(list_empty(&cell->servers));
-       ASSERT(list_empty(&cell->vl_list));
+#ifdef CONFIG_AFS_FSCACHE
+       cell->cache = fscache_acquire_cookie(afs_cache_netfs.primary_index,
+                                            &afs_cell_cache_index_def,
+                                            cell, true);
+#endif
+       ret = afs_proc_cell_setup(net, cell);
+       if (ret < 0)
+               return ret;
+       spin_lock(&net->proc_cells_lock);
+       list_add_tail(&cell->proc_link, &net->proc_cells);
+       spin_unlock(&net->proc_cells_lock);
+       return 0;
+}
+
+/*
+ * Deactivate a cell.
+ */
+static void afs_deactivate_cell(struct afs_net *net, struct afs_cell *cell)
+{
+       _enter("%s", cell->name);
 
        afs_proc_cell_remove(net, cell);
 
-       down_write(&net->proc_cells_sem);
+       spin_lock(&net->proc_cells_lock);
        list_del_init(&cell->proc_link);
-       up_write(&net->proc_cells_sem);
+       spin_unlock(&net->proc_cells_lock);
 
 #ifdef CONFIG_AFS_FSCACHE
        fscache_relinquish_cookie(cell->cache, 0);
+       cell->cache = NULL;
 #endif
-       key_put(cell->anonymous_key);
-       kfree(cell);
 
-       _leave(" [destroyed]");
+       _leave("");
 }
 
 /*
- * purge in-memory cell database on module unload or afs_init() failure
- * - the timeout daemon is stopped before calling this
+ * Manage a cell record, initialising and destroying it, maintaining its DNS
+ * records.
  */
-void afs_cell_purge(struct afs_net *net)
+static void afs_manage_cell(struct work_struct *work)
 {
-       struct afs_cell *cell;
+       struct afs_cell *cell = container_of(work, struct afs_cell, manager);
+       struct afs_net *net = cell->net;
+       bool deleted;
+       int ret;
+
+       _enter("%s", cell->name);
+
+again:
+       _debug("state %u", cell->state);
+       switch (cell->state) {
+       case AFS_CELL_INACTIVE:
+       case AFS_CELL_FAILED:
+               write_seqlock(&net->cells_lock);
+               deleted = refcount_dec_if_one(&cell->usage);
+               if (deleted)
+                       rb_erase(&cell->net_node, &net->cells);
+               write_sequnlock(&net->cells_lock);
+               if (deleted)
+                       goto final_destruction;
+               if (cell->state == AFS_CELL_FAILED)
+                       goto done;
+               cell->state = AFS_CELL_UNSET;
+               goto again;
+
+       case AFS_CELL_UNSET:
+               cell->state = AFS_CELL_ACTIVATING;
+               goto again;
+
+       case AFS_CELL_ACTIVATING:
+               ret = afs_activate_cell(net, cell);
+               if (ret < 0)
+                       goto activation_failed;
+
+               cell->state = AFS_CELL_ACTIVE;
+               smp_wmb();
+               clear_bit(AFS_CELL_FL_NOT_READY, &cell->flags);
+               wake_up_bit(&cell->flags, AFS_CELL_FL_NOT_READY);
+               goto again;
+
+       case AFS_CELL_ACTIVE:
+               if (refcount_read(&cell->usage) > 1) {
+                       time64_t now = ktime_get_real_seconds();
+                       if (cell->dns_expiry <= now && net->live)
+                               afs_update_cell(cell);
+                       goto done;
+               }
+               cell->state = AFS_CELL_DEACTIVATING;
+               goto again;
+
+       case AFS_CELL_DEACTIVATING:
+               set_bit(AFS_CELL_FL_NOT_READY, &cell->flags);
+               if (refcount_read(&cell->usage) > 1)
+                       goto reverse_deactivation;
+               afs_deactivate_cell(net, cell);
+               cell->state = AFS_CELL_INACTIVE;
+               goto again;
+
+       default:
+               break;
+       }
+       _debug("bad state %u", cell->state);
+       BUG(); /* Unhandled state */
+
+activation_failed:
+       cell->error = ret;
+       afs_deactivate_cell(net, cell);
+
+       cell->state = AFS_CELL_FAILED;
+       smp_wmb();
+       if (test_and_clear_bit(AFS_CELL_FL_NOT_READY, &cell->flags))
+               wake_up_bit(&cell->flags, AFS_CELL_FL_NOT_READY);
+       goto again;
+
+reverse_deactivation:
+       cell->state = AFS_CELL_ACTIVE;
+       smp_wmb();
+       clear_bit(AFS_CELL_FL_NOT_READY, &cell->flags);
+       wake_up_bit(&cell->flags, AFS_CELL_FL_NOT_READY);
+       _leave(" [deact->act]");
+       return;
+
+done:
+       _leave(" [done %u]", cell->state);
+       return;
+
+final_destruction:
+       call_rcu(&cell->rcu, afs_cell_destroy);
+       dec_after_work(&net->cells_outstanding);
+       _leave(" [destruct %d]", atomic_read(&net->cells_outstanding));
+}
+
+/*
+ * Manage the records of cells known to a network namespace.  This includes
+ * updating the DNS records and garbage collecting unused cells that were
+ * automatically added.
+ *
+ * Note that constructed cell records may only be removed from net->cells by
+ * this work item, so it is safe for this work item to stash a cursor pointing
+ * into the tree and then return to caller (provided it skips cells that are
+ * still under construction).
+ *
+ * Note also that we were given an increment on net->cells_outstanding by
+ * whoever queued us that we need to deal with before returning.
+ */
+void afs_manage_cells(struct work_struct *work)
+{
+       struct afs_net *net = container_of(work, struct afs_net, cells_manager);
+       struct rb_node *cursor;
+       time64_t now = ktime_get_real_seconds(), next_manage = TIME64_MAX;
+       bool purging = !net->live;
 
        _enter("");
 
-       afs_put_cell(net->ws_cell);
+       /* Trawl the cell database looking for cells that have expired from
+        * lack of use and cells whose DNS results have expired and dispatch
+        * their managers.
+        */
+       read_seqlock_excl(&net->cells_lock);
 
-       down_write(&net->cells_sem);
+       for (cursor = rb_first(&net->cells); cursor; cursor = rb_next(cursor)) {
+               struct afs_cell *cell =
+                       rb_entry(cursor, struct afs_cell, net_node);
+               bool sched_cell = false;
 
-       while (!list_empty(&net->cells)) {
-               cell = NULL;
+               _debug("manage %s %u", cell->name, refcount_read(&cell->usage));
 
-               /* remove the next cell from the front of the list */
-               write_lock(&net->cells_lock);
+               ASSERTCMP(refcount_read(&cell->usage), >=, 1);
 
-               if (!list_empty(&net->cells)) {
-                       cell = list_entry(net->cells.next,
-                                         struct afs_cell, link);
-                       list_del_init(&cell->link);
+               if (purging && test_and_clear_bit(AFS_CELL_FL_NO_GC, 
&cell->flags))
+                       refcount_dec(&cell->usage);
+
+               ASSERTIFCMP(purging, refcount_read(&cell->usage), ==, 1);
+
+               if (refcount_read(&cell->usage) == 1) {
+                       time64_t expire_at = cell->last_inactive;
+
+                       if (!test_bit(AFS_CELL_FL_DNS_FAIL, &cell->flags) &&
+                           !test_bit(AFS_CELL_FL_NOT_FOUND, &cell->flags))
+                               expire_at += afs_cell_gc_delay;
+                       if (purging || expire_at <= now)
+                               sched_cell = true;
+                       else if (expire_at < next_manage)
+                               next_manage = expire_at;
                }
 
-               write_unlock(&net->cells_lock);
+               if (!purging) {
+                       if (cell->dns_expiry <= now)
+                               sched_cell = true;
+                       else if (cell->dns_expiry <= next_manage)
+                               next_manage = cell->dns_expiry;
+               }
+
+               if (sched_cell)
+                       schedule_work(&cell->manager);
+       }
+
+       read_sequnlock_excl(&net->cells_lock);
 
-               if (cell) {
-                       _debug("PURGING CELL %s (%d)",
-                              cell->name, atomic_read(&cell->usage));
+       /* Update the timer on the way out.  We have to pass an increment on
+        * cells_outstanding in the namespace that we are in to the timer or
+        * the work scheduler.
+        */
+       if (!purging && next_manage < TIME64_MAX) {
+               now = ktime_get_real_seconds();
 
-                       /* now the cell should be left with no references */
-                       afs_cell_destroy(net, cell);
+               if (next_manage - now <= 0) {
+                       if (schedule_work(&net->cells_manager))
+                               atomic_inc(&net->cells_outstanding);
+               } else {
+                       afs_set_cell_timer(net, next_manage - now);
                }
        }
 
-       up_write(&net->cells_sem);
+       dec_after_work(&net->cells_outstanding);
+       _leave(" [%d]", atomic_read(&net->cells_outstanding));
+}
+
+/*
+ * Purge in-memory cell database.
+ */
+void afs_cell_purge(struct afs_net *net)
+{
+       struct afs_cell *ws;
+
+       _enter("");
+
+       write_seqlock(&net->cells_lock);
+       ws = net->ws_cell;
+       net->ws_cell = NULL;
+       write_sequnlock(&net->cells_lock);
+       afs_put_cell(net, ws);
+
+       _debug("del timer");
+       if (del_timer_sync(&net->cells_timer))
+               atomic_dec(&net->cells_outstanding);
+
+       _debug("kick mgr");
+       afs_queue_cell_manager(net);
+
+       _debug("wait");
+       wait_on_atomic_t(&net->cells_outstanding, atomic_t_wait,
+                        TASK_UNINTERRUPTIBLE);
        _leave("");
 }
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 47d5ae08f071..f96398163a68 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -202,13 +202,14 @@ struct afs_net {
        atomic_t                nr_superblocks;
 
        /* Cell database */
-       struct list_head        cells;
+       struct rb_root          cells;
        struct afs_cell         *ws_cell;
-       rwlock_t                cells_lock;
-       struct rw_semaphore     cells_sem;
-       wait_queue_head_t       cells_freeable_wq;
+       struct work_struct      cells_manager;
+       struct timer_list       cells_timer;
+       atomic_t                cells_outstanding;
+       seqlock_t               cells_lock;
 
-       struct rw_semaphore     proc_cells_sem;
+       spinlock_t              proc_cells_lock;
        struct list_head        proc_cells;
 
        /* Volume location database */
@@ -235,14 +236,26 @@ struct afs_net {
 
 extern struct afs_net __afs_net;// Dummy AFS network namespace; TODO: replace 
with real netns
 
+enum afs_cell_state {
+       AFS_CELL_UNSET,
+       AFS_CELL_ACTIVATING,
+       AFS_CELL_ACTIVE,
+       AFS_CELL_DEACTIVATING,
+       AFS_CELL_INACTIVE,
+       AFS_CELL_FAILED,
+};
+
 /*
  * AFS cell record
  */
 struct afs_cell {
-       atomic_t                usage;
-       struct list_head        link;           /* main cell list link */
-       struct afs_net          *net;           /* The network namespace */
+       union {
+               struct rcu_head rcu;
+               struct rb_node  net_node;       /* Node in net->cells */
+       };
+       struct afs_net          *net;
        struct key              *anonymous_key; /* anonymous user key for this 
cell */
+       struct work_struct      manager;        /* Manager for init/deinit/dns 
*/
        struct list_head        proc_link;      /* /proc cell list link */
 #ifdef CONFIG_AFS_FSCACHE
        struct fscache_cookie   *cache;         /* caching cookie */
@@ -255,12 +268,26 @@ struct afs_cell {
        /* volume location record management */
        struct rw_semaphore     vl_sem;         /* volume management 
serialisation semaphore */
        struct list_head        vl_list;        /* cell's active VL record list 
*/
+       time64_t                dns_expiry;     /* Time AFSDB/SRV record 
expires */
+       time64_t                last_inactive;  /* Time of last drop of usage 
count */
+       refcount_t              usage;
+       unsigned long           flags;
+#define AFS_CELL_FL_NOT_READY  0               /* The cell record is not ready 
for use */
+#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 */
+       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 */
-
-       char                    name[0];        /* cell name - must go last */
+       u8                      name_len;       /* Length of name */
+       char                    name[64 + 1];   /* Cell name, case-flattened 
and NUL-padded */
 };
 
 /*
@@ -310,6 +337,7 @@ 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_net          *net;           /* The network namespace */
        struct afs_cell         *cell;          /* cell in which server resides 
*/
        struct list_head        link;           /* link in cell's server list */
        struct list_head        grave;          /* link in master graveyard 
list */
@@ -476,12 +504,14 @@ extern void afs_flush_callback_breaks(struct afs_server 
*);
 /*
  * cell.c
  */
-#define afs_get_cell(C) do { atomic_inc(&(C)->usage); } while(0)
-extern int __net_init afs_cell_init(struct afs_net *, char *);
-extern struct afs_cell *afs_cell_create(struct afs_net *, const char *, 
unsigned, char *, bool);
-extern struct afs_cell *afs_cell_lookup(struct afs_net *, const char *, 
unsigned, bool);
-extern struct afs_cell *afs_grab_cell(struct afs_cell *);
-extern void afs_put_cell(struct afs_cell *);
+#define afs_get_cell(C) do { refcount_inc(&(C)->usage); } while(0)
+extern int __net_init 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 void afs_put_cell(struct afs_net *, struct afs_cell *);
+extern void afs_manage_cells(struct work_struct *);
+extern void afs_cells_timer(unsigned long);
 extern void __net_exit afs_cell_purge(struct afs_net *);
 
 /*
diff --git a/fs/afs/main.c b/fs/afs/main.c
index 87b1a9c8000d..e0342e445099 100644
--- a/fs/afs/main.c
+++ b/fs/afs/main.c
@@ -46,12 +46,15 @@ static int __net_init afs_net_init(struct afs_net *net)
 
        INIT_WORK(&net->charge_preallocation_work, afs_charge_preallocation);
        mutex_init(&net->socket_mutex);
-       INIT_LIST_HEAD(&net->cells);
-       rwlock_init(&net->cells_lock);
-       init_rwsem(&net->cells_sem);
-       init_waitqueue_head(&net->cells_freeable_wq);
-       init_rwsem(&net->proc_cells_sem);
+
+       net->cells = RB_ROOT;
+       seqlock_init(&net->cells_lock);
+       INIT_WORK(&net->cells_manager, afs_manage_cells);
+       setup_timer(&net->cells_timer, afs_cells_timer, (unsigned long)net);
+
+       spin_lock_init(&net->proc_cells_lock);
        INIT_LIST_HEAD(&net->proc_cells);
+
        INIT_LIST_HEAD(&net->vl_updates);
        INIT_LIST_HEAD(&net->vl_graveyard);
        INIT_DELAYED_WORK(&net->vl_reaper, afs_vlocation_reaper);
@@ -82,11 +85,14 @@ static int __net_init afs_net_init(struct afs_net *net)
        return 0;
 
 error_open_socket:
+       net->live = false;
        afs_vlocation_purge(net);
        afs_cell_purge(net);
 error_cell_init:
+       net->live = false;
        afs_proc_cleanup(net);
 error_proc:
+       net->live = false;
        return ret;
 }
 
diff --git a/fs/afs/proc.c b/fs/afs/proc.c
index ee10f0089d87..df3614306056 100644
--- a/fs/afs/proc.c
+++ b/fs/afs/proc.c
@@ -186,7 +186,7 @@ static void *afs_proc_cells_start(struct seq_file *m, 
loff_t *_pos)
 {
        struct afs_net *net = afs_seq2net(m);
 
-       down_read(&net->proc_cells_sem);
+       rcu_read_lock();
        return seq_list_start_head(&net->proc_cells, *_pos);
 }
 
@@ -205,9 +205,7 @@ static void *afs_proc_cells_next(struct seq_file *m, void 
*v, loff_t *pos)
  */
 static void afs_proc_cells_stop(struct seq_file *m, void *v)
 {
-       struct afs_net *net = afs_seq2net(m);
-
-       up_read(&net->proc_cells_sem);
+       rcu_read_unlock();
 }
 
 /*
@@ -225,8 +223,7 @@ static int afs_proc_cells_show(struct seq_file *m, void *v)
        }
 
        /* display one cell per line on subsequent lines */
-       seq_printf(m, "%3d %s\n",
-                  atomic_read(&cell->usage), cell->name);
+       seq_printf(m, "%3u %s\n", refcount_read(&cell->usage), cell->name);
        return 0;
 }
 
@@ -279,13 +276,13 @@ static ssize_t afs_proc_cells_write(struct file *file, 
const char __user *buf,
        if (strcmp(kbuf, "add") == 0) {
                struct afs_cell *cell;
 
-               cell = afs_cell_create(net, name, strlen(name), args, false);
+               cell = afs_lookup_cell(net, name, strlen(name), args, true);
                if (IS_ERR(cell)) {
                        ret = PTR_ERR(cell);
                        goto done;
                }
 
-               afs_put_cell(cell);
+               set_bit(AFS_CELL_FL_NO_GC, &cell->flags);
                printk("kAFS: Added new cell '%s'\n", name);
        } else {
                goto inval;
@@ -354,7 +351,7 @@ int afs_proc_cell_setup(struct afs_net *net, struct 
afs_cell *cell)
 {
        struct proc_dir_entry *dir;
 
-       _enter("%p{%s}", cell, cell->name);
+       _enter("%p{%s},%p", cell, cell->name, net->proc_afs);
 
        dir = proc_mkdir(cell->name, net->proc_afs);
        if (!dir)
diff --git a/fs/afs/server.c b/fs/afs/server.c
index 9b38f386a142..57c2f605e11b 100644
--- a/fs/afs/server.c
+++ b/fs/afs/server.c
@@ -68,6 +68,7 @@ static struct afs_server *afs_alloc_server(struct afs_cell 
*cell,
        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);
@@ -254,7 +255,7 @@ static void afs_destroy_server(struct afs_server *server)
        ASSERTCMP(server->cb_break_head, ==, server->cb_break_tail);
        ASSERTCMP(atomic_read(&server->cb_break_n), ==, 0);
 
-       afs_put_cell(server->cell);
+       afs_put_cell(server->net, server->cell);
        kfree(server);
 }
 
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 1bfc7b28700b..5113a48f907d 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -200,13 +200,14 @@ static int afs_parse_options(struct afs_mount_params 
*params,
                token = match_token(p, afs_options_list, args);
                switch (token) {
                case afs_opt_cell:
-                       cell = afs_cell_lookup(params->net,
-                                              args[0].from,
-                                              args[0].to - args[0].from,
-                                              false);
+                       rcu_read_lock();
+                       cell = afs_lookup_cell_rcu(params->net,
+                                                  args[0].from,
+                                                  args[0].to - args[0].from);
+                       rcu_read_unlock();
                        if (IS_ERR(cell))
                                return PTR_ERR(cell);
-                       afs_put_cell(params->cell);
+                       afs_put_cell(params->net, params->cell);
                        params->cell = cell;
                        break;
 
@@ -308,13 +309,14 @@ static int afs_parse_device_name(struct afs_mount_params 
*params,
 
        /* lookup the cell record */
        if (cellname || !params->cell) {
-               cell = afs_cell_lookup(params->net, cellname, cellnamesz, true);
+               cell = afs_lookup_cell(params->net, cellname, cellnamesz,
+                                      NULL, false);
                if (IS_ERR(cell)) {
                        printk(KERN_ERR "kAFS: unable to lookup cell '%*.*s'\n",
                               cellnamesz, cellnamesz, cellname ?: "");
                        return PTR_ERR(cell);
                }
-               afs_put_cell(params->cell);
+               afs_put_cell(params->net, params->cell);
                params->cell = cell;
        }
 
@@ -474,7 +476,7 @@ static struct dentry *afs_mount(struct file_system_type 
*fs_type,
                kfree(as);
        }
 
-       afs_put_cell(params.cell);
+       afs_put_cell(params.net, params.cell);
        kfree(new_opts);
        _leave(" = 0 [%p]", sb);
        return dget(sb->s_root);
@@ -488,7 +490,7 @@ static struct dentry *afs_mount(struct file_system_type 
*fs_type,
 error_vol:
        afs_put_volume(params.net, vol);
 error:
-       afs_put_cell(params.cell);
+       afs_put_cell(params.net, params.cell);
        key_put(params.key);
        kfree(new_opts);
        _leave(" = %d", ret);
diff --git a/fs/afs/vlocation.c b/fs/afs/vlocation.c
index 4f8c15c09a6d..ec5ab8dc9bc8 100644
--- a/fs/afs/vlocation.c
+++ b/fs/afs/vlocation.c
@@ -480,11 +480,11 @@ void afs_put_vlocation(struct afs_net *net, struct 
afs_vlocation *vl)
 /*
  * destroy a dead volume location record
  */
-static void afs_vlocation_destroy(struct afs_vlocation *vl)
+static void afs_vlocation_destroy(struct afs_net *net, struct afs_vlocation 
*vl)
 {
        _enter("%p", vl);
 
-       afs_put_cell(vl->cell);
+       afs_put_cell(net, vl->cell);
        kfree(vl);
 }
 
@@ -539,7 +539,7 @@ void afs_vlocation_reaper(struct work_struct *work)
        while (!list_empty(&corpses)) {
                vl = list_entry(corpses.next, struct afs_vlocation, grave);
                list_del(&vl->grave);
-               afs_vlocation_destroy(vl);
+               afs_vlocation_destroy(net, vl);
        }
 
        _leave("");
diff --git a/fs/afs/xattr.c b/fs/afs/xattr.c
index 2830e4f48d85..e58e00ee9747 100644
--- a/fs/afs/xattr.c
+++ b/fs/afs/xattr.c
@@ -45,7 +45,7 @@ static int afs_xattr_get_cell(const struct xattr_handler 
*handler,
        struct afs_cell *cell = vnode->volume->cell;
        size_t namelen;
 
-       namelen = strlen(cell->name);
+       namelen = cell->name_len;
        if (size == 0)
                return namelen;
        if (namelen > size)

Reply via email to