#include <linux/dns_resolver.h>
 #include <linux/sched.h>
 #include <linux/inet.h>
+#include <linux/namei.h>
 #include <keys/rxrpc-type.h>
 #include "internal.h"
 
        ret = afs_proc_cell_setup(cell);
        if (ret < 0)
                return ret;
-       spin_lock(&net->proc_cells_lock);
+
+       mutex_lock(&net->proc_cells_lock);
        list_add_tail(&cell->proc_link, &net->proc_cells);
-       spin_unlock(&net->proc_cells_lock);
+       afs_dynroot_mkdir(net, cell);
+       mutex_unlock(&net->proc_cells_lock);
        return 0;
 }
 
 
        afs_proc_cell_remove(cell);
 
-       spin_lock(&net->proc_cells_lock);
+       mutex_lock(&net->proc_cells_lock);
        list_del_init(&cell->proc_link);
-       spin_unlock(&net->proc_cells_lock);
+       afs_dynroot_rmdir(net, cell);
+       mutex_unlock(&net->proc_cells_lock);
 
 #ifdef CONFIG_AFS_FSCACHE
        fscache_relinquish_cookie(cell->cache, NULL, false);
 
-/* dir.c: AFS dynamic root handling
+/* AFS dynamic root handling
  *
  * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
  * Written by David Howells (dhowells@redhat.com)
        .d_release      = afs_d_release,
        .d_automount    = afs_d_automount,
 };
+
+/*
+ * Create a manually added cell mount directory.
+ * - The caller must hold net->proc_cells_lock
+ */
+int afs_dynroot_mkdir(struct afs_net *net, struct afs_cell *cell)
+{
+       struct super_block *sb = net->dynroot_sb;
+       struct dentry *root, *subdir;
+       int ret;
+
+       if (!sb || atomic_read(&sb->s_active) == 0)
+               return 0;
+
+       /* Let the ->lookup op do the creation */
+       root = sb->s_root;
+       inode_lock(root->d_inode);
+       subdir = lookup_one_len(cell->name, root, cell->name_len);
+       if (IS_ERR(subdir)) {
+               ret = PTR_ERR(subdir);
+               goto unlock;
+       }
+
+       /* Note that we're retaining an extra ref on the dentry */
+       subdir->d_fsdata = (void *)1UL;
+       ret = 0;
+unlock:
+       inode_unlock(root->d_inode);
+       return ret;
+}
+
+/*
+ * Remove a manually added cell mount directory.
+ * - The caller must hold net->proc_cells_lock
+ */
+void afs_dynroot_rmdir(struct afs_net *net, struct afs_cell *cell)
+{
+       struct super_block *sb = net->dynroot_sb;
+       struct dentry *root, *subdir;
+
+       if (!sb || atomic_read(&sb->s_active) == 0)
+               return;
+
+       root = sb->s_root;
+       inode_lock(root->d_inode);
+
+       /* Don't want to trigger a lookup call, which will re-add the cell */
+       subdir = try_lookup_one_len(cell->name, root, cell->name_len);
+       if (IS_ERR_OR_NULL(subdir)) {
+               _debug("lookup %ld", PTR_ERR(subdir));
+               goto no_dentry;
+       }
+
+       _debug("rmdir %pd %u", subdir, d_count(subdir));
+
+       if (subdir->d_fsdata) {
+               _debug("unpin %u", d_count(subdir));
+               subdir->d_fsdata = NULL;
+               dput(subdir);
+       }
+       dput(subdir);
+no_dentry:
+       inode_unlock(root->d_inode);
+       _leave("");
+}
+
+/*
+ * Populate a newly created dynamic root with cell names.
+ */
+int afs_dynroot_populate(struct super_block *sb)
+{
+       struct afs_cell *cell;
+       struct afs_net *net = afs_sb2net(sb);
+       int ret;
+
+       if (mutex_lock_interruptible(&net->proc_cells_lock) < 0)
+               return -ERESTARTSYS;
+
+       net->dynroot_sb = sb;
+       list_for_each_entry(cell, &net->proc_cells, proc_link) {
+               ret = afs_dynroot_mkdir(net, cell);
+               if (ret < 0)
+                       goto error;
+       }
+
+       ret = 0;
+out:
+       mutex_unlock(&net->proc_cells_lock);
+       return ret;
+
+error:
+       net->dynroot_sb = NULL;
+       goto out;
+}
+
+/*
+ * When a dynamic root that's in the process of being destroyed, depopulate it
+ * of pinned directories.
+ */
+void afs_dynroot_depopulate(struct super_block *sb)
+{
+       struct afs_net *net = afs_sb2net(sb);
+       struct dentry *root = sb->s_root, *subdir, *tmp;
+
+       /* Prevent more subdirs from being created */
+       mutex_lock(&net->proc_cells_lock);
+       if (net->dynroot_sb == sb)
+               net->dynroot_sb = NULL;
+       mutex_unlock(&net->proc_cells_lock);
+
+       inode_lock(root->d_inode);
+
+       /* Remove all the pins for dirs created for manually added cells */
+       list_for_each_entry_safe(subdir, tmp, &root->d_subdirs, d_child) {
+               if (subdir->d_fsdata) {
+                       subdir->d_fsdata = NULL;
+                       dput(subdir);
+               }
+       }
+
+       inode_unlock(root->d_inode);
+}
 
        atomic_t                cells_outstanding;
        seqlock_t               cells_lock;
 
-       spinlock_t              proc_cells_lock;
+       struct mutex            proc_cells_lock;
        struct list_head        proc_cells;
 
        /* Known servers.  Theoretically each fileserver can only be in one
        struct mutex            lock_manager_mutex;
 
        /* Misc */
+       struct super_block      *dynroot_sb;    /* Dynamic root mount superblock */
        struct proc_dir_entry   *proc_afs;      /* /proc/net/afs directory */
        struct afs_sysnames     *sysnames;
        rwlock_t                sysnames_lock;
 extern const struct dentry_operations afs_dynroot_dentry_operations;
 
 extern struct inode *afs_try_auto_mntpt(struct dentry *, struct inode *);
+extern int afs_dynroot_mkdir(struct afs_net *, struct afs_cell *);
+extern void afs_dynroot_rmdir(struct afs_net *, struct afs_cell *);
+extern int afs_dynroot_populate(struct super_block *);
+extern void afs_dynroot_depopulate(struct super_block *);
 
 /*
  * file.c
 
        INIT_WORK(&net->cells_manager, afs_manage_cells);
        timer_setup(&net->cells_timer, afs_cells_timer, 0);
 
-       spin_lock_init(&net->proc_cells_lock);
+       mutex_init(&net->proc_cells_lock);
        INIT_LIST_HEAD(&net->proc_cells);
 
        seqlock_init(&net->fs_lock);
 
 
        return (as->net_ns == as1->net_ns &&
                as->volume &&
-               as->volume->vid == as1->volume->vid);
+               as->volume->vid == as1->volume->vid &&
+               !as->dyn_root);
 }
 
 static int afs_dynroot_test_super(struct super_block *sb, void *data)
 {
-       return false;
+       struct afs_super_info *as1 = data;
+       struct afs_super_info *as = AFS_FS_S(sb);
+
+       return (as->net_ns == as1->net_ns &&
+               as->dyn_root);
 }
 
 static int afs_set_super(struct super_block *sb, void *data)
        if (!sb->s_root)
                goto error;
 
-       if (params->dyn_root)
+       if (as->dyn_root) {
                sb->s_d_op = &afs_dynroot_dentry_operations;
-       else
+               ret = afs_dynroot_populate(sb);
+               if (ret < 0)
+                       goto error;
+       } else {
                sb->s_d_op = &afs_fs_dentry_operations;
+       }
 
        _leave(" = 0");
        return 0;
        }
 }
 
+static void afs_kill_super(struct super_block *sb)
+{
+       struct afs_super_info *as = AFS_FS_S(sb);
+       struct afs_net *net = afs_net(as->net_ns);
+
+       if (as->dyn_root)
+               afs_dynroot_depopulate(sb);
+       
+       /* Clear the callback interests (which will do ilookup5) before
+        * deactivating the superblock.
+        */
+       if (as->volume)
+               afs_clear_callback_interests(net, as->volume->servers);
+       kill_anon_super(sb);
+       if (as->volume)
+               afs_deactivate_volume(as->volume);
+       afs_destroy_sbi(as);
+}
+
 /*
  * get an AFS superblock
  */
        return ERR_PTR(ret);
 }
 
-static void afs_kill_super(struct super_block *sb)
-{
-       struct afs_super_info *as = AFS_FS_S(sb);
-
-       /* Clear the callback interests (which will do ilookup5) before
-        * deactivating the superblock.
-        */
-       if (as->volume)
-               afs_clear_callback_interests(afs_net(as->net_ns),
-                                            as->volume->servers);
-       kill_anon_super(sb);
-       if (as->volume)
-               afs_deactivate_volume(as->volume);
-       afs_destroy_sbi(as);
-}
-
 /*
  * Initialise an inode cache slab element prior to any use.  Note that
  * afs_alloc_inode() *must* reset anything that could incorrectly leak from one
 
        return inode_permission(base->d_inode, MAY_EXEC);
 }
 
+/**
+ * try_lookup_one_len - filesystem helper to lookup single pathname component
+ * @name:      pathname component to lookup
+ * @base:      base directory to lookup from
+ * @len:       maximum length @len should be interpreted to
+ *
+ * Look up a dentry by name in the dcache, returning NULL if it does not
+ * currently exist.  The function does not try to create a dentry.
+ *
+ * Note that this routine is purely a helper for filesystem usage and should
+ * not be called by generic code.
+ *
+ * The caller must hold base->i_mutex.
+ */
+struct dentry *try_lookup_one_len(const char *name, struct dentry *base, int len)
+{
+       struct qstr this;
+       int err;
+
+       WARN_ON_ONCE(!inode_is_locked(base->d_inode));
+
+       err = lookup_one_len_common(name, base, len, &this);
+       if (err)
+               return ERR_PTR(err);
+
+       return lookup_dcache(&this, base, 0);
+}
+EXPORT_SYMBOL(try_lookup_one_len);
+
 /**
  * lookup_one_len - filesystem helper to lookup single pathname component
  * @name:      pathname component to lookup
 
 extern struct dentry *kern_path_locked(const char *, struct path *);
 extern int kern_path_mountpoint(int, const char *, struct path *, unsigned int);
 
+extern struct dentry *try_lookup_one_len(const char *, struct dentry *, int);
 extern struct dentry *lookup_one_len(const char *, struct dentry *, int);
 extern struct dentry *lookup_one_len_unlocked(const char *, struct dentry *, int);