[PATCH] afs: Support the AFS dynamic root

From: David Howells
Date: Thu Jan 18 2018 - 08:27:04 EST


Support the AFS dynamic root which is a pseudo-volume that doesn't connect
to any server resource, but rather is just a root directory that
dynamically creates mountpoint directories where the name of such a
directory is the name of the cell.

Such a mount can be created thus:

mount -t afs none /afs -o dyn

Dynamic root superblocks aren't shared except by bind mounts and
propagation. Cell root volumes can then be mounted by referring to them by
name, e.g.:

ls /afs/grand.central.org/

The kernel will upcall to consult the DNS if the address wasn't supplied
directly.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

Documentation/filesystems/afs.txt | 17 +++++
fs/afs/dir.c | 86 ++++++++++++++++++++++-----
fs/afs/inode.c | 48 +++++++++------
fs/afs/internal.h | 12 ++--
fs/afs/mntpt.c | 14 ++--
fs/afs/super.c | 119 +++++++++++++++++++++++++------------
6 files changed, 216 insertions(+), 80 deletions(-)

diff --git a/Documentation/filesystems/afs.txt b/Documentation/filesystems/afs.txt
index ba99b5ac4fd8..c5254f6d234d 100644
--- a/Documentation/filesystems/afs.txt
+++ b/Documentation/filesystems/afs.txt
@@ -7,6 +7,7 @@ Contents:
- Overview.
- Usage.
- Mountpoints.
+ - Dynamic root.
- Proc filesystem.
- The cell database.
- Security.
@@ -127,6 +128,22 @@ mounted on /afs in one go by doing:
umount /afs


+============
+DYNAMIC ROOT
+============
+
+A mount option is available to create a serverless mount that is only usable
+for dynamic lookup. Creating such a mount can be done by, for example:
+
+ mount -t afs none /afs -o dyn
+
+This creates a mount that just has an empty directory at the root. Attempting
+to look up a name in this directory will cause a mountpoint to be created that
+looks up a cell of the same name, for example:
+
+ ls /afs/grand.central.org/
+
+
===============
PROC FILESYSTEM
===============
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 23c7f395d718..3e7e2b53ce00 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -21,6 +21,8 @@

static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags);
+static struct dentry *afs_dynroot_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags);
static int afs_dir_open(struct inode *inode, struct file *file);
static int afs_readdir(struct file *file, struct dir_context *ctx);
static int afs_d_revalidate(struct dentry *dentry, unsigned int flags);
@@ -64,6 +66,17 @@ const struct inode_operations afs_dir_inode_operations = {
.listxattr = afs_listxattr,
};

+const struct file_operations afs_dynroot_file_operations = {
+ .open = dcache_dir_open,
+ .release = dcache_dir_close,
+ .iterate_shared = dcache_readdir,
+ .llseek = dcache_dir_lseek,
+};
+
+const struct inode_operations afs_dynroot_inode_operations = {
+ .lookup = afs_dynroot_lookup,
+};
+
const struct dentry_operations afs_fs_dentry_operations = {
.d_revalidate = afs_d_revalidate,
.d_delete = afs_d_delete,
@@ -471,22 +484,20 @@ static int afs_do_lookup(struct inode *dir, struct dentry *dentry,
* Try to auto mount the mountpoint with pseudo directory, if the autocell
* operation is setted.
*/
-static struct inode *afs_try_auto_mntpt(
- int ret, struct dentry *dentry, struct inode *dir, struct key *key,
- struct afs_fid *fid)
+static struct inode *afs_try_auto_mntpt(struct dentry *dentry,
+ struct inode *dir, struct afs_fid *fid)
{
- const char *devname = dentry->d_name.name;
struct afs_vnode *vnode = AFS_FS_I(dir);
struct inode *inode;
+ int ret = -ENOENT;

- _enter("%d, %p{%pd}, {%x:%u}, %p",
- ret, dentry, dentry, vnode->fid.vid, vnode->fid.vnode, key);
+ _enter("%p{%pd}, {%x:%u}",
+ dentry, dentry, vnode->fid.vid, vnode->fid.vnode);

- if (ret != -ENOENT ||
- !test_bit(AFS_VNODE_AUTOCELL, &vnode->flags))
+ if (!test_bit(AFS_VNODE_AUTOCELL, &vnode->flags))
goto out;

- inode = afs_iget_autocell(dir, devname, strlen(devname), key);
+ inode = afs_iget_pseudo_dir(dir->i_sb, false);
if (IS_ERR(inode)) {
ret = PTR_ERR(inode);
goto out;
@@ -545,13 +556,16 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,

ret = afs_do_lookup(dir, dentry, &fid, key);
if (ret < 0) {
- inode = afs_try_auto_mntpt(ret, dentry, dir, key, &fid);
- if (!IS_ERR(inode)) {
- key_put(key);
- goto success;
+ if (ret == -ENOENT) {
+ inode = afs_try_auto_mntpt(dentry, dir, &fid);
+ if (!IS_ERR(inode)) {
+ key_put(key);
+ goto success;
+ }
+
+ ret = PTR_ERR(inode);
}

- ret = PTR_ERR(inode);
key_put(key);
if (ret == -ENOENT) {
d_add(dentry, NULL);
@@ -583,12 +597,53 @@ static struct dentry *afs_lookup(struct inode *dir, struct dentry *dentry,
}

/*
+ * Look up an entry in a dynroot directory.
+ */
+static struct dentry *afs_dynroot_lookup(struct inode *dir, struct dentry *dentry,
+ unsigned int flags)
+{
+ struct afs_vnode *vnode;
+ struct afs_fid fid;
+ struct inode *inode;
+ int ret;
+
+ vnode = AFS_FS_I(dir);
+
+ _enter("%pd", dentry);
+
+ ASSERTCMP(d_inode(dentry), ==, NULL);
+
+ if (dentry->d_name.len >= AFSNAMEMAX) {
+ _leave(" = -ENAMETOOLONG");
+ return ERR_PTR(-ENAMETOOLONG);
+ }
+
+ inode = afs_try_auto_mntpt(dentry, dir, &fid);
+ if (IS_ERR(inode)) {
+ ret = PTR_ERR(inode);
+ if (ret == -ENOENT) {
+ d_add(dentry, NULL);
+ _leave(" = NULL [negative]");
+ return NULL;
+ }
+ _leave(" = %d [do]", ret);
+ return ERR_PTR(ret);
+ }
+
+ d_add(dentry, inode);
+ _leave(" = 0 { ino=%lu v=%u }",
+ d_inode(dentry)->i_ino, d_inode(dentry)->i_generation);
+ return NULL;
+}
+
+/*
* check that a dentry lookup hit has found a valid entry
* - NOTE! the hit can be a negative hit too, so we can't assume we have an
* inode
*/
static int afs_d_revalidate(struct dentry *dentry, unsigned int flags)
{
+ struct afs_super_info *as = dentry->d_sb->s_fs_info;
struct afs_vnode *vnode, *dir;
struct afs_fid uninitialized_var(fid);
struct dentry *parent;
@@ -600,6 +655,9 @@ static int afs_d_revalidate(struct dentry *dentry, unsigned int flags)
if (flags & LOOKUP_RCU)
return -ECHILD;

+ if (as->dyn_root)
+ return 1;
+
if (d_really_is_positive(dentry)) {
vnode = AFS_FS_I(d_inode(dentry));
_enter("{v={%x:%u} n=%pd fl=%lx},",
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 1e81864ef0b2..fee0e49abfc8 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -146,7 +146,7 @@ int afs_iget5_test(struct inode *inode, void *opaque)
*
* These pseudo inodes don't match anything.
*/
-static int afs_iget5_autocell_test(struct inode *inode, void *opaque)
+static int afs_iget5_pseudo_dir_test(struct inode *inode, void *opaque)
{
return 0;
}
@@ -168,31 +168,34 @@ static int afs_iget5_set(struct inode *inode, void *opaque)
}

/*
- * inode retrieval for autocell
+ * Create an inode for a dynamic root directory or an autocell dynamic
+ * automount dir.
*/
-struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name,
- int namesz, struct key *key)
+struct inode *afs_iget_pseudo_dir(struct super_block *sb, bool root)
{
struct afs_iget_data data;
struct afs_super_info *as;
struct afs_vnode *vnode;
- struct super_block *sb;
struct inode *inode;
static atomic_t afs_autocell_ino;

- _enter("{%x:%u},%*.*s,",
- AFS_FS_I(dir)->fid.vid, AFS_FS_I(dir)->fid.vnode,
- namesz, namesz, dev_name ?: "");
+ _enter("");

- sb = dir->i_sb;
as = sb->s_fs_info;
- data.volume = as->volume;
- data.fid.vid = as->volume->vid;
- data.fid.unique = 0;
- data.fid.vnode = 0;
+ if (as->volume) {
+ data.volume = as->volume;
+ data.fid.vid = as->volume->vid;
+ }
+ if (root) {
+ data.fid.vnode = 1;
+ data.fid.unique = 1;
+ } else {
+ data.fid.vnode = atomic_inc_return(&afs_autocell_ino);
+ data.fid.unique = 0;
+ }

- inode = iget5_locked(sb, atomic_inc_return(&afs_autocell_ino),
- afs_iget5_autocell_test, afs_iget5_set,
+ inode = iget5_locked(sb, data.fid.vnode,
+ afs_iget5_pseudo_dir_test, afs_iget5_set,
&data);
if (!inode) {
_leave(" = -ENOMEM");
@@ -210,7 +213,12 @@ struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name,

inode->i_size = 0;
inode->i_mode = S_IFDIR | S_IRUGO | S_IXUGO;
- inode->i_op = &afs_autocell_inode_operations;
+ if (root) {
+ inode->i_op = &afs_dynroot_inode_operations;
+ inode->i_fop = &afs_dynroot_file_operations;
+ } else {
+ inode->i_op = &afs_autocell_inode_operations;
+ }
set_nlink(inode, 2);
inode->i_uid = GLOBAL_ROOT_UID;
inode->i_gid = GLOBAL_ROOT_GID;
@@ -222,8 +230,12 @@ struct inode *afs_iget_autocell(struct inode *dir, const char *dev_name,
inode->i_generation = 0;

set_bit(AFS_VNODE_PSEUDODIR, &vnode->flags);
- set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags);
- inode->i_flags |= S_AUTOMOUNT | S_NOATIME;
+ if (!root) {
+ set_bit(AFS_VNODE_MOUNTPOINT, &vnode->flags);
+ inode->i_flags |= S_AUTOMOUNT;
+ }
+
+ inode->i_flags |= S_NOATIME;
unlock_new_inode(inode);
_leave(" = %p", inode);
return inode;
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 804d1f905622..f38d6a561a84 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -36,6 +36,7 @@ struct afs_mount_params {
bool rwpath; /* T if the parent should be considered R/W */
bool force; /* T to force cell type */
bool autocell; /* T if set auto mount operation */
+ bool dyn_root; /* T if dynamic root */
afs_voltype_t type; /* type of volume requested */
int volnamesz; /* size of volume name */
const char *volname; /* name of volume to mount */
@@ -186,6 +187,7 @@ struct afs_super_info {
struct afs_net *net; /* Network namespace */
struct afs_cell *cell; /* The cell in which the volume resides */
struct afs_volume *volume; /* volume record */
+ bool dyn_root; /* True if dynamic root */
};

static inline struct afs_super_info *AFS_FS_S(struct super_block *sb)
@@ -634,10 +636,13 @@ extern bool afs_cm_incoming_call(struct afs_call *);
/*
* dir.c
*/
-extern bool afs_dir_check_page(struct inode *, struct page *);
+extern const struct file_operations afs_dir_file_operations;
extern const struct inode_operations afs_dir_inode_operations;
+extern const struct file_operations afs_dynroot_file_operations;
+extern const struct inode_operations afs_dynroot_inode_operations;
extern const struct dentry_operations afs_fs_dentry_operations;
-extern const struct file_operations afs_dir_file_operations;
+
+extern bool afs_dir_check_page(struct inode *, struct page *);

/*
* file.c
@@ -695,8 +700,7 @@ extern int afs_fs_get_capabilities(struct afs_net *, struct afs_server *,
*/
extern int afs_fetch_status(struct afs_vnode *, struct key *);
extern int afs_iget5_test(struct inode *, void *);
-extern struct inode *afs_iget_autocell(struct inode *, const char *, int,
- struct key *);
+extern struct inode *afs_iget_pseudo_dir(struct super_block *, bool);
extern struct inode *afs_iget(struct super_block *, struct key *,
struct afs_fid *, struct afs_file_status *,
struct afs_callback *,
diff --git a/fs/afs/mntpt.c b/fs/afs/mntpt.c
index 690fea9d84c3..4f5020ec1e2f 100644
--- a/fs/afs/mntpt.c
+++ b/fs/afs/mntpt.c
@@ -72,7 +72,7 @@ static int afs_mntpt_open(struct inode *inode, struct file *file)
*/
static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt)
{
- struct afs_super_info *super;
+ struct afs_super_info *as;
struct vfsmount *mnt;
struct afs_vnode *vnode;
struct page *page;
@@ -142,11 +142,13 @@ static struct vfsmount *afs_mntpt_do_automount(struct dentry *mntpt)
}

/* work out what options we want */
- super = AFS_FS_S(mntpt->d_sb);
- memcpy(options, "cell=", 5);
- strcpy(options + 5, super->volume->cell->name);
- if (super->volume->type == AFSVL_RWVOL || rwpath)
- strcat(options, ",rwpath");
+ as = AFS_FS_S(mntpt->d_sb);
+ if (as->cell) {
+ memcpy(options, "cell=", 5);
+ strcpy(options + 5, as->cell->name);
+ if ((as->volume && as->volume->type == AFSVL_RWVOL) || rwpath)
+ strcat(options, ",rwpath");
+ }

/* try and do the mount */
_debug("--- attempting mount %s -o %s ---", devname, options);
diff --git a/fs/afs/super.c b/fs/afs/super.c
index 1037dd41a622..91ae2c4da35a 100644
--- a/fs/afs/super.c
+++ b/fs/afs/super.c
@@ -64,6 +64,7 @@ static atomic_t afs_count_active_inodes;
enum {
afs_no_opt,
afs_opt_cell,
+ afs_opt_dyn,
afs_opt_rwpath,
afs_opt_vol,
afs_opt_autocell,
@@ -71,6 +72,7 @@ enum {

static const match_table_t afs_options_list = {
{ afs_opt_cell, "cell=%s" },
+ { afs_opt_dyn, "dyn" },
{ afs_opt_rwpath, "rwpath" },
{ afs_opt_vol, "vol=%s" },
{ afs_opt_autocell, "autocell" },
@@ -148,6 +150,11 @@ static int afs_show_devname(struct seq_file *m, struct dentry *root)
const char *suf = "";
char pref = '%';

+ if (as->dyn_root) {
+ seq_puts(m, "none");
+ return 0;
+ }
+
switch (volume->type) {
case AFSVL_RWVOL:
break;
@@ -171,8 +178,12 @@ static int afs_show_devname(struct seq_file *m, struct dentry *root)
*/
static int afs_show_options(struct seq_file *m, struct dentry *root)
{
+ struct afs_super_info *as = AFS_FS_S(root->d_sb);
+
+ if (as->dyn_root)
+ seq_puts(m, ",dyn");
if (test_bit(AFS_VNODE_AUTOCELL, &AFS_FS_I(d_inode(root))->flags))
- seq_puts(m, "autocell");
+ seq_puts(m, ",autocell");
return 0;
}

@@ -212,7 +223,7 @@ static int afs_parse_options(struct afs_mount_params *params,
break;

case afs_opt_rwpath:
- params->rwpath = 1;
+ params->rwpath = true;
break;

case afs_opt_vol:
@@ -220,7 +231,11 @@ static int afs_parse_options(struct afs_mount_params *params,
break;

case afs_opt_autocell:
- params->autocell = 1;
+ params->autocell = true;
+ break;
+
+ case afs_opt_dyn:
+ params->dyn_root = true;
break;

default:
@@ -254,7 +269,7 @@ static int afs_parse_device_name(struct afs_mount_params *params,
int cellnamesz;

_enter(",%s", name);
-
+
if (!name) {
printk(KERN_ERR "kAFS: no volume name specified\n");
return -EINVAL;
@@ -336,7 +351,14 @@ static int afs_test_super(struct super_block *sb, void *data)
struct afs_super_info *as1 = data;
struct afs_super_info *as = AFS_FS_S(sb);

- return as->net == as1->net && as->volume->vid == as1->volume->vid;
+ return (as->net == as1->net &&
+ as->volume &&
+ as->volume->vid == as1->volume->vid);
+}
+
+static int afs_dynroot_test_super(struct super_block *sb, void *data)
+{
+ return false;
}

static int afs_set_super(struct super_block *sb, void *data)
@@ -365,24 +387,29 @@ static int afs_fill_super(struct super_block *sb,
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = AFS_FS_MAGIC;
sb->s_op = &afs_super_ops;
- sb->s_xattr = afs_xattr_handlers;
+ if (!as->dyn_root)
+ sb->s_xattr = afs_xattr_handlers;
ret = super_setup_bdi(sb);
if (ret)
return ret;
sb->s_bdi->ra_pages = VM_MAX_READAHEAD * 1024 / PAGE_SIZE;
- sprintf(sb->s_id, "%u", as->volume->vid);
-
- afs_activate_volume(as->volume);

/* allocate the root inode and dentry */
- fid.vid = as->volume->vid;
- fid.vnode = 1;
- fid.unique = 1;
- inode = afs_iget(sb, params->key, &fid, NULL, NULL, NULL);
+ if (as->dyn_root) {
+ inode = afs_iget_pseudo_dir(sb, true);
+ } else {
+ sprintf(sb->s_id, "%u", as->volume->vid);
+ afs_activate_volume(as->volume);
+ fid.vid = as->volume->vid;
+ fid.vnode = 1;
+ fid.unique = 1;
+ inode = afs_iget(sb, params->key, &fid, NULL, NULL, NULL);
+ }
+
if (IS_ERR(inode))
return PTR_ERR(inode);

- if (params->autocell)
+ if (params->autocell || params->dyn_root)
set_bit(AFS_VNODE_AUTOCELL, &AFS_FS_I(inode)->flags);

ret = -ENOMEM;
@@ -407,7 +434,10 @@ static struct afs_super_info *afs_alloc_sbi(struct afs_mount_params *params)
as = kzalloc(sizeof(struct afs_super_info), GFP_KERNEL);
if (as) {
as->net = afs_get_net(params->net);
- as->cell = afs_get_cell(params->cell);
+ if (params->dyn_root)
+ as->dyn_root = true;
+ else
+ as->cell = afs_get_cell(params->cell);
}
return as;
}
@@ -451,18 +481,20 @@ static struct dentry *afs_mount(struct file_system_type *fs_type,
goto error;
}

- ret = afs_parse_device_name(&params, dev_name);
- if (ret < 0)
- goto error;
+ if (!params.dyn_root) {
+ ret = afs_parse_device_name(&params, dev_name);
+ if (ret < 0)
+ goto error;

- /* try and do the mount securely */
- key = afs_request_key(params.cell);
- if (IS_ERR(key)) {
- _leave(" = %ld [key]", PTR_ERR(key));
- ret = PTR_ERR(key);
- goto error;
+ /* try and do the mount securely */
+ key = afs_request_key(params.cell);
+ if (IS_ERR(key)) {
+ _leave(" = %ld [key]", PTR_ERR(key));
+ ret = PTR_ERR(key);
+ goto error;
+ }
+ params.key = key;
}
- params.key = key;

/* allocate a superblock info record */
ret = -ENOMEM;
@@ -470,20 +502,25 @@ static struct dentry *afs_mount(struct file_system_type *fs_type,
if (!as)
goto error_key;

- /* Assume we're going to need a volume record; at the very least we can
- * use it to update the volume record if we have one already. This
- * checks that the volume exists within the cell.
- */
- candidate = afs_create_volume(&params);
- if (IS_ERR(candidate)) {
- ret = PTR_ERR(candidate);
- goto error_as;
- }
+ if (!params.dyn_root) {
+ /* Assume we're going to need a volume record; at the very
+ * least we can use it to update the volume record if we have
+ * one already. This checks that the volume exists within the
+ * cell.
+ */
+ candidate = afs_create_volume(&params);
+ if (IS_ERR(candidate)) {
+ ret = PTR_ERR(candidate);
+ goto error_as;
+ }

- as->volume = candidate;
+ as->volume = candidate;
+ }

/* allocate a deviceless superblock */
- sb = sget(fs_type, afs_test_super, afs_set_super, flags, as);
+ sb = sget(fs_type,
+ as->dyn_root ? afs_dynroot_test_super : afs_test_super,
+ afs_set_super, flags, as);
if (IS_ERR(sb)) {
ret = PTR_ERR(sb);
goto error_as;
@@ -529,9 +566,11 @@ static void afs_kill_super(struct super_block *sb)
/* Clear the callback interests (which will do ilookup5) before
* deactivating the superblock.
*/
- afs_clear_callback_interests(as->net, as->volume->servers);
+ if (as->volume)
+ afs_clear_callback_interests(as->net, as->volume->servers);
kill_anon_super(sb);
- afs_deactivate_volume(as->volume);
+ if (as->volume)
+ afs_deactivate_volume(as->volume);
afs_destroy_sbi(as);
}

@@ -619,12 +658,16 @@ static void afs_destroy_inode(struct inode *inode)
*/
static int afs_statfs(struct dentry *dentry, struct kstatfs *buf)
{
+ struct afs_super_info *as = AFS_FS_S(dentry->d_sb);
struct afs_fs_cursor fc;
struct afs_volume_status vs;
struct afs_vnode *vnode = AFS_FS_I(d_inode(dentry));
struct key *key;
int ret;

+ if (as->dyn_root)
+ return 0;
+
key = afs_request_key(vnode->volume->cell);
if (IS_ERR(key))
return PTR_ERR(key);